Java I/O全文摘要(十)過濾流,壓縮流

1  壓縮流

java.util.zip 包,包含了6個流類和其他6個其他各式各樣用於讀寫zip,gzip和其他解壓縮格式。

Java使用這些類來讀取寫入JAR文檔和顯示PNG圖片。

你可以將這些工具用於通用的壓縮和解壓縮過程。

由於有了這些類,壓縮和解壓縮變得很容易。



2 Inflaters and Deflaters (解壓者和壓縮者)

java.util.zip.Deflater 和 java.util.zip.Inflater 爲其他類提供壓縮和解壓的能力。

它們是Java的壓縮和解壓的引擎

大部分都是CPU密集型計算,所以底層使用了C來寫.



3 壓縮數據

Deflater類包含了壓縮塊數據的方法。

你可以選擇壓縮格式,級別和策略。

Deflater9步完成壓縮:
1. 構造Deflater對象。
2. 選擇策略(可選)。
3. 設置壓縮級別(可選)。
4. 重置字典(可選)。
5. 設置輸入
6. 重複壓縮數據,直到needsInput()返回true
7. 如果有更多的可用數據,返回第5步繼續添加。
否則,進行第8步
8. 完成數據
9. 如果有更多的流需要壓縮,重啓壓縮器。


通常,你無需直接使用這個類,而是使用諸如DeflaterInputStream或 DeflaterOutputStream這樣的壓縮流。這個類提供了一些便利的方法。


4 構造解壓對象 

三個構造函數:

public Deflater(int level, boolean useGzip)
public Deflater(int level)
public Deflater( )


level越大,通常壓縮時間越長,壓縮得到的包越小

例如:

public static final int NO_COMPRESSION = 0;
public static final int BEST_SPEED = 1;
public static final int BEST_COMPRESSION = 9;
public static final int DEFAULT_COMPRESSION = -1;

如果useGzip爲true,則使用gzip;否則,使用zlib格式。


5 策略選擇

Java支持的壓縮策略包括:filtered, Huffman, and default

public void setStrategy(int strategy)

支持:

Deflater.FILTERED = 1;
Deflater.HUFFMAN_ONLY = 2;
Deflater.DEFAULT_STRATEGY = -1;


如果出現不支持的策略,拋出IllegalArgumentException

默認策略總是重複的詞

哈弗曼編碼:各個詞頻不一樣

過濾器:可以妥協的2進制數據(例如音頻視頻之類)


6 設置壓縮級別

除了在構造函數中進行級別的指定,你還可以通過如下方法:

public void setLevel(int Level)
來實現對壓縮級別的指定。

總的而言,壓縮速度和壓縮質量總是負相關的

好的編碼方式是使用下面幾個常量中的一個:

Deflater.NO_COMPRESSION (0), 
Deflater.BEST_SPEED (1), 
Deflater.BEST_COMPRESSION (9), 
Deflater.DEFAULT_COMPRESSION (-1)
而不是一個顯式的值。

在小文件上做的測試,發現0~9直接差別不大。

對於GIF, JPEG, or PNG這類圖片文件,他們具有內置的壓縮方式,所以通用的壓縮反而可能增加他們的體積。


7 設置壓縮字典

默認的壓縮字典是根據順序來進行的,它將最先讀到的內容放置到字典中。第二次讀到這個詞時,將它用對應字典的位置來替換。

你可以爲你的壓縮文件預設這樣的文本值

額外值得說明的是:由於文件直接差別很大,而設置字典通常需要字典本身的存儲空間,所以設計上一定要小心謹慎。


8 其他過程

byte[] originalBytes = ...

     Deflater deflater = new Deflater();
     deflater.setInput(originalBytes);
     deflater.finish();

     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     byte[] buf = new byte[8192];
     while (!deflater.finished()) {
         int byteCount = deflater.deflate(buf);
         baos.write(buf, 0, byteCount);
     }
     deflater.end();

     byte[] compressedBytes = baos.toByteArray();

設置輸入,壓縮完成,將壓縮得到內容寫到內存中。

如果你需要使用相同的策略,無需創建一個新的壓縮器,而是使用壓縮器的reset()方法



9 檢查解壓器的狀態

Deflater提供了幾個用於檢查狀態的方法

public int getAdler( )

提供了對未壓縮內容的32位地址校驗和。


public int getTotalIn( )
返回setInput中設置的字節數


public int getTotalOut( )

返回deflate方法返回的字節數


例如下面這個代碼,說明了壓縮了多少:

System.out.println((1.0 - def.getTotalOut()/def.getTotalIn( ))*100.0 +
"% saved");



10 解壓器

解壓器包含了用於解壓zip, gzip, or zlib格式的方法。由於參數較少,使用比Deflater 要簡單。

