DataInputStream 實作 DataInput 介面,繼承自 FilterInputStream。它包裝任意 InputStream,提供基本型別的固定長度、大端序二進制讀取,配合 DataOutputStream 實現跨平台二進制資料交換。核心特性:大端序讀取、修改版 UTF-8 解碼、readFully 保證讀滿、拋出 IOException/EOFException。
| 方法 | 位元組數 | 回傳型別 | 解碼規則 |
|---|---|---|---|
| readBoolean() | 1 | boolean | 非 0 為 true |
| readByte() | 1 | byte | 有號位元組 |
| readUnsignedByte() | 1 | int (0-255) | 無號位元組 |
| readShort() | 2 | short | 大端序有號 |
| readUnsignedShort() | 2 | int (0-65535) | 大端序無號 |
| readChar() | 2 | char | 大端序 Unicode 碼點 |
| readInt() | 4 | int | 大端序有號 |
| readLong() | 8 | long | 大端序有號 |
| readFloat() | 4 | float | readInt → Float.intBitsToFloat |
| readDouble() | 8 | double | readLong → Double.longBitsToDouble |
| readUTF() | 2+長度 | String | 修改版 UTF-8 解碼 |
// readInt() 範例:從 4 位元組重建 int
public final int readInt() throws IOException {
int ch1 = in.read(); // 高 24-31 位
int ch2 = in.read(); // 高 16-23 位
int ch3 = in.read(); // 高 8-15 位
int ch4 = in.read(); // 低 0-7 位
if ((ch1 | ch2 | ch3 | ch4) < 0) throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
// readLong() 使用 8 位元組暫存陣列
private byte readBuffer[] = new byte[8];
public final long readLong() throws IOException {
readFully(readBuffer, 0, 8);
return (((long)readBuffer[0] << 56) +
((long)(readBuffer[1] & 255) << 48) +
((long)(readBuffer[2] & 255) << 40) +
((long)(readBuffer[3] & 255) << 32) +
((long)(readBuffer[4] & 255) << 24) +
((readBuffer[5] & 255) << 16) +
((readBuffer[6] & 255) << 8) +
((readBuffer[7] & 255) << 0));
}
// 讀滿位元組陣列,否則拋出 EOFException
public final void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
public final void readFully(byte[] b, int off, int len) throws IOException {
if (len < 0) throw new IndexOutOfBoundsException();
int n = 0;
while (n < len) {
int count = in.read(b, off + n, len - n);
if (count < 0) throw new EOFException();
n += count;
}
}
關鍵差異:InputStream.read() 可能讀不滿(返回實際讀取數),readFully() 循環讀取直到滿足長度或遇到 EOF,適合結構化二進制解析。
utflen(2 位元組)作為資料長度bytearr 和字元緩衝區 chararrreadFully(bytearr, 0, utflen) 讀滿資料位元組bytearr 解碼為 chararr:
((b1&0x1F)<<6) | (b2&0x3F)((b1&0x0F)<<12) | ((b2&0x3F)<<6) | (b3&0x3F)new String(chararr, 0, chararr_count)
// 取首位元組高 4 位判斷 UTF-8 序列長度
int c = bytearr[count] & 0xFF; // 轉無號 0-255
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx (0-127) → 1 位元組
chararr[chararr_count++] = (char) c;
count++;
break;
case 12: case 13:
// 110xxxxx (192-223) → 2 位元組
// 驗證第 2 位元組為 10xxxxxx
char2 = bytearr[count+1] & 0xFF;
if ((char2 & 0xC0) != 0x80) throw UTFDataFormatException;
chararr[chararr_count++] = (char)(((c & 0x1F) << 6) | (char2 & 0x3F));
count += 2;
break;
case 14:
// 1110xxxx (224-239) → 3 位元組
char2 = bytearr[count+1] & 0xFF;
char3 = bytearr[count+2] & 0xFF;
if ((char2 & 0xC0) != 0x80 || (char3 & 0xC0) != 0x80) throw UTFDataFormatException;
chararr[chararr_count++] = (char)(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | (char3 & 0x3F));
count += 3;
break;
default:
// 10xxxxxx (延續位元組) 或 1111xxxx (無效) → 異常
throw new UTFDataFormatException("malformed input around byte " + count);
}
import java.io.*;
import java.nio.charset.StandardCharsets;
public class DataInputStreamDemo {
public static void main(String[] args) {
demoBasicReading();
demoProtocolParsing();
demoRobustReading();
}
// 基本型別讀取驗證
private static void demoBasicReading() {
File file = new File("datatypes.bin");
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("UnsignedByte: %d%n", dis.readUnsignedByte());
System.out.printf("Char: 0x%04X%n", dis.readChar());
System.out.printf("Short: %d (0x%04X)%n", dis.readShort(), dis.readShort());
System.out.printf("UnsignedShort: %d%n", dis.readUnsignedShort());
System.out.printf("Int: %d (0x%08X)%n", dis.readInt(), dis.readInt());
System.out.printf("Long: %d (0x%016X)%n", dis.readLong(), dis.readLong());
System.out.println("Float: " + dis.readFloat());
System.out.println("Double: " + dis.readDouble());
System.out.println("UTF: " + dis.readUTF());
} catch (IOException e) { e.printStackTrace(); }
}
// 解析自定義二進制協定
private static void demoProtocolParsing() {
// 協定:[Magic:2] [Version:1] [Type:1] [Length:4] [Payload:N] [Checksum:1]
try (DataInputStream dis = new DataInputStream(new FileInputStream("protocol.bin"))) {
int magic = dis.readUnsignedShort();
if (magic != 0xCAFE) throw new IOException("Magic 不符: " + Integer.toHexString(magic));
int version = dis.readUnsignedByte();
int type = dis.readUnsignedByte();
int length = dis.readInt();
if (length < 0 || length > 1_000_000)
throw new IOException("長度異常: " + length);
byte[] payload = new byte[length];
dis.readFully(payload); // 保證讀滿
int checksum = dis.readUnsignedByte();
// 驗證校驗和(簡單加總)
int calcSum = 0;
calcSum += (magic >> 8) & 0xFF;
calcSum += magic & 0xFF;
calcSum += version;
calcSum += type;
calcSum += (length >> 24) & 0xFF;
calcSum += (length >> 16) & 0xFF;
calcSum += (length >> 8) & 0xFF;
calcSum += length & 0xFF;
for (byte b : payload) calcSum += b & 0xFF;
calcSum &= 0xFF;
if (calcSum != checksum) {
System.err.println("校驗和失敗: 計算=" + calcSum + " 文件=" + checksum);
}
String text = new String(payload, StandardCharsets.UTF_8);
System.out.printf("Ver: %d, Type: 0x%02X, Len: %d, Checksum: %s%n",
version, type, length, calcSum == checksum ? "OK" : "FAIL");
System.out.println("Payload: " + text);
} catch (IOException e) { e.printStackTrace(); }
}
// 健壯讀取模式:處理截斷、損壞、超大資料
private static void demoRobustReading() {
File file = new File("corrupted.bin");
// 建立損壞檔案:長度聲稱 100 但只有 10 位元組
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) {
dos.writeInt(100); // 長度
dos.write(new byte[10]); // 只有 10 位元組資料
} catch (IOException e) { e.printStackTrace(); }
// 健壯讀取
try (DataInputStream dis = new DataInputStream(new FileInputStream(file))) {
int claimedLen = dis.readInt();
System.out.println("聲稱長度: " + claimedLen);
if (claimedLen < 0 || claimedLen > 10_000_000) {
throw new IOException("長度明顯異常,拒絕分配記憶體");
}
byte[] data = new byte[claimedLen];
try {
dis.readFully(data);
System.out.println("讀取成功: " + data.length + " 位元組");
} catch (EOFException e) {
System.err.println("檔案截斷: 預期 " + claimedLen + " 位元組,實際讀取 " + (claimedLen - data.length) + " 位元組缺失");
// 可選:讀取可用部分、記錄錯誤、跳過、重試等
}
} catch (IOException e) { e.printStackTrace(); }
}
}
// 正確顯示位元組/短整數的十六進制
private static String byteToHexString(byte val) {
return String.format("0x%02X", val & 0xFF); // &0xFF 移除符號擴展
}
private static String shortToHexString(short val) {
return String.format("0x%04X", val & 0xFFFF);
}
private static String charToHexString(char val) {
return String.format("0x%04X", (int) val); // char 本身無號
}
// 使用範例
System.out.println(byteToHexString((byte) 0x8F)); // 0x8F (而非 0xFFFFFF8F)
System.out.println(shortToHexString((short) 0x8FCF)); // 0x8FCF
| 特性 | DataInputStream | BufferedReader | ObjectInputStream |
|---|---|---|---|
| 資料格式 | 基本型別二進制 | 文字行導向 | 物件序列化 |
| 端序 | 大端序 | N/A | 大端序 |
| 字串編碼 | 修改版 UTF-8 | 指定編碼 | 修改版 UTF-8 |
| 讀取保證 | readFully 讀滿 | readLine 讀行 | 完整物件圖 |
| 跨語言相容 | 高 | 低 | 低 |
| 適用場景 | 二進制協定、檔案格式、網路封包 | 文字日誌、配置檔、CSV | Java 物件持久化、RMI |
DataOutputStream,讀取端用 DataInputStream,嚴格對應型別順序。new DataInputStream(new BufferedInputStream(new FileInputStream(file))) 大幅提升小塊讀取效能。readUnsignedByte/Short 返回 int 避免符號誤解,十六進制顯示務必 & 0xFF/0xFFFF。
// 使用 ByteBuffer 進行相同解析,支援大小端切換、記憶體映射
try (FileChannel channel = FileChannel.open(Path.of("data.bin"), StandardOpenOption.READ)) {
ByteBuffer buf = ByteBuffer.allocate(1024).order(ByteOrder.BIG_ENDIAN);
channel.read(buf);
buf.flip();
int magic = buf.getShort() & 0xFFFF;
byte version = buf.get();
byte type = buf.get();
int length = buf.getInt();
byte[] payload = new byte[length];
buf.get(payload);
// ...
}
DataInputStream 是 Java 二進制資料解析的基石,掌握其大端序讀取、readFully 語義、修改版 UTF-8 解碼與異常處理,能構建健壯、高效、跨平台的二進制協定解析器。