zip解压中文乱码解决与使用ant实现zip解压缩

本文介绍了解决Java中中文文件名压缩与解压缩的方法,包括使用Apache Ant进行高效压缩和通过修改JDK自带的Zip工具类源码来支持中文档名。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

缘由:
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");

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

package com.cliff.common; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import org.apache.tools.zip.ZipEntry; import org.apache.tools.zip.ZipFile; import org.apache.tools.zip.ZipOutputStream; /** * * 类名: ZipUtil.java * 描述:压缩/解压缩zip包处理类 * 创建者:XXX * 创建日期:2015年5月7日 - 下午1:35:02 * 版本: V0.1 * 修改者: * 修改日期: */ public class ZipUtil { /** * * 功能描述:压缩文件 * 创建者:XXX * 创建日期: 2015年5月7日 - 下午1:35:18 * 版本: V0.1 * 修改者: * 修改日期: * @param directory 指定压缩文件路径 压缩到同目录 * @throws IOException * void */ public static void zip(String directory) throws FileNotFoundException, IOException { zip("", null, directory); } /** * * 功能描述:压缩文件 * 创建者:XXX * 创建日期: 2015年5月7日 - 下午1:36:03 * 版本: V0.1 * 修改者: * 修改日期: * @param zipFileName 压缩产生的zip包文件名--带路径,如果为null或空则默认按文件名生产压缩文件名 * @param relativePath 相对路径,默认为空 * @param directory 文件或目录的绝对路径 * void */ public static void zip(String zipFileName, String relativePath, String directory) throws FileNotFoundException, IOException { String fileName = zipFileName; if (fileName == null || fileName.trim().equals("")) { File temp = new File(directory); if (temp.isDirectory()) { fileName = directory + ".zip"; } else { if (directory.indexOf(".") > 0) { fileName = directory.substring(0, directory.lastIndexOf("."))+ "zip"; } else { fileName = directory + ".zip"; } } } ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(fileName)); try { zip(zos, relativePath, directory); } catch (IOException ex) { throw ex; } finally { if (null != zos) { zos.close(); } } } /** * * 功能描述:压缩文件 * 创建者:XXX * 创建日期: 2015年5月7日 - 下午1:37:55 * 版本: V0.1 * 修改者: * 修改日期: * @param zos 压缩输出流 * @param relativePath 相对路径 * @param absolutPath 文件或文件夹绝对路径 * @throws IOException * void */ private static void zip(ZipOutputStream zos, String relativePath, String absolutPath) throws IOException { File file = new File(absolutPath); if (file.isDirectory()) { File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) { File tempFile = files[i]; if (tempFile.isDirectory()) { String newRelativePath = relativePath + tempFile.getName() + File.separator; createZipNode(zos, newRelativePath); zip(zos, newRelativePath, tempFile.getPath()); } else { zipFile(zos, tempFile, relativePath); } } } else { zipFile(zos, file, relativePath); } } /** * * 功能描述:压缩文件 * 创建者:XXX * 创建日期: 2015年5月7日 - 下午1:38:46 * 版本: V0.1 * 修改者: * 修改日期: * @param zos 压缩输出流 * @param file 文件对象 * @param relativePath 相对路径 * @throws IOException * void */ private static void zipFile(ZipOutputStream zos, File file, String relativePath) throws IOException { ZipEntry entry = new ZipEntry(relativePath + file.getName()); zos.putNextEntry(entry); InputStream is = null; try { is = new FileInputStream(file); int BUFFERSIZE = 2 <= 0) { zos.write(buffer, 0, length); } zos.flush(); zos.closeEntry(); } catch (IOException ex) { throw ex; } finally { if (null != is) { is.close(); } } } /** * * 功能描述:创建目录 * 创建者:XXX * 创建日期: 2015年5月7日 - 下午1:39:12 * 版本: V0.1 * 修改者: * 修改日期: * @param zos zip输出流 * @param relativePath 相对路径 * @throws IOException * void */ private static void createZipNode(ZipOutputStream zos, String relativePath) throws IOException { ZipEntry zipEntry = new ZipEntry(relativePath); zos.putNextEntry(zipEntry); zos.closeEntry(); } /** * * 功能描述:解压缩文件 * 创建者:XXX * 创建日期: 2015年5月7日 - 下午1:39:32 * 版本: V0.1 * 修改者: * 修改日期: * @param zipFilePath zip文件路径 * @param targetPath 解压缩到的位置,如果为null或空字符串则默认解压缩到跟zip包同目录跟zip包同名的文件夹下 * void */ public static void unzip(String zipFilePath, String targetPath) throws IOException { InputStream is = null; FileOutputStream fileOut = null; File file = null; ZipFile zipFile = null; try { zipFile = new ZipFile(zipFilePath,"GBK"); String directoryPath = ""; if (null == targetPath || "".equals(targetPath)) { directoryPath = zipFilePath.substring(0, zipFilePath.lastIndexOf(".")); } else { directoryPath = targetPath; } for(Enumeration entries = zipFile.getEntries(); entries.hasMoreElements();){ ZipEntry entry = (ZipEntry)entries.nextElement(); file = new File(directoryPath+"/"+entry.getName()); if(entry.isDirectory()){ file.mkdirs(); }else{ //如果指定文件的目录不存在,则创建之. File parent = file.getParentFile(); if(!parent.exists()){ parent.mkdirs(); } is = zipFile.getInputStream(entry); fileOut = new FileOutputStream(file); int readLen = 0; byte[] buffer = new byte[4096]; while ((readLen = is.read(buffer, 0, 4096)) >= 0) { fileOut.write(buffer, 0, readLen); } fileOut.close(); is.close(); } } zipFile.close(); } catch (IOException ex) { throw ex; } finally { if(null != zipFile){ zipFile = null; } if (null != is) { is.close(); } if (null != fileOut) { fileOut.close(); } } } /** * * 功能描述:生产文件 如果文件所在路径不存在则生成路径 * 创建者:XXX * 创建日期: 2015年5月7日 - 下午1:41:04 * 版本: V0.1 * 修改者: * 修改日期: * @param fileName 文件名 带路径 * @param isDirectory 是否为路径 * @return * File */ public static File buildFile(String fileName, boolean isDirectory) { File target = new File(fileName); if (isDirectory){ target.mkdirs(); } else { if (!target.getParentFile().exists()) { target.getParentFile().mkdirs(); target = new File(target.getAbsolutePath()); } } return target; } }
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值