你需要做下面幾部:

1 構件一個 Inflater對象,

2 設置輸入經過壓縮了的數據

3 調用needsDictionary( )判斷是否需要預置字典

4 如果需要字典,則使用getAdler( ) 獲得字典的32位地址校驗和,然後調用setDictionary( )來設置字典

5 解壓數據直到inflate( ) 返回0

6 如果needsInput( )返回真,返回第2步繼續添加數據

7 使用finished()來返回true

如果需要使用Inflater 解壓更多數據,重置它。


你通常很少使用這個類,考慮使用InflaterInputStream 或 InflaterOutputStream。


11 解壓其他操作

可類比參考壓縮流:

byte[] compressedBytes = ...
     int decompressedByteCount = ... // From your format's metadata.
     Inflater inflater = new Inflater();
     inflater.setInput(compressedBytes, 0, compressedBytes.length);
     byte[] decompressedBytes = new byte[decompressedByteCount];
     if (inflater.inflate(decompressedBytes) != decompressedByteCount) {
         throw new AssertionError();
     }
     inflater.end();


12 檢查解壓器的狀態

public int getAdler( )
獲得32位校驗和


public int getTotalIn( )
返回setInput( )設置的字節數


public int getTotalOut( )
返回通過inflate( )總數


public int getRemaining( )
返回還剩下的壓縮字節數

13 高級壓縮流

總而言之,Deflater和Inflater有一些原始。所以我們推薦使用:

java.util.zip.DeflaterOutputStream 類是一個過濾流,它在將內容寫到潛在的流之前會進行壓縮。

java.util.zip.InflaterInputStream 類將在把內容傳遞到程序之前進行一個解壓。

java.util.zip.GZIPInputStreamjava.util.zip.GZIPOutputStream 與上面做的事情相同,不過他是GZIP 


14 壓縮流使用例子

你就像使用別的過濾流一樣使用就好

import java.io.*;
import java.util.zip.*;
public class GZipper {
  public final static String GZIP_SUFFIX = ".gz";
  public static void main(String[] args) {
    for (int i = 0; i < args.length; i++) {
      try {
        InputStream fin = new FileInputStream(args[i]);
        OutputStream fout = new FileOutputStream(args[i] + GZIP_SUFFIX);
        GZIPOutputStream gzout = new GZIPOutputStream(fout);
        for (int c = fin.read(); c != -1; c = fin.read( )) {
          gzout.write(c);
        }
        gzout.close( );
      }
      catch (IOException ex) {
        System.err.println(ex);
      }
    }
  }
}


 


15 Zip文件

Gzip僅僅是壓縮方式,而zip則既是壓縮方式,又是歸檔方式。

java.util.zip.ZipFile代表了整個文檔。

java.util.zip.ZipEntry代表了在歸檔文件中的單獨的一個文件。


ZipFile擁有多個構造方法

public ZipFile(String filename) throws ZipException, IOException
public ZipFile(File file) throws ZipException, IOException
public ZipFile(File file, int mode) throws IOException


其中 mode可以爲:

ZipFile.READZipFile.DELETE

設定爲ZipFile.DELETE後,會自動刪除文件。


你可以使用:

public Enumeration<? extends ZipEntry> entries( )

來獲得ZipFile的所有條目。


獲得一個ZipEntry:

public ZipEntry getEntry(String name)

額外說明:ZipEntry中的getName的方法將返回這個名字。


獲得對應ZipEntry的輸入流:

public InputStream getInputStream(ZipEntry ze) throws IOException


16 Zip條目

使用構造方法來進行對象拷貝。

拷貝內容將會包含特定的屬性,例如下面這些get方法對應的屬性:

public String  getName( )
public long    getTime( )
public long    getSize( )
public long    getCompressedSize( )
public long    getCrc( )
public int     getMethod( )
public byte[]  getExtra( )
public String  getComment( )
public boolean isDirectory( )


17 ZipOutputStream

Java可以使用兩種格式保存zip格式。無壓縮的或者壓縮的。

分別表示爲:

public static final int STORED = ZipEntry.STORED;
public static final int DEFLATED = ZipEntry.DEFLATED;


解壓文件將使用Deflater提供的解壓方法來進行解壓.(

因爲:

public class ZipOutputStream  extends DeflaterOutputStream
                              implements ZipConstants

)

由於zip不僅是壓縮形式也是歸檔形式,所以它包含了多個內容項,這些內容項各自包含了壓縮和儲存的文件。


此外,zip文件頭部包含了歸檔本身的信息,例如條目的位置。

因此,不該在輸出流中寫入原始的壓縮的數據。

因此必須創建連續的文件,然後寫入到條目中。

