IO流(二)

簡介

IO 流的高級點的用法。

一、IO流的裝飾器模式

當我們需要給一個“基礎” InputStream 附加各種功能時,我們先確定這個能提供數據源的 InputStream 是什麼。

InputStream file = new FileInputStream("test.gz");

如果希望 FileInputStream 能提供緩衝的功能來提高讀取的效率,因此我們用BufferedInputStream 包裝這個 InputStream,得到的包裝類型是BufferedInputStream,但它仍然被視爲一個 InputStream:

InputStream buffered = new BufferedInputStream(file);

假設該文件已經用gzip壓縮了,我們希望直接讀取解壓縮的內容,就可以再包裝一個GZIPInputStream:

InputStream gzip = new GZIPInputStream(buffered);
  • 無論我們包裝多少次,得到的對象始終是 InputStream,我們直接用 InputStream 來引用它,就可以正常讀取。
  • 在疊加多個 FilterInputStream,我們只需要持有最外層的 InputStream,並且,當最外層的 InputStream關閉時(在try(resource)塊的結束處自動關閉),內層的InputStream 的 close() 方法也會被自動調用,並最終調用到最核心的“基礎” InputStream,因此不存在資源泄露。

二、操作Zip

ZipInputStream 是一種 FilterInputStream,它可以直接讀取zip包的內容。另一個JarInputStream 是從 ZipInputStream 派生,它增加的主要功能是直接讀取jar文件裏面的 MANIFEST.MF 文件。因爲本質上 jar 包就是 zip 包,只是額外附加了一些固定的描述文件。

1.讀取zip包
try (ZipInputStream zip = new ZipInputStream(new FileInputStream("test.zip"))) {
    ZipEntry entry = null;
    while ((entry = zip.getNextEntry()) != null) {
        String name = entry.getName();
        if (!entry.isDirectory()) {
            int n;
            while ((n = zip.read()) != -1) {
                ...
            }
        }
    }
}
2.寫入zip包

ZipOutputStream 是一種 FilterOutputStream,它可以直接寫入內容 zip 包。每寫入一個文件前,先調用 putNextEntry(),然後用 write() 寫入 byte[] 數據,寫入完畢後調用 closeEntry() 結束這個文件的打包。

try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream("out.zip"))) {		//指定輸出文件
    File[] files = {new File("MyFile.java"),new File("MyFilter.java")};
    for (File file : files) {
        zip.putNextEntry(new ZipEntry(file.getName()));
        zip.write(getFileDataAsBytes(file));
        zip.closeEntry();
    }
}

public static byte[] getFileDataAsBytes(File f){
 byte[] data = new byte[(int)f.length()];
     try(InputStream is =new FileInputStream(f)){
         int n;
         while((n=is.read(data))!=-1){
             System.out.println("read" + n + "bytes");
         }
     }catch(IOException e){
         e.printStackTrace();
     }
     return data;
 }

三、讀取classpath資源

classpath 中的資源文件,路徑總是以開頭,我們先獲取當前的 Class 對象,然後調用 getResourceAsStream() 就可以直接從 classpath 讀取任意的資源文件,如果資源文件不存在,它將返回 null

try (InputStream input = getClass().getResourceAsStream("/default.properties")) {
    if (input != null) {
        // 業務代碼。。。
    }
}

四、序列化

序列化是指把一個 Java 對象變成二進制內容,本質上就是一個 byte[] 數組。一個 Java 對象要能序列化,必須實現一個特殊的 java.io.Serializable 接口。

1.序列化

把一個Java對象變爲byte[]數組,需要使用ObjectOutputStream。它負責把一個Java對象寫入一個字節流:

OutputStream outputStream = new FileOutputStream("out.txt");
try(ObjectOutputStream output = new ObjectOutputStream(outputStream)){
    // 寫入int:
    output.writeInt(12345);
    // 寫入String:
    output.writeUTF("Hello");
    // 寫入Object:
    output.writeObject(Double.valueOf(123.456));
    output.flush();
}

ObjectOutputStream 既可以寫入基本類型,如 int,boolean,也可以寫入String(以UTF-8編碼),還可以寫入實現了 Serializable 接口的對象。因爲寫入Object時需要大量的類型信息,所以寫入的內容很大。

2.反序列化

和 ObjectOutputStream 相反,ObjectInputStream 負責從一個字節流讀取 Java 對象:

try (ObjectInputStream input = new ObjectInputStream(new FileInputStream("out.txt"))) {
  int n = input.readInt();
  String s = input.readUTF();
  Object d = input.readObject();
} catch (ClassNotFoundException e) {
  e.printStackTrace();
}

readObject()可能拋出的異常有:

  • ClassNotFoundException: 沒有找到對應的Class;
  • InvalidClassException: Class不匹配。

對於 ClassNotFoundException,這種情況常見於一臺電腦上的Java程序把一個Java對象,例如,Person對象序列化以後,通過網絡傳給另一臺電腦上的另一個Java程序,但是這臺電腦的Java程序並沒有定義Person類,所以無法反序列化。
對於 InvalidClassException,這種情況常見於序列化的Person對象定義了一個int類型的age字段,但是反序列化時,Person類定義的age字段被改成了long類型,所以導致class不兼容。
爲了避免這種class定義變動導致的不兼容,Java 的序列化允許 class 定義一個特殊的 serialVersionUID 靜態變量,用於標識Java類的序列化“版本”,通常可以由IDE自動生成。如果增加或修改了字段,可以改變serialVersionUID的值,這樣就能自動阻止不匹配的class版本:

public class Person implements Serializable {
    private static final long serialVersionUID = 2709425275741743919L;
}

五、參考文檔

https://www.liaoxuefeng.com/wiki/1252599548343744/1255945227202752

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章