Java中有與zip相關的api,位於java.util.zip.*包下。
在一些應用場景中,可能需要對程序生成的文件如導出比較大的excel文件,然後需要對其壓縮上傳。
在《Java 核心技術 卷二 高級特性》這本書中就有關於zip的介紹,下面將其摘錄如下:
1.讀取zip文件
zip文檔通常以壓縮格式存儲了一個或多個文件,每個zip文檔都有一個包含諸如文件名字和所使用的壓縮方法等信息的頭。在Java中,可以使用ZipInputStream來讀入zip文檔。
你可能需要瀏覽文檔中每個單獨的項,getNextEntry方法就可以返回一個描述這些項的ZipEntry類型的對象。ZipInputStream的read方法被修改爲在碰到當前項的結尾時返回-1(而不是碰到zip文件的末尾),然後你必須調用closeEntry來讀入下一項。下面是典型的通讀zip文件的僞代碼:
ZipInputStream zin = new ZipInputStream(new FileInputStream(zipname));
ZipEntry entry;
while((entry=zin.getNextEntry())!=null)
{
analyze entry;
read the contents of zin;
zin.closeEntry();
}
zin.close();
當希望讀入某個zip項的內容時,我們可能並不想使用原生的read方法,通常,我們將使用某個更能勝任的流過濾器的方法。例如,爲了讀入zip文件內部的一個文本文件,我們可以使用下面的循環:
Scanner in = new Scanner(zin);
while(in.hasNextLine())
do something with in.nextLine();
注意: zip輸入流在讀入zip文件發生錯誤時,會拋出ZipException。通常這種錯誤在zip文件被破壞時發生。
2.寫出到zip文件
要寫出到zip文件,可以使用ZipOutputStream,而對於你希望放入到zip文件中的每一項,都應該創建一個ZipEntry對象,並將文件名傳遞給ZipEntry的構造器,它將設置其他諸如文件日期和解壓縮方法等參數。如果需要,你可以覆蓋這些設置。然後,你需要調用ZipOutputStream的putNextEntry方法來開始寫出新文件,並將文件數據發送到zip流中。當完成時,需要調用closeEntry。然後,你需要對所有你希望存儲的文件都重複這個過程。下面是代碼框架:
FileOutputStream fout = new FileOutputStream("test.zip");
ZipOutputStream zout = new ZipOutputStream(fout);
for all files
{
ZipEntry ze = new ZipEntry(filename);// file.getName();
zout.putNextEntry(ze);
send data to zout;
zout.closeEntry();
}
zout.close();
注意: ZipEntry ze = new ZipEntry(filename);中的filename,這個路徑是相對路徑(相對於test.zip這個zip文件的路徑),所以如果是目錄層級的話,需要加上層級目錄。
上面這段僞代碼非常重要,可以幫助你很好的理解下面的遞歸結構程序。
package test;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipUtils {
public static final String EXT = ".zip";
// 符號"/"用來作爲目錄標識判斷符
private static final String PATH = "/";
private static final int BUFFER = 1024;
/**
* 壓縮文件或文件夾
* @param zipOutfile zip輸出文件
* @param file 待壓縮的文件或文件夾
* @throws IOException
*/
public static void compressFile(File zipOutfile,File file) throws IOException{
System.out.println("Begin Compress file ...");
String zipname = zipOutfile.getName();
File outfile = new File(zipOutfile.getParent(),zipname.substring(0, zipname.lastIndexOf("."))+EXT);
if(outfile.exists()) return;
ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(outfile));
putEntry(zout,file,PATH);
zout.close();
System.out.println("END");
}
/**
* 遞歸地將 文件或目錄 放置到輸出流中
* @param zout
* @param file
* @param curdir 當前目錄層級
* @throws IOException
*/
private static void putEntry(ZipOutputStream zout,File file,String curdir) throws IOException{
if(file.isFile()){
ZipEntry ze = new ZipEntry(curdir+file.getName());
zout.putNextEntry(ze);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
file));
byte[] b = new byte[BUFFER];
int count ;
while((count=bis.read(b, 0, BUFFER))!=-1)
zout.write(b, 0, count);
bis.close();
zout.closeEntry();
}else{
ZipEntry ze = new ZipEntry(curdir+file.getName()+PATH);//放置目錄到zout 帶 / 表示目錄
zout.putNextEntry(ze);
zout.closeEntry();
for(File f : file.listFiles())
putEntry(zout,f,curdir+file.getName()+PATH);
}
}
/**
* 壓縮文件或目錄 實際調用的是compressFile(ZipOutputStream,File)
* @param file 文件或目錄
* @throws IOException
*/
public static void compressFile(File file)throws IOException{
System.out.println("Begin Compress single file or dir...");
String zipname = file.getName();
System.out.println(file.getName()+","+file.getParent()+","+file.getPath());
if(file.isFile()){
File zipOutfile = new File(file.getParent(),zipname.substring(0, zipname.lastIndexOf("."))+EXT);
compressFile(zipOutfile,file);
}else{
File zipOutfile = new File(file.getParent(),zipname+EXT);
compressFile(zipOutfile,file);
}
}
public static void main(String[] args) throws Exception {
//File file = new File("E:\\CSV\\f.txt");
File file = new File("E:/CSV/fd");
compressFile(file);
}
}
在壓縮某個文件夾時,比較費解的是如何將該文件夾的文件都放置在一個zip中,並且文件的層級關係保持不變,所以上面遞歸的核心地方是當子文件是文件夾時,需要遞歸地將該子文件夾中的文件entry放置到該文件夾下,所以參數需要傳遞一個表示當前層級的curdir。