1. 程式人生 > 其它 >Java 基礎 (IO 物件流,隨機儲存檔案流)

Java 基礎 (IO 物件流,隨機儲存檔案流)

IO 物件流

ObjectInputStream 和 OjbectOutputSteam

用於儲存和讀取基本資料型別資料或物件的處理流。它的強大之處就是可以把Java中的物件寫入到資料來源中,也能把物件從資料來源中還原回來。

  • 序列化: 用 ObjectOutputStream 類儲存基本型別資料或物件的機制
  • 反序列化: 用 ObjectInputStream 類讀取基本型別資料或物件的機制
  • ObjectOutputStream 利 ObjectInputStream 不能序列化 static 和transient 修飾的成員變數

物件序列化機制允許把記憶體中的 Java 物件轉換成平臺無關的二進位制流,從而允許把這種二進位制流持久地儲存在磁碟上,或通過網路將這種二進位制流傳輸到另一個網路節點。當其它程式獲取了這種二進位制流,就可以恢復成原來的 Java 物件

序列化的好處在於可將任何實現了 Serializable 介面的物件轉化為位元組資料,使其在儲存和傳輸時可被還原

如果需要讓某個物件支援序列化機制,則必須讓物件所屬的類及其屬性是可序列化的,為了讓某個類是可序列化的,該類必須實現如下兩個介面之一。否則,會丟擲 NotSerializableException 異常

> Serializable
> Externalizable

凡是實現 Serializable 介面的類都有一個表示序列化版本識別符號的靜態變數;

> private static final long serialVersionUID;
> serialVersionUID 用來表明類的不同版本間的相容性。簡言之,其目的是以序列化物件進行版本控制,有關各版本反序列化時是否相容。
> 如果類沒有顯示定義這個靜態常量,它的值是Java執行時環境根據類的內部細節自動生成的。若類的例項變數做了修改,seriaVersionUID 可能發生變化。故建議,顯式宣告。

Person.java

package com.klvchen.java;

import java.io.Serializable;

public class Person implements Serializable {

    public static final long serialVersionUID = 475684125L;

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

ObjectInputOutputStreamTest.java

package com.klvchen.java;

import org.junit.Test;

import java.io.*;

public class ObjectInputOutputStreamTest {
    /*
    序列化過程:將記憶體中的 java 物件儲存到磁碟中或通過網路傳輸出去
    使用 ObjectOutputStream 實現
     */

    @Test
    public void testObjectOutputStream(){
        ObjectOutputStream oos = null;

        try {
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            oos.writeObject(new String("我愛北京天安門"));
            oos.flush();

            oos.writeObject(new Person("wangyi", 23));
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /*
    反序列化:將磁碟檔案中的物件還原為記憶體中的一個 java 物件
    使用ObjectInputStream 來實現
     */
    @Test
    public void testObjectInputStream(){
        ObjectInputStream ois = null;

        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));

            Object obj = ois.readObject();
            String str = (String) obj;

            Person p = (Person) ois.readObject();

            System.out.println(str);
            System.out.println(p);

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

IO 隨機儲存檔案流 RandomAccessFile

* RandomAccessFile 宣告在 java.io 包下,但直接繼承於java.lang.Object類。並且它實現了 DataInput、DataOutput 這兩個介面,也就意味著這個類既可以讀也可以寫。

* RandomAccessFile 類支援“隨機訪問”的方式,程式可以直接跳到檔案的任意地方來讀、寫檔案
> 支援只訪問檔案的部分內容
> 可以向已存在的檔案後追加內容

* RandomAccessFile 物件包含一個記錄指標,用以標示當前讀寫處的位置。RandomAccessFile 類物件可以自由移動記錄指標:
> long getFilePointer(): 獲取檔案記錄指標的當前位置
> void seek(long pos): 將檔案記錄指標定位到 pos 位置

* 構造器
public RandomAccessFile(File file,String mode)
public RandomAccessFile(String name,String mode)

* 建立 RandomAccessFile 類例項需要指定一個 mode引數,該引數指定RandomAccessFile 的訪問模式:
>r: 以只讀方式開啟
>rw: 開啟以便讀取和寫入
>rwd: 開啟以便讀取和寫入;同步檔案內容的更新
>rws: 開啟以便讀取和寫入;同步檔案內容和元資料的更新

* 如果模式為只讀r。則不會建立檔案,而是會去讀取一個已經存在的檔案,如果讀取的檔案不存在則會出現異常。如果模式為rw讀寫。如果檔案不存在則會去建立檔案,如果存在則不會建立。

如果 RandomAccessFile 作為輸出流時,寫出到的檔案如果不存在,則在執行過程中自動建立如果寫出到的檔案存在,則會對原有檔案內容進行覆蓋。(預設情況下,從頭覆蓋>|

hello.txt

abcdefghijklm

RandomAccessFileTest.java

package com.klvchen.java;

import org.junit.Test;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;

public class RandomAccessFileTest {

    @Test
    public void test1()  {
        RandomAccessFile raf1 = null;
        RandomAccessFile raf2 = null;
        try {
            raf1 = new RandomAccessFile(new File("1.png" ),"r");
            raf2 = new RandomAccessFile(new File("2.png"), "rw");

            byte[] buffer = new byte[1024];
            int len;
            while ((len = raf1.read(buffer)) != -1) {
                raf2.write(buffer, 0 ,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (raf1 != null) {
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (raf2 != null) {
                try {
                    raf2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Test
    public void test2() throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw");

        raf1.seek(3); //將指標調到角標為3的位置
        //儲存指標3後面的所有資料到StringBuilder中
        StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());

        byte[] buffer = new byte[20];
        int len;
        while ((len = raf1.read(buffer)) != -1) {
            builder.append(new String(buffer, 0, len));
        }

        //調回指標,寫入"xyz"
        raf1.seek(3);
        raf1.write("xyz".getBytes());

        //將StringBuilder中的資料寫入到檔案中
        raf1.write(builder.toString().getBytes());

        raf1.close();
    }
}