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

来源:互联网 发布:淘宝哪些鞋店 编辑:程序博客网 时间:2024/05/01 10:12

缘由:
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");是解决中文名乱码的关键。

[java] view plaincopyprint?
  1. <span style="font-size: 14px;">import java.io.*;  
  2. import org.apache.tools.zip.*;  
  3. import java.util.Enumeration;  
  4.   
  5. /** 
  6.  *<p> 
  7.  * <b>功能:zip压缩、解压(支持中文文件名)</b> 
  8.  *<p> 
  9.  * 说明:使用Apache Ant提供的zip工具org.apache.tools.zip实现zip压缩和解压功能. 
  10.  * 解决了由于java.util.zip包不支持汉字的问题。 
  11.  *  
  12.  * @author Winty 
  13.  * @modifier vernon.zheng 
  14.  */  
  15. public class AntZip {  
  16.     private ZipFile zipFile;  
  17.     private ZipOutputStream zipOut; // 压缩Zip  
  18.     private ZipEntry zipEntry;  
  19.     private static int bufSize; // size of bytes  
  20.     private byte[] buf;  
  21.     private int readedBytes;  
  22.     // 用于压缩中。要去除的绝对父路路径,目的是将绝对路径变成相对路径。  
  23.     private String deleteAbsoluteParent;  
  24.   
  25.     /** 
  26.      *构造方法。默认缓冲区大小为512字节。 
  27.      */  
  28.     public AntZip() {  
  29.         this(512);  
  30.     }  
  31.   
  32.     /** 
  33.      *构造方法。 
  34.      *  
  35.      * @param bufSize 
  36.      *            指定压缩或解压时的缓冲区大小 
  37.      */  
  38.     public AntZip(int bufSize) {  
  39.         this.bufSize = bufSize;  
  40.         this.buf = new byte[this.bufSize];  
  41.         deleteAbsoluteParent = null;  
  42.     }  
  43.   
  44.     /** 
  45.      *压缩文件夹内的所有文件和目录。 
  46.      *  
  47.      * @param zipDirectory 
  48.      *            需要压缩的文件夹名 
  49.      */  
  50.     public void doZip(String zipDirectory) {  
  51.         File zipDir = new File(zipDirectory);  
  52.         doZip(new File[] { zipDir }, zipDir.getName());  
  53.     }  
  54.   
  55.     /** 
  56.      *压缩多个文件或目录。可以指定多个单独的文件或目录。而 <code>doZip(String zipDirectory)</code> 
  57.      * 则直接压缩整个文件夹。 
  58.      *  
  59.      * @param files 
  60.      *            要压缩的文件或目录组成的<code>File</code>数组。 
  61.      *@param zipFileName 
  62.      *            压缩后的zip文件名,如果后缀不是".zip", 自动添加后缀".zip"。 
  63.      */  
  64.     public void doZip(File[] files, String zipFileName) {  
  65.         // 未指定压缩文件名,默认为"ZipFile"  
  66.         if (zipFileName == null || zipFileName.equals(""))  
  67.             zipFileName = "ZipFile";  
  68.   
  69.         // 添加".zip"后缀  
  70.         if (!zipFileName.endsWith(".zip"))  
  71.             zipFileName += ".zip";  
  72.   
  73.         try {  
  74.             this.zipOut = new ZipOutputStream(new BufferedOutputStream(  
  75.                     new FileOutputStream(zipFileName)));  
  76.             compressFiles(files, this.zipOut, true);  
  77.             this.zipOut.close();  
  78.         } catch (IOException ioe) {  
  79.             ioe.printStackTrace();  
  80.         }  
  81.     }  
  82.   
  83.     /** 
  84.      *压缩文件和目录。由doZip()调用 
  85.      *  
  86.      * @param files 
  87.      *            要压缩的文件 
  88.      *@param zipOut 
  89.      *            zip输出流 
  90.      *@param isAbsolute 
  91.      *            是否是要去除的绝对路径的根路径。因为compressFiles() 
  92.      *            会递归地被调用,所以只用deleteAbsoluteParent不行。必须用isAbsolute来指明 
  93.      *            compressFiles()是第一次调用,而不是后续的递归调用。即如果要压缩的路径是 
  94.      *            E:\temp,那么第一次调用时,isAbsolute=true,则deleteAbsoluteParent会记录 
  95.      *            要删除的路径就是E:\ ,当压缩子目录E:\temp\folder时,isAbsolute=false, 
  96.      *            再递归调用compressFiles()时,deleteAbsoluteParent仍然是E:\ 。从而保证了 
  97.      *            将E:\temp及其子目录均正确地转化为相对目录。这样压缩才不会出错。不然绝对 路径E:\也会被写入到压缩文件中去。 
  98.      */  
  99.     private void compressFiles(File[] files, ZipOutputStream zipOut,  
  100.             boolean isAbsolute) throws IOException {  
  101.   
  102.         for (File file : files) {  
  103.             if (file == null)  
  104.                 continue// 空的文件对象  
  105.   
  106.             // 删除绝对父路径  
  107.             if (file.isAbsolute()) {  
  108.                 if (isAbsolute) {  
  109.                     deleteAbsoluteParent = file.getParentFile()  
  110.                             .getAbsolutePath();  
  111.                     deleteAbsoluteParent = appendSeparator(deleteAbsoluteParent);  
  112.                 }  
  113.             } else  
  114.                 deleteAbsoluteParent = "";  
  115.   
  116.             if (file.isDirectory()) {// 是目录  
  117.                 compressFolder(file, zipOut);  
  118.             } else {// 是文件  
  119.                 compressFile(file, zipOut);  
  120.             }  
  121.         }  
  122.     }  
  123.   
  124.     /** 
  125.      *压缩文件或空目录。由compressFiles()调用。 
  126.      *  
  127.      * @param file 
  128.      *            需要压缩的文件 
  129.      *@param zipOut 
  130.      *            zip输出流 
  131.      */  
  132.     public void compressFile(File file, ZipOutputStream zipOut)  
  133.             throws IOException {  
  134.   
  135.         String fileName = file.toString();  
  136.   
  137.         /* 去除绝对父路径。 */  
  138.         if (file.isAbsolute())  
  139.             fileName = fileName.substring(deleteAbsoluteParent.length());  
  140.         if (fileName == null || fileName == "")  
  141.             return;  
  142.   
  143.         /* 
  144.          * 因为是空目录,所以要在结尾加一个"/"。 不然就会被当作是空文件。 ZipEntry的isDirectory()方法中,目录以"/"结尾. 
  145.          * org.apache.tools.zip.ZipEntry : public boolean isDirectory() { return 
  146.          * getName().endsWith("/"); } 
  147.          */  
  148.         if (file.isDirectory())  
  149.             fileName = fileName + "/";// 此处不能用"\\"  
  150.   
  151.         zipOut.putNextEntry(new ZipEntry(fileName));  
  152.   
  153.         // 如果是文件则需读;如果是空目录则无需读,直接转到zipOut.closeEntry()。  
  154.         if (file.isFile()) {  
  155.             FileInputStream fileIn = new FileInputStream(file);  
  156.             while ((this.readedBytes = fileIn.read(this.buf)) > 0) {  
  157.                 zipOut.write(this.buf, 0this.readedBytes);  
  158.             }  
  159.             fileIn.close();  
  160.         }  
  161.   
  162.         zipOut.closeEntry();  
  163.     }  
  164.   
  165.     /** 
  166.      *递归完成目录文件读取。由compressFiles()调用。 
  167.      *  
  168.      * @param dir 
  169.      *            需要处理的文件对象 
  170.      *@param zipOut 
  171.      *            zip输出流 
  172.      */  
  173.     private void compressFolder(File dir, ZipOutputStream zipOut)  
  174.             throws IOException {  
  175.   
  176.         File[] files = dir.listFiles();  
  177.   
  178.         if (files.length == 0)// 如果目录为空,则单独压缩空目录。  
  179.             compressFile(dir, zipOut);  
  180.         else  
  181.             // 如果目录不为空,则分别处理目录和文件.  
  182.             compressFiles(files, zipOut, false);  
  183.     }  
  184.   
  185.     /** 
  186.      *解压指定zip文件。 
  187.      *  
  188.      * @param unZipFileName 
  189.      *            需要解压的zip文件名 
  190.      */  
  191.     public void unZip(String unZipFileName) {  
  192.         FileOutputStream fileOut;  
  193.         File file;  
  194.         InputStream inputStream;  
  195.   
  196.         try {  
  197.             this.zipFile = new ZipFile(unZipFileName);  
  198.   
  199.             for (Enumeration entries = this.zipFile.getEntries(); entries  
  200.                     .hasMoreElements();) {  
  201.   
  202.                 ZipEntry entry = (ZipEntry) entries.nextElement();  
  203.                 file = new File(entry.getName());  
  204.   
  205.                 if (entry.isDirectory()) {// 是目录,则创建之  
  206.                     file.mkdirs();  
  207.                 } else {// 是文件  
  208.                     // 如果指定文件的父目录不存在,则创建之.  
  209.                     File parent = file.getParentFile();  
  210.                     if (parent != null && !parent.exists()) {  
  211.                         parent.mkdirs();  
  212.                     }  
  213.   
  214.                     inputStream = zipFile.getInputStream(entry);  
  215.   
  216.                     fileOut = new FileOutputStream(file);  
  217.                     while ((this.readedBytes = inputStream.read(this.buf)) > 0) {  
  218.                         fileOut.write(this.buf, 0this.readedBytes);  
  219.                     }  
  220.                     fileOut.close();  
  221.   
  222.                     inputStream.close();  
  223.                 }  
  224.             }  
  225.             this.zipFile.close();  
  226.         } catch (IOException ioe) {  
  227.             ioe.printStackTrace();  
  228.         }  
  229.     }  
  230.     /** 
  231.      *解压指定zip文件。其中"GB18030"解决中文乱码 
  232.      *  
  233.      * @param unZipFileName 
  234.      *            需要解压的zip文件名 
  235.      * @param outputPath 
  236.      *            输出路径 
  237.      */  
  238.     public void unZip(String unZipFileName,String outputPath) {  
  239.         FileOutputStream fileOut;  
  240.         File file;  
  241.         InputStream inputStream;  
  242.   
  243.         try {  
  244.             this.zipFile = new ZipFile(unZipFileName, "GB18030");  
  245.   
  246.             for (Enumeration entries = this.zipFile.getEntries(); entries  
  247.                     .hasMoreElements();) {  
  248.   
  249.                 ZipEntry entry = (ZipEntry) entries.nextElement();  
  250.                 file = new File(outputPath+entry.getName());  
  251.   
  252.                 if (entry.isDirectory()) {// 是目录,则创建之  
  253.                     file.mkdirs();  
  254.                 } else {// 是文件  
  255.                     // 如果指定文件的父目录不存在,则创建之.  
  256.                     File parent = file.getParentFile();  
  257.                     if (parent != null && !parent.exists()) {  
  258.                         parent.mkdirs();  
  259.                     }  
  260.   
  261.                     inputStream = zipFile.getInputStream(entry);  
  262.   
  263.                     fileOut = new FileOutputStream(file);  
  264.                     while ((this.readedBytes = inputStream.read(this.buf)) > 0) {  
  265.                         fileOut.write(this.buf, 0this.readedBytes);  
  266.                     }  
  267.                     fileOut.close();  
  268.   
  269.                     inputStream.close();  
  270.                 }  
  271.             }  
  272.             this.zipFile.close();  
  273.         } catch (IOException ioe) {  
  274.             ioe.printStackTrace();  
  275.         }  
  276.     }  
  277.   
  278.     /** 
  279.      *给文件路径或目录结尾添加File.separator 
  280.      *  
  281.      * @param fileName 
  282.      *            需要添加路径分割符的路径 
  283.      *@return 如果路径已经有分割符,则原样返回,否则添加分割符后返回。 
  284.      */  
  285.     private String appendSeparator(String path) {  
  286.         if (!path.endsWith(File.separator))  
  287.             path += File.separator;  
  288.         return path;  
  289.     }  
  290.   
  291.     /** 
  292.      *解压指定zip文件。 
  293.      *  
  294.      * @param unZipFile 
  295.      *            需要解压的zip文件对象 
  296.      */  
  297.     public void unZip(File unZipFile) {  
  298.         unZip(unZipFile.toString());  
  299.     }  
  300.   
  301.     /** 
  302.      *设置压缩或解压时缓冲区大小。 
  303.      *  
  304.      * @param bufSize 
  305.      *            缓冲区大小 
  306.      */  
  307.     public void setBufSize(int bufSize) {  
  308.         this.bufSize = bufSize;  
  309.     }  
  310.     // 主函数,用于测试AntZip类  
  311.     /* 
  312.      * public static void main(String[] args)throws Exception{ 
  313.      * if(args.length>=2){ AntZip zip = new AntZip(); 
  314.      *  
  315.      * if(args[0].equals("-zip")){ //将后续参数全部转化为File对象 File[] files = new File[ 
  316.      * args.length - 1]; for(int i = 0;i < args.length - 1; i++){ files = new 
  317.      * File(args[i + 1]); } 
  318.      *  
  319.      * //将第一个文件名作为zip文件名 zip.doZip(files , files[0].getName()); 
  320.      *  
  321.      * return ; } else if(args[0].equals("-unzip")){ zip.unZip(args[1]); return 
  322.      * ; } } 
  323.      *  
  324.      * System.out.println("Usage:"); 
  325.      * System.out.println("压缩:java AntZip -zip [directoryName | fileName]... "); 
  326.      * System.out.println("解压:java AntZip -unzip fileName.zip"); } 
  327.      */  
  328.   
  329. }</span>  

