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");

其他地方都不用改,便可以处理中文档名的压缩。

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