下面幾步:

  1. Construct a ZipOutputStream object from an underlying stream, most often a file output stream.

  2. Set the comment for the zip file (optional).

  3. Set the default compression level and method (optional).

  4. Construct a ZipEntry object.

  5. Set the metainformation for the zip entry.

  6. Put the zip entry in the archive.

  7. Write the entry's data onto the output stream.

  8. Close the zip entry (optional).

  9. Repeat steps 4 through 8 for each entry you want to store in the archive.

  10. Finish the zip output stream.

  11. Close the zip output stream.


常規的write( ), flush( ), and close( )將導致錯誤。


詳細解釋:

1> 構造和初始化ZipOutputStream:

FileOutputStream fout = new FileOutputStream("data.zip");
ZipOutputStream  zout = new ZipOutputStream(fout);

2> 設置評論

public void setComment(String comment)

3> 設置壓縮級別和方法

設置方法:

public void setMethod(int method)
包括:
ZipOutputStream.DEFLATED (壓縮); the alternative is ZipOutputStream.STORED (非壓縮). 


設置級別:

public void setLevel(int level)
0無壓縮 9最高壓縮


4> 構建ZipEntry並放入

public void putNextEntry(ZipEntry ze) throws IOException

5> 寫入Entry數據

public void write(byte[] data, int offset, int length) throws IOException

6> 關閉Entry

public void closeEntry( ) throws IOException

7> 完成輸出流

public void finish( ) throws IOException

8> 關閉輸出流

public void close( ) throws IOException


例子(截取):

FileOutputStream fout = new FileOutputStream(outputFile);
    ZipOutputStream zout = new ZipOutputStream(fout);
    zout.setLevel(level);
    for (int i = start; i < args.length; i++) {
      ZipEntry ze = new ZipEntry(args[i]);
      FileInputStream fin = new FileInputStream(args[i]);
      try {
        System.out.println("Compressing " + args[i]);
        zout.putNextEntry(ze);
        for (int c = fin.read(); c != -1; c = fin.read( )) {
          zout.write(c);
        }
      }
      finally {
        fin.close( );
      }
    }
    zout.close( );

從這個例子我們看出:

文件輸入流讀取字符,然後用zip輸出流進行輸出。

所以ZipEntry用於指定輸出的壓縮內容。

大概就是兩步,指定nextEntry ;然後寫入


18 The ZipInputStream Class

和輸出流很相似,也有幾步:

  1. Construct a ZipInputStream object from an underlying stream.

  2. Open the next zip entry in the archive.

  3. Read data from the zip entry using InputStream methods such as read( ).

  4. Close the zip entry (optional).

  5. Repeat steps 2 through 4 as long as there are more entries (files) remaining in the archive.

  6. Close the zip input stream.


樣例:

import java.util.zip.*;
import java.io.*;
public class Unzipper2 {
  public static void main(String[] args) throws IOException {
    for (int i = 0; i < args.length; i++) {
      FileInputStream fin = new FileInputStream(args[i]);
      ZipInputStream zin = new ZipInputStream(fin);
      ZipEntry ze = null;
      while ((ze = zin.getNextEntry( )) != null) {
        System.out.println("Unzipping " + ze.getName( ));
        FileOutputStream fout = new FileOutputStream(ze.getName( ));
        for (int c = zin.read(); c != -1; c = zin.read( )) {
          fout.write(c);
        }
        zin.closeEntry( );
        fout.close( );
      }
      zin.close( );
    }
  }
}

ZipEntry僅僅用於控制讀取長度。


19 校驗和

在文本文件中1位的修改通常只會影響一個字符,但是在壓縮文件中,1位的錯誤將會導致整個文件無法讀取。

所以在壓縮文件末尾加上校驗和已確保文件是完好無損的。

當使用1位校驗和時,往往不能滿足要求,

如果使用8位,然後對65536求餘,這樣幾乎結果會隨機出現在任意位置,從而有更好的效果

當然,精心設計的校驗和會有更好的效果。


接口:

java.util.zip.Checksum
需要實現的方法:

public abstract void update(int b)
public abstract void update(byte[] data, int offset, int length)
public abstract long getValue( )
public abstract void reset( )

雖然,你可以實現自己的,但是建議使用已有的。

例如:

CRC32 and Adler32,他們都在java.util.zip包中。


除了直接使用這兩個類的update方式來進行更新以外,考慮流:

public CheckedInputStream(InputStream in, Checksum cksum)
public CheckedOutputStream(OutputStream out, Checksum cksum)

例子如下:

FileInputStream fin = new FileInputStream("/etc/passwd");
Checksum cksum = new CRC32( );
CheckedInputStream cin = new CheckedInputStream(fin, cksum);



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