第二种 从修改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的时候,设定档名的编码) 
 
[java] view plaincopyprint?
  1. <span style="font-size: 18px;"> </span><span style="font-size: 14px;">public CZipOutputStream(OutputStream out,String encoding) {   
  2.      super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));   
  3.      usesDefaultDeflater = true;   
  4.      this.encoding=encoding;   
  5.   } </span>  

6.找到byte[] nameBytes = getUTF8Bytes(e.name);(有二个地方),将它修改如下: 
 
[java] view plaincopyprint?
  1. <span style="font-size: 14px;"byte[] nameBytes = null;   
  2.   try   
  3.   {   
  4.     if (this.encoding.toUpperCase().equals("UTF-8"))   
  5.        nameBytes =getUTF8Bytes(e.name);   
  6.     else   
  7.        nameBytes= e.name.getBytes(this.encoding);   
  8.   }   
  9.   catch(Exception byteE)   
  10.   {   
  11.     nameBytes=getUTF8Bytes(e.name);   
  12.   } </span>  

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的时候,设定档名的编码) 
[java] view plaincopyprint?
  1. <span style="font-size: 14px;">public CZipInputStream(InputStream in,String encoding) {   
  2.   super(new PushbackInputStream(in,512),new Inflater(true),512);   
  3.   usesDefaultInflater = true;   
  4.   if(in == null) {   
  5.        throw new NullPointerException("in is null");   
  6.   }   
  7.   this.encoding=encoding;   
  8. } </span>  


