1. 程式人生 > >系統學習 Java IO (十二)----資料流和物件流 DataInputStream/DataOutputStream & ObjectInputStream/ObjectOutputStream

系統學習 Java IO (十二)----資料流和物件流 DataInputStream/DataOutputStream & ObjectInputStream/ObjectOutputStream

DataInputStream/DataOutputStream

允許應用程式以與機器無關方式從底層輸入流中讀取基本 Java 資料型別。

要想使用資料輸出流和輸入流,必須按指定的格式儲存資料,才可以將資料輸入流將資料讀取進來,所以通常使用 DataInputStream 來讀取 DataOutputStream 寫入的資料。

DataInputStream 類能夠從 InputStream 中讀取 Java 基本型別(int,float,long等),而不僅僅是原始位元組。 將InputStream包裝在 DataInputStream 中,就可以從 DataInputStream 中讀取 Java 基本型別。 這就是為什麼它被稱為 DataInputStream - 因為它讀取資料(數字)而不僅僅是位元組。

如果需要讀取的資料包含大於一個位元組的Java 基本型別,則 DataInputStream 非常方便。DataInputStream 希望接受有序多位元組型別資料。

同時使用 DataInputStream 和 DataOutputStream

如前所述,DataInputStream 類通常與 DataOutputStream 一起使用,首先使用 DataOutputStream 寫入資料,然後使用 DataInputStream 再次讀取資料。 以下是示例Java程式碼:

public class DataStream {
    public static void main(String[] args) throws IOException {
        String file = "D:\\test\\output.txt";
        DataOutputStream output = new DataOutputStream(new FileOutputStream(file));
        output.write(1); // 預設是 byte
        output.writeInt(123); // 指定寫入 int
        output.writeInt(321);
        output.writeLong(789);
        output.writeFloat(123.45f);
        output.close();

        // 一定要按照寫入的順序和型別讀取,否則會出錯;
        DataInputStream input = new DataInputStream(new FileInputStream(file));
        byte b = (byte) input.read();
        int i1 = input.readInt();
        int i2 = input.readInt();
        Long l = input.readLong();
        Float f = input.readFloat();
        input.close();

        System.out.println("i1 = " + i1);
        System.out.println("i2 = " + i2);
        System.out.println("b = " + b);
        System.out.println("l = " + l);
        System.out.println("f = " + f);
    }
}

注意:一定要按照寫入的順序和型別讀取,否則會出錯;
其實 DataInputStream 類的實現中,讀取方法中只有一個 read() 方法是真正幹活,其他的 readXXX() 都是呼叫 read() 完成任務。看如下程式碼:

    public final byte readByte() throws IOException {
        int ch = in.read();
        if (ch < 0)
            throw new EOFException();
        return (byte)(ch);
    }

    public final char readChar() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        if ((ch1 | ch2) < 0)
            throw new EOFException();
        return (char)((ch1 << 8) + (ch2 << 0));
    }

    public final int readInt() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        int ch3 = in.read();
        int ch4 = in.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0)
            throw new EOFException();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    }

可以看到,XXX 佔 多少個位元組,就會在其內部呼叫多少次 read() 方法。

ObjectInputStream/ObjectOutputStream

和 DataInputStream 包裝成 Java 基本型別類似,ObjectInputStream 類能夠從 InputStream 中讀取Java物件,而不僅僅是原始位元組。 當然,讀取的位元組必須表示有效的序列化 Java 物件。 通常,使用 ObjectInputStream 來讀取 ObjectOutputStream 編寫(序列化)的物件。
下面是一個例子:

public class ObjectStream {
    public static void main(String[] args) throws Exception {
        // Serializable 是一個標識介面,實現類只要承諾能被序列化就行了
        class People implements Serializable {
            String name;
            int age;
        }
        File file = new File("D:\\test\\object.data");

        ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(file));
        People someOne = new People();
        someOne.name = "Json";
        someOne.age = 18;
        output.writeObject(someOne);
        output.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream(file));
        People people = (People) input.readObject();
        System.out.println("name = " + people.name + ", age = " + people.age);
        input.close();
    }
}
Close()

使用完資料流後記得關閉它。 關閉 DataInputStream 還將關閉 DataInputStream 正在讀取的 InputStream 例項。可以使用 try-with-resources 方式自動關閉。ObjectInputStream 同理。

Serializable

如果一個類要進行序列化和反序列化,就必須實現 Serializable 標記介面,這樣就可以使用 ObjectOutputStream 完成 Java 物件序列化(寫入),使用 ObjectInputStream 完成反序列化(讀取)。

Serializable 是一個標記介面意味著它不包含任何方法。 因此,實現 Serializable 的類不必實現任何特定方法,只是告訴 Java 該類物件支援序列化。

serialVersionUID

除了實現 Serializable 介面之外,用於序列化的類還應包含名為 serialVersionUID 的 private static final long 變數。
Java 的物件序列化 API 使用 serialVersionUID 變數來確定反序列化物件是否是使用相同版本的類進行序列化的,因為它現在正嘗試將其反序列化。

想象一下,Person 物件被序列化為磁碟。 然後對 Person 類進行更改。 然後反序列化儲存的 Person 物件。 這樣,序列化的 Person 物件可能與 Person 類的新版本不對應。
要檢測此類問題,實現 Serializable 的類應包含 serialVersionUID 欄位。 如果對類進行了重大更改,則還應更改其 serialVersionUID 值。
許多 Java IDE 包含生成 serialVersionUID 的工具,可以使用工具生成的 UID 。

在今天的世界(2015年之後)中,許多 Java 專案使用與 Java 序列化機制不同的機制來序列化 Java 物件。 例如,Java 物件被序列化為 JSON,BSON 或其他更優化的二進位制格式。 這具有以下優點:物件也可由非 Java 應用程式讀取。 例如,在 Web 瀏覽器中執行的 JavaScript 可以本地序列化和反序列化 JSON 中的物件。
順便說一下,這些其他物件序列化機制通常不需要 Java 類實現 Serializabl e。 他們通常使用 Java 反射來檢查類,這裡是 Java IO 教程,具體要看看 Java Json 的教程了。