DataOutputStream.writeUTF写入的是带2字节长度前缀的修改版UTF-8,不支持代理对,无法正确编码大部分emoji及U+20000以上字符;必须用DataInputStream.readUTF读取,否则会乱码或异常。

writeUTF 写入的不是纯 UTF-8 字节流
DataOutputStream.writeUTF 会先写入 2 字节长度前缀(big-endian),再写入经过修改的 UTF-8 编码字节 —— 它把 \u0000 到 \u007F 映射为单字节,\u0080 到 \u07FF 映射为双字节,但 \u0800 及以上仍用三字节,且**不支持代理对(surrogate pairs)**。这意味着它不能正确编码大部分 emoji 和部分中文扩展区字符(如 U+20000 以上)。如果你需要标准 UTF-8,别用 writeUTF。
写入后读取必须用 DataInputStream.readUTF
因为 writeUTF 加了长度前缀,直接用 FileReader 或 Files.readString 会读出乱码或抛 MalformedInputException。唯一安全的配套读法是 DataInputStream.readUTF:
try (DataInputStream dis = new DataInputStream(new FileInputStream("out.dat"))) {
String s = dis.readUTF(); // ✅ 自动解析前缀并解码
}
常见错误包括:用 BufferedReader 读、用 new String(Files.readAllBytes(...), "UTF-8") 解码 —— 这两种都会把前 2 字节当内容读,导致字符串开头出现奇怪字符(如 "\u0000\u0005hello")。
想控制格式?得自己拼接,别依赖 writeUTF
如果目标是“特定格式”,比如 JSON 风格的 "key":"value"、带换行的文本块、或固定字段分隔(如 name|age|city),writeUTF 不适合。它只做“带长度的字符串序列化”,没有分隔、无转义、不兼容文本编辑器查看。
- 要写可读文本:用
PrintWriter或BufferedWriter,显式调用write()+newLine() - 要写结构化二进制:用
DataOutputStream手动写writeInt(长度)、write(原始字节数组) - 要写标准 UTF-8 且带长度:自己计算
s.getBytes(StandardCharsets.UTF_8),再writeShort长度 +write字节
例如手动写标准 UTF-8:
byte[] utf8 = s.getBytes(StandardCharsets.UTF_8); dos.writeShort(utf8.length); dos.write(utf8); // ✅ 后续可用 Files.readAllBytes + new String(..., UTF_8) 读
注意 null 和空字符串的边界行为
writeUTF(null) 会直接抛 NullPointerException;writeUTF("") 写入 0x00 0x00(两个零字节),readUTF 能正常返回空串。但如果你在协议中约定“null 用空字符串代替”,得自己处理,DataOutputStream 不提供空值语义。
真正麻烦的是混合写入:比如先写一个 writeUTF 字符串,再写一个 writeInt,再写一个 writeUTF —— 一旦某次 readUTF 失败(比如文件截断),后续所有 readInt、readUTF 都会错位。这种格式几乎没有容错能力。