6.找到ZipEntry e = createZipEntry(getUTF8String(b, 0, len));这一行,将它改成如下: 
[java] view plaincopyprint?
  1. <span style="font-size: 14px;">ZipEntry e=null;   
  2. try   
  3. {   
  4.   if (this.encoding.toUpperCase().equals("UTF-8"))   
  5.      e=createZipEntry(getUTF8String(b, 0, len));   
  6.   else   
  7.      e=createZipEntry(new String(b,0,len,this.encoding));   
  8. }   
  9. catch(Exception byteE)   
  10. {   
  11.   e=createZipEntry(getUTF8String(b, 0, len));   
  12. } </span>  


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」一定要打勾。 
以後当压缩及解压缩时有中文档名及路径的问题时,就可以指定编码方式来处理了。 
[java] view plaincopyprint?
  1. <span style="font-size: 14px;">CZipOutputStream zos=new CZipOutputStream(OutputStream os,String encoding);  
  2.   
  3. CZipInputStream zins=new CZipInputStream(InputStream ins,String encoding);</span>  
以「压缩与解压缩(1)」为例:
[java] view plaincopyprint?
  1. <span style="font-size: 14px;">FileOutputStream fos =new FileOutputStream(request.getRealPath("/")+"myzip.zip");  
  2.   
  3. CZipOutputStream zos=new CZipOutputStream(fos,"GBK");</span><span style="font-size: 14px;">  
  4. </span>  

其他地方都不用改,便可以处理中文档名的压缩。
0 0
原创粉丝点击