首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java IO 數據流:DataOutputStream 基本型別二進制輸出完整指南

     編輯:Java 相關知識

核心定位:機器無關的基本型別二進制寫入

DataOutputStream 實作 DataOutput 介面,繼承自 FilterOutputStream。它包裝任意 OutputStream,提供基本型別(boolean、byte、char、short、int、long、float、double)的固定長度、大端序二進制寫入,配合 DataInputStream 實現跨平台、跨語言的二進制資料交換。核心特性:固定編碼(UTF-8 修改版)、大端序、無緩衝、拋出 IOException。

基本型別寫入規範

方法位元組數編碼規則
writeBoolean(boolean v)1true=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)4Float.floatToIntBits → writeInt
writeDouble(double v)8Double.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

writeUTF 修改版 UTF-8 格式詳解

編碼規則

  • 字元 U+0001 ~ U+007F:1 位元組 0xxxxxxx(同標準 UTF-8)
  • 字元 U+0080 ~ U+07FF:2 位元組 110xxxxx 10xxxxxx
  • 字元 U+0800 ~ U+FFFF:3 位元組 1110xxxx 10xxxxxx 10xxxxxx
  • 補字元 (U+10000+):以 Surrogate Pair 形式各自編碼,共 6 位元組(標準 UTF-8 為 4 位元組)
  • U+0000 (NUL):編碼為 2 位元組 11000000 10000000(標準 UTF-8 為 1 位元組 0x00)
  • 長度前綴:無符號短整數(2 位元組),最大 65535 位元組

位元組結構

[長度高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 vs 其他輸出流

特性DataOutputStreamPrintStreamObjectOutputStream
輸出格式基本型別二進制文字格式化物件序列化
端序大端序N/A大端序
字串編碼修改版 UTF-8指定編碼修改版 UTF-8
跨語言相容高(C/C++/Go 易解析)低(需解析文字)低(Java 專用)
版本演進手動管理文字相容性好serialVersionUID
效能極高中(格式化開銷)中(反射開銷)

最佳實踐與避坑指南

  1. 明確協定文檔:二進制協定必須文檔化每個欄位的型別、順序、端序、編碼規則。
  2. 長度前綴策略:變長資料(字串、陣列、Blob)務必加長度前綴,避免邊界模糊。
  3. 校驗和/CRC:關鍵協定加入 CRC32 或簡單校驗和,檢測傳輸損壞。
  4. 版本號欄位:協定首部保留版本號,支援向前/向後相容演進。
  5. 補字元處理:writeUTF 對 Emoji 等補字元效率極低(6 位元組/字元),大量文本建議改用標準 UTF-8 + 長度前綴。
  6. 緩衝加速:大量寫入時包裝 BufferedOutputStream:new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))。
  7. 不要混用文字與二進制:同一流中交替寫入 writeUTF 和 println 會導致解析器無法同步。

常見誤區

  • 以為 writeUTF 是標準 UTF-8:修改版 UTF-8 在 NUL、補字元處理上不同,跨語言解析需實作對應邏輯。
  • 忽略大端序:x86/x64 為小端序,直接記憶體複製會導致數值錯誤,必須使用 DataOutputStream/InputStream 或 ByteBuffer.order(BIG_ENDIAN)。
  • writeChars 寫入字串:writeChars 逐字元寫入 2 位元組(大端序),無長度前綴,讀取端需預知長度,實用性極低。

DataOutputStream 是 Java 二進制協定構建的核心工具,掌握其固定長度、大端序、修改版 UTF-8 等編碼規範,能構建高效、跨平台、跨語言的資料交換格式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved