DataOutputStream 實作 DataOutput 介面,繼承自 FilterOutputStream。它包裝任意 OutputStream,提供基本型別(boolean、byte、char、short、int、long、float、double)的固定長度、大端序二進制寫入,配合 DataInputStream 實現跨平台、跨語言的二進制資料交換。核心特性:固定編碼(UTF-8 修改版)、大端序、無緩衝、拋出 IOException。
| 方法 | 位元組數 | 編碼規則 |
|---|---|---|
| writeBoolean(boolean v) | 1 | true=0x01, false=0x00 |
| writeByte(int v) | 1 | 低 8 位 |
| writeShort(int v) | 2 | 大端序:高 8 位在前 |
| writeChar(int v) | 2 | 大端序:Unicode 碼點高 8 位在前 |
| writeInt(int v) | 4 | 大端序:高 24 位至低 8 位 |
| writeLong(long v) | 8 | 大端序:高 56 位至低 8 位 |
| writeFloat(float v) | 4 | Float.floatToIntBits → writeInt |
| writeDouble(double v) | 8 | Double.doubleToLongBits → writeLong |
| writeUTF(String str) | 2 + 長度 | 修改版 UTF-8:前 2 位元組無符號短整數表示長度 |
// writeInt(0x12345678) 寫入 4 位元組:
out.write((v >>> 24) & 0xFF); // 0x12
out.write((v >>> 16) & 0xFF); // 0x34
out.write((v >>> 8) & 0xFF); // 0x56
out.write((v >>> 0) & 0xFF); // 0x78
// 檔案中位元組順序:12 34 56 78
0xxxxxxx(同標準 UTF-8)110xxxxx 10xxxxxx1110xxxx 10xxxxxx 10xxxxxx11000000 10000000(標準 UTF-8 為 1 位元組 0x00)[長度高8位] [長度低8位] [UTF-8資料位元組...] 2 bytes N bytes
public class DataOutputStream extends FilterOutputStream implements DataOutput {
protected int written; // 已寫入位元組計數
private byte[] bytearr = null; // writeUTF 暫存緩衝
public DataOutputStream(OutputStream out) {
super(out);
}
// 基本型別寫入:委派給底層 out,更新 written 計數
public final void writeInt(int v) throws IOException {
out.write((v >>> 24) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
incCount(4);
}
// writeUTF 核心邏輯
public final void writeUTF(String str) throws IOException {
writeUTF(str, this);
}
static int writeUTF(String str, DataOutput out) throws IOException {
int strlen = str.length();
int utflen = 0;
// 計算編碼後位元組長度
for (int i = 0; i < strlen; i++) {
int c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) utflen++;
else if (c > 0x07FF) utflen += 3;
else utflen += 2; // 包含 NUL (0x0000) 情況
}
if (utflen > 65535) throw new UTFDataFormatException("encoded string too long");
// 確保緩衝區足夠
if (bytearr == null || bytearr.length < utflen) bytearr = new byte[utflen * 2];
// 編碼寫入 bytearr
int count = 0;
for (int i = 0; i < strlen; i++) {
int c = str.charAt(i);
if (!((c >= 0x0001) && (c <= 0x007F))) {
if (c > 0x07FF) {
bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F));
bytearr[count++] = (byte) (0x80 | (c & 0x3F));
} else {
bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
bytearr[count++] = (byte) (0x80 | (c & 0x3F));
}
} else {
bytearr[count++] = (byte) c;
}
}
// 寫入長度前綴 + 資料
out.writeShort(utflen);
out.write(bytearr, 0, utflen);
return utflen + 2;
}
}
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class DataOutputStreamDemo {
public static void main(String[] args) {
demoBasicTypes();
demoUTFStrings();
demoProtocolBuilding();
}
// 基本型別寫入與讀回驗證
private static void demoBasicTypes() {
File file = new File("datatypes.bin");
// 寫入
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) {
dos.writeBoolean(true);
dos.writeByte(0x41); // 'A'
dos.writeChar(0x4243); // 'B','C'
dos.writeShort(0x4445); // 0x4445
dos.writeInt(0x12345678);
dos.writeLong(0x0FEDCBA987654321L);
dos.writeFloat(3.14159f);
dos.writeDouble(2.718281828);
} catch (IOException e) { e.printStackTrace(); }
// 讀回驗證
try (DataInputStream dis = new DataInputStream(new FileInputStream(file))) {
System.out.println("Boolean: " + dis.readBoolean());
System.out.printf("Byte: 0x%02X%n", dis.readByte());
System.out.printf("Char: 0x%04X%n", dis.readChar());
System.out.printf("Short: 0x%04X%n", dis.readShort());
System.out.printf("Int: 0x%08X%n", dis.readInt());
System.out.printf("Long: 0x%016X%n", dis.readLong());
System.out.println("Float: " + dis.readFloat());
System.out.println("Double: " + dis.readDouble());
} catch (IOException e) { e.printStackTrace(); }
}
// 字串編碼差異展示
private static void demoUTFStrings() {
String text = "Hello 世界 🚀"; // 含 ASCII、中文、Emoji(補字元)
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("utf_strings.bin"))) {
dos.writeUTF(text);
} catch (IOException e) { e.printStackTrace(); }
// 對比標準 UTF-8
byte[] standardUtf8 = text.getBytes(StandardCharsets.UTF_8);
byte[] modifiedUtf8;
try (DataInputStream dis = new DataInputStream(new FileInputStream("utf_strings.bin"))) {
int len = dis.readUnsignedShort();
modifiedUtf8 = new byte[len];
dis.readFully(modifiedUtf8);
} catch (IOException e) { e.printStackTrace(); return; }
System.out.println("標準 UTF-8 長度: " + standardUtf8.length);
System.out.println("修改版 UTF-8 長度: " + modifiedUtf8.length);
System.out.println("標準 UTF-8 Hex: " + bytesToHex(standardUtf8));
System.out.println("修改版 UTF-8 Hex: " + bytesToHex(modifiedUtf8));
}
// 簡易二進制協定:訊息頭 + 載荷
private static void demoProtocolBuilding() {
// 協定定義:
// [Magic:2 bytes=0xCAFE] [Version:1 byte] [Type:1 byte] [Length:4 bytes] [Payload:N bytes] [Checksum:1 byte]
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("protocol.bin"))) {
String payload = "用戶登入: user123";
byte[] payloadBytes = payload.getBytes(StandardCharsets.UTF_8);
dos.writeShort(0xCAFE); // Magic
dos.writeByte(1); // Version 1
dos.writeByte(0x01); // Type: Login
dos.writeInt(payloadBytes.length); // Length
dos.write(payloadBytes); // Payload
// 簡單校驗和:所有位元組加總取低 8 位
// 這裡為示範,實際需重新計算或預留位置回寫
dos.writeByte(0); // Checksum 佔位
} catch (IOException e) { e.printStackTrace(); }
// 解析驗證
try (DataInputStream dis = new DataInputStream(new FileInputStream("protocol.bin"))) {
int magic = dis.readUnsignedShort();
int version = dis.readUnsignedByte();
int type = dis.readUnsignedByte();
int length = dis.readInt();
byte[] payload = new byte[length];
dis.readFully(payload);
int checksum = dis.readUnsignedByte();
System.out.printf("Magic: 0x%04X, Ver: %d, Type: 0x%02X, Len: %d%n",
magic, version, type, length);
System.out.println("Payload: " + new String(payload, StandardCharsets.UTF_8));
} catch (IOException e) { e.printStackTrace(); }
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) sb.append(String.format("%02X ", b));
return sb.toString();
}
}
| 特性 | DataOutputStream | PrintStream | ObjectOutputStream |
|---|---|---|---|
| 輸出格式 | 基本型別二進制 | 文字格式化 | 物件序列化 |
| 端序 | 大端序 | N/A | 大端序 |
| 字串編碼 | 修改版 UTF-8 | 指定編碼 | 修改版 UTF-8 |
| 跨語言相容 | 高(C/C++/Go 易解析) | 低(需解析文字) | 低(Java 專用) |
| 版本演進 | 手動管理 | 文字相容性好 | serialVersionUID |
| 效能 | 極高 | 中(格式化開銷) | 中(反射開銷) |
writeUTF 對 Emoji 等補字元效率極低(6 位元組/字元),大量文本建議改用標準 UTF-8 + 長度前綴。BufferedOutputStream:new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))。writeUTF 和 println 會導致解析器無法同步。writeChars 逐字元寫入 2 位元組(大端序),無長度前綴,讀取端需預知長度,實用性極低。DataOutputStream 是 Java 二進制協定構建的核心工具,掌握其固定長度、大端序、修改版 UTF-8 等編碼規範,能構建高效、跨平台、跨語言的資料交換格式。