缘由:
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");
其他地方都不用改,便可以处理中文档名的压缩。