zip解壓中文亂碼解決與使用ant實現zip解壓縮

緣由:
java對於文字的編碼是以unicode爲基礎,因此,若是以ZipInputStream及ZipOutputStream來處理壓縮及解壓縮的工作,碰到中文檔名或路徑,那當然是以unicode來處理羅! 
但是,現在市面上的壓縮及解壓縮軟體,例如winzip,卻是不支援unicode的,一碰到檔名以unicode編碼的檔案,它就不處理。 
那要如何才能做出讓winzip能夠處理的壓縮檔呢?
有兩種方式:
一種是使用apache的ant實現zip解壓縮,另一種是修改jdk自帶zip工具類的源碼
因爲ant內部是多線程讀取文件,解壓的文件雖然是亂序的,但是效率明顯比jdk的zip方式高很多。推薦使用ant的zip實現。

第一種使用ant實現的zip解壓縮,其中解壓的亂碼注意使用
public void unZip(String unZipFileName,String outputPath) 其中
this.zipFile = new ZipFile(unZipFileName, "GB18030");是解決中文名亂碼的關鍵。

import java.io.*;
import org.apache.tools.zip.*;
import java.util.Enumeration;

/**
 *<p>
 * <b>功能:zip壓縮、解壓(支持中文文件名)</b>
 *<p>
 * 說明:使用Apache Ant提供的zip工具org.apache.tools.zip實現zip壓縮和解壓功能.
 * 解決了由於java.util.zip包不支持漢字的問題。
 * 
 * @author Winty
 * @modifier vernon.zheng
 */
public class AntZip {
	private ZipFile zipFile;
	private ZipOutputStream zipOut; // 壓縮Zip
	private ZipEntry zipEntry;
	private static int bufSize; // size of bytes
	private byte[] buf;
	private int readedBytes;
	// 用於壓縮中。要去除的絕對父路路徑,目的是將絕對路徑變成相對路徑。
	private String deleteAbsoluteParent;

	/**
	 *構造方法。默認緩衝區大小爲512字節。
	 */
	public AntZip() {
		this(512);
	}

	/**
	 *構造方法。
	 * 
	 * @param bufSize
	 *            指定壓縮或解壓時的緩衝區大小
	 */
	public AntZip(int bufSize) {
		this.bufSize = bufSize;
		this.buf = new byte[this.bufSize];
		deleteAbsoluteParent = null;
	}

	/**
	 *壓縮文件夾內的所有文件和目錄。
	 * 
	 * @param zipDirectory
	 *            需要壓縮的文件夾名
	 */
	public void doZip(String zipDirectory) {
		File zipDir = new File(zipDirectory);
		doZip(new File[] { zipDir }, zipDir.getName());
	}

	/**
	 *壓縮多個文件或目錄。可以指定多個單獨的文件或目錄。而 <code>doZip(String zipDirectory)</code>
	 * 則直接壓縮整個文件夾。
	 * 
	 * @param files
	 *            要壓縮的文件或目錄組成的<code>File</code>數組。
	 *@param zipFileName
	 *            壓縮後的zip文件名,如果後綴不是".zip", 自動添加後綴".zip"。
	 */
	public void doZip(File[] files, String zipFileName) {
		// 未指定壓縮文件名,默認爲"ZipFile"
		if (zipFileName == null || zipFileName.equals(""))
			zipFileName = "ZipFile";

		// 添加".zip"後綴
		if (!zipFileName.endsWith(".zip"))
			zipFileName += ".zip";

		try {
			this.zipOut = new ZipOutputStream(new BufferedOutputStream(
					new FileOutputStream(zipFileName)));
			compressFiles(files, this.zipOut, true);
			this.zipOut.close();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}

	/**
	 *壓縮文件和目錄。由doZip()調用
	 * 
	 * @param files
	 *            要壓縮的文件
	 *@param zipOut
	 *            zip輸出流
	 *@param isAbsolute
	 *            是否是要去除的絕對路徑的根路徑。因爲compressFiles()
	 *            會遞歸地被調用,所以只用deleteAbsoluteParent不行。必須用isAbsolute來指明
	 *            compressFiles()是第一次調用,而不是後續的遞歸調用。即如果要壓縮的路徑是
	 *            E:\temp,那麼第一次調用時,isAbsolute=true,則deleteAbsoluteParent會記錄
	 *            要刪除的路徑就是E:\ ,當壓縮子目錄E:\temp\folder時,isAbsolute=false,
	 *            再遞歸調用compressFiles()時,deleteAbsoluteParent仍然是E:\ 。從而保證了
	 *            將E:\temp及其子目錄均正確地轉化爲相對目錄。這樣壓縮纔不會出錯。不然絕對 路徑E:\也會被寫入到壓縮文件中去。
	 */
	private void compressFiles(File[] files, ZipOutputStream zipOut,
			boolean isAbsolute) throws IOException {

		for (File file : files) {
			if (file == null)
				continue; // 空的文件對象

			// 刪除絕對父路徑
			if (file.isAbsolute()) {
				if (isAbsolute) {
					deleteAbsoluteParent = file.getParentFile()
							.getAbsolutePath();
					deleteAbsoluteParent = appendSeparator(deleteAbsoluteParent);
				}
			} else
				deleteAbsoluteParent = "";

			if (file.isDirectory()) {// 是目錄
				compressFolder(file, zipOut);
			} else {// 是文件
				compressFile(file, zipOut);
			}
		}
	}

	/**
	 *壓縮文件或空目錄。由compressFiles()調用。
	 * 
	 * @param file
	 *            需要壓縮的文件
	 *@param zipOut
	 *            zip輸出流
	 */
	public void compressFile(File file, ZipOutputStream zipOut)
			throws IOException {

		String fileName = file.toString();

		/* 去除絕對父路徑。 */
		if (file.isAbsolute())
			fileName = fileName.substring(deleteAbsoluteParent.length());
		if (fileName == null || fileName == "")
			return;

		/*
		 * 因爲是空目錄,所以要在結尾加一個"/"。 不然就會被當作是空文件。 ZipEntry的isDirectory()方法中,目錄以"/"結尾.
		 * org.apache.tools.zip.ZipEntry : public boolean isDirectory() { return
		 * getName().endsWith("/"); }
		 */
		if (file.isDirectory())
			fileName = fileName + "/";// 此處不能用"\\"

		zipOut.putNextEntry(new ZipEntry(fileName));

		// 如果是文件則需讀;如果是空目錄則無需讀,直接轉到zipOut.closeEntry()。
		if (file.isFile()) {
			FileInputStream fileIn = new FileInputStream(file);
			while ((this.readedBytes = fileIn.read(this.buf)) > 0) {
				zipOut.write(this.buf, 0, this.readedBytes);
			}
			fileIn.close();
		}

		zipOut.closeEntry();
	}

	/**
	 *遞歸完成目錄文件讀取。由compressFiles()調用。
	 * 
	 * @param dir
	 *            需要處理的文件對象
	 *@param zipOut
	 *            zip輸出流
	 */
	private void compressFolder(File dir, ZipOutputStream zipOut)
			throws IOException {

		File[] files = dir.listFiles();

		if (files.length == 0)// 如果目錄爲空,則單獨壓縮空目錄。
			compressFile(dir, zipOut);
		else
			// 如果目錄不爲空,則分別處理目錄和文件.
			compressFiles(files, zipOut, false);
	}

	/**
	 *解壓指定zip文件。
	 * 
	 * @param unZipFileName
	 *            需要解壓的zip文件名
	 */
	public void unZip(String unZipFileName) {
		FileOutputStream fileOut;
		File file;
		InputStream inputStream;

		try {
			this.zipFile = new ZipFile(unZipFileName);

			for (Enumeration entries = this.zipFile.getEntries(); entries
					.hasMoreElements();) {

				ZipEntry entry = (ZipEntry) entries.nextElement();
				file = new File(entry.getName());

				if (entry.isDirectory()) {// 是目錄,則創建之
					file.mkdirs();
				} else {// 是文件
					// 如果指定文件的父目錄不存在,則創建之.
					File parent = file.getParentFile();
					if (parent != null && !parent.exists()) {
						parent.mkdirs();
					}

					inputStream = zipFile.getInputStream(entry);

					fileOut = new FileOutputStream(file);
					while ((this.readedBytes = inputStream.read(this.buf)) > 0) {
						fileOut.write(this.buf, 0, this.readedBytes);
					}
					fileOut.close();

					inputStream.close();
				}
			}
			this.zipFile.close();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
	/**
	 *解壓指定zip文件。其中"GB18030"解決中文亂碼
	 * 
	 * @param unZipFileName
	 *            需要解壓的zip文件名
	 * @param outputPath
	 *            輸出路徑
	 */
	public void unZip(String unZipFileName,String outputPath) {
		FileOutputStream fileOut;
		File file;
		InputStream inputStream;

		try {
			this.zipFile = new ZipFile(unZipFileName, "GB18030");

			for (Enumeration entries = this.zipFile.getEntries(); entries
					.hasMoreElements();) {

				ZipEntry entry = (ZipEntry) entries.nextElement();
				file = new File(outputPath+entry.getName());

				if (entry.isDirectory()) {// 是目錄,則創建之
					file.mkdirs();
				} else {// 是文件
					// 如果指定文件的父目錄不存在,則創建之.
					File parent = file.getParentFile();
					if (parent != null && !parent.exists()) {
						parent.mkdirs();
					}

					inputStream = zipFile.getInputStream(entry);

					fileOut = new FileOutputStream(file);
					while ((this.readedBytes = inputStream.read(this.buf)) > 0) {
						fileOut.write(this.buf, 0, this.readedBytes);
					}
					fileOut.close();

					inputStream.close();
				}
			}
			this.zipFile.close();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}

	/**
	 *給文件路徑或目錄結尾添加File.separator
	 * 
	 * @param fileName
	 *            需要添加路徑分割符的路徑
	 *@return 如果路徑已經有分割符,則原樣返回,否則添加分割符後返回。
	 */
	private String appendSeparator(String path) {
		if (!path.endsWith(File.separator))
			path += File.separator;
		return path;
	}

	/**
	 *解壓指定zip文件。
	 * 
	 * @param unZipFile
	 *            需要解壓的zip文件對象
	 */
	public void unZip(File unZipFile) {
		unZip(unZipFile.toString());
	}

	/**
	 *設置壓縮或解壓時緩衝區大小。
	 * 
	 * @param bufSize
	 *            緩衝區大小
	 */
	public void setBufSize(int bufSize) {
		this.bufSize = bufSize;
	}
	// 主函數,用於測試AntZip類
	/*
	 * public static void main(String[] args)throws Exception{
	 * if(args.length>=2){ AntZip zip = new AntZip();
	 * 
	 * if(args[0].equals("-zip")){ //將後續參數全部轉化爲File對象 File[] files = new File[
	 * args.length - 1]; for(int i = 0;i < args.length - 1; i++){ files = new
	 * File(args[i + 1]); }
	 * 
	 * //將第一個文件名作爲zip文件名 zip.doZip(files , files[0].getName());
	 * 
	 * return ; } else if(args[0].equals("-unzip")){ zip.unZip(args[1]); return
	 * ; } }
	 * 
	 * System.out.println("Usage:");
	 * System.out.println("壓縮:java AntZip -zip [directoryName | fileName]... ");
	 * System.out.println("解壓:java AntZip -unzip fileName.zip"); }
	 */

}

第二種 從修改ZipInputStream及ZipOutputStream對於檔名的編碼方式來着手了。
我們可以從jdk的src.zip取得ZipInputStream及ZipOutputStream的原始碼來加以修改: 

一、ZipOutputStream.java 
1.從jdk的src.zip取得ZipOutputStream.java原始碼,另存新檔存到c:/java/util/zip這個資料夾裏,檔名改爲CZipOutputStream.java。 
2.開始修改原始碼,將class名稱改爲CZipOutputStream 
3.建構式也必須更改爲CZipOutputStream 
4.新增member,這個member記錄編碼方式 
  private String encoding="UTF-8"; 
5.再新增一個建構式(這個建構式可以讓這個class在new的時候,設定檔名的編碼) 
 
 public CZipOutputStream(OutputStream out,String encoding) { 
     super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); 
     usesDefaultDeflater = true; 
     this.encoding=encoding; 
  } 

6.找到byte[] nameBytes = getUTF8Bytes(e.name);(有二個地方),將它修改如下: 
 
 byte[] nameBytes = null; 
  try 
  { 
    if (this.encoding.toUpperCase().equals("UTF-8")) 
       nameBytes =getUTF8Bytes(e.name); 
    else 
       nameBytes= e.name.getBytes(this.encoding); 
  } 
  catch(Exception byteE) 
  { 
    nameBytes=getUTF8Bytes(e.name); 
  } 

7.將檔案儲存在c:/java/util/zip這個資料夾內,請記得一定要有這個路徑結構, 
才能把CZipOutputStream.class放在正確的package結構裏 


二、ZipInputStream.java 
1.從jdk的src.zip取得ZipInputStream.java原始碼,另存新檔存到c:/java/util/zip這個資料夾裏,檔名改爲CZipInputStream.java。 
2.開始修改原始碼,將class名稱改爲CZipInputStream 
3.建構式也必須更改爲CZipInputStream 
4.新增member,這個member記錄編碼方式 
  private String encoding="UTF-8"; 
5.再新增一個建構式如下(這個建構式可以讓這個class在new的時候,設定檔名的編碼) 
public CZipInputStream(InputStream in,String encoding) { 
  super(new PushbackInputStream(in,512),new Inflater(true),512); 
  usesDefaultInflater = true; 
  if(in == null) { 
       throw new NullPointerException("in is null"); 
  } 
  this.encoding=encoding; 
} 


6.找到ZipEntry e = createZipEntry(getUTF8String(b, 0, len));這一行,將它改成如下: 
ZipEntry e=null; 
try 
{ 
  if (this.encoding.toUpperCase().equals("UTF-8")) 
     e=createZipEntry(getUTF8String(b, 0, len)); 
  else 
     e=createZipEntry(new String(b,0,len,this.encoding)); 
} 
catch(Exception byteE) 
{ 
  e=createZipEntry(getUTF8String(b, 0, len)); 
} 


7.將檔案儲存在c:/java/util/zip這個資料夾內,請記得一定要有這個路徑結構,才能把CZipInputStream.class放在正確的package結構裏 


以上兩個檔案儲存後compile產生CZipOutputStream.class及CZipInputStream.class,使用winzip開啓[java_home]/jre/lib/rt.jar這個檔案,將CZipOutputStream.class及CZipInputStream.class加進去,記得「Save full path info」一定要打勾。 
以後當壓縮及解壓縮時有中文檔名及路徑的問題時,就可以指定編碼方式來處理了。 
CZipOutputStream zos=new CZipOutputStream(OutputStream os,String encoding);

CZipInputStream zins=new CZipInputStream(InputStream ins,String encoding);
以「壓縮與解壓縮(1)」爲例:
FileOutputStream fos =new FileOutputStream(request.getRealPath("/")+"myzip.zip");

CZipOutputStream zos=new CZipOutputStream(fos,"GBK");

其他地方都不用改,便可以處理中文檔名的壓縮。

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