Java压缩技术(二) ZIP压缩——Java原生实现

原文:http://snowolf.iteye.com/blog/642298

去年整理了一篇ZLib算法Java实现(Java压缩技术(一) ZLib),一直惦记却没时间补充。今天得空,整理一下ZIP的java原生实现。
看了几篇zip压缩算法的帖子,讲的算是比较细致了,但就是没有对应的解压缩实现,太惜败了! 我就喜欢没事做总结,稍作整理,将其收纳!

相关链接:
Java压缩技术(一) ZLib
Java压缩技术(二) ZIP压缩——Java原生实现
Java压缩技术(三) ZIP解压缩——Java原生实现
Java压缩技术(四) GZIP——Java原生实现
Java压缩技术(五) GZIP相关——浏览器解析
Java压缩技术(六) BZIP2——Commons实现
Java压缩技术(七) TAR——Commons实现

查过相关资料后才知道,ZIP应该算作归档类的压缩算法,每一门学科都可深可浅!

闲言少叙,先说ZIP压缩。
zip压缩需要通过ZipOutputStream 执行write方法将压缩数据写到指定输出流中。
注意,这里应先使用CheckedOutputStream 指定文件校验算法。(通常使用CRC32算法)。代码如下所示:

Java代码  收藏代码
  1. CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(destPath), new CRC32());  
  2. ZipOutputStream zos = new ZipOutputStream(cos);  


接下来,需要将待压缩文件以ZipEntry的方式追加到压缩文件中,如下所示:

Java代码  收藏代码
  1.  /** 
  2.  * 压缩包内文件名定义 
  3.  *  
  4.  * <pre> 
  5.  * 如果有多级目录,那么这里就需要给出包含目录的文件名 
  6.  * 如果用WinRAR打开压缩包,中文名将显示为乱码 
  7.  * </pre> 
  8.  */  
  9. ZipEntry entry = new ZipEntry(dir + file.getName());  
  10.   
  11. zos.putNextEntry(entry);  


ZipEntry就是压缩包中的每一个实体!
完成上述准备后,就可以执行压缩操作了。实际上,就是执行ZipOutputStream类的write方法,如下所示:

Java代码  收藏代码
  1. BufferedInputStream bis = new BufferedInputStream(new FileInputStream(  
  2.         file));  
  3.   
  4. int count;  
  5. byte data[] = new byte[BUFFER];  
  6. while ((count = bis.read(data, 0, BUFFER)) != -1) {  
  7.     zos.write(data, 0, count);  
  8. }  
  9. bis.close();  


当然,如果待添加的压缩项是一个目录。那么,需要通过递归的方式指定最终的压缩项。
如果要添加一个空目录,注意使用符号"/"(String PATH="/";)作为添加项名字结尾符!

递归构建目录压缩,代码如下:

Java代码  收藏代码
  1. /** 
  2.  * 压缩 
  3.  *  
  4.  * @param srcFile 
  5.  *            源路径 
  6.  * @param zos 
  7.  *            ZipOutputStream 
  8.  * @param basePath 
  9.  *            压缩包内相对路径 
  10.  * @throws Exception 
  11.  */  
  12. private static void compress(File srcFile, ZipOutputStream zos,  
  13.         String basePath) throws Exception {  
  14.     if (srcFile.isDirectory()) {  
  15.         compressDir(srcFile, zos, basePath);  
  16.     } else {  
  17.         compressFile(srcFile, zos, basePath);  
  18.     }  
  19. }  
  20.   
  21. /** 
  22.  * 压缩目录 
  23.  *  
  24.  * @param dir 
  25.  * @param zos 
  26.  * @param basePath 
  27.  * @throws Exception 
  28.  */  
  29. private static void compressDir(File dir, ZipOutputStream zos,  
  30.         String basePath) throws Exception {  
  31.   
  32.     File[] files = dir.listFiles();  
  33.   
  34.     // 构建空目录  
  35.     if (files.length < 1) {  
  36.         ZipEntry entry = new ZipEntry(basePath + dir.getName() + PATH);  
  37.   
  38.         zos.putNextEntry(entry);  
  39.         zos.closeEntry();  
  40.     }  
  41.   
  42.     for (File file : files) {  
  43.         // 递归压缩  
  44.         compress(file, zos, basePath + dir.getName() + PATH);  
  45.     }  
  46. }  


x是一个空目录,用WinRAR打开后,可以看到这个目录下还有一个空文件名文件!


来个完整的压缩实现,代码如下所示:

Java代码  收藏代码
  1. /** 
  2.  * 2010-4-12 
  3.  */  
  4. package org.zlex.commons.io;  
  5.   
  6. import java.io.BufferedInputStream;  
  7. import java.io.BufferedOutputStream;  
  8. import java.io.File;  
  9. import java.io.FileInputStream;  
  10. import java.io.FileOutputStream;  
  11. import java.util.zip.CRC32;  
  12. import java.util.zip.CheckedInputStream;  
  13. import java.util.zip.CheckedOutputStream;  
  14. import java.util.zip.ZipEntry;  
  15. import java.util.zip.ZipInputStream;  
  16. import java.util.zip.ZipOutputStream;  
  17.   
  18. /** 
  19.  * ZIP压缩工具 
  20.  *  
  21.  * @author  <a href="mailto:zlex.dongliang@gmail.com">梁栋</a>    
  22.  * @since 1.0 
  23.  */  
  24. public class ZipUtils {  
  25.   
  26.     public static final String EXT = ".zip";  
  27.     private static final String BASE_DIR = "";  
  28.   
  29.     // 符号"/"用来作为目录标识判断符  
  30.     private static final String PATH = "/";  
  31.     private static final int BUFFER = 1024;  
  32.   
  33.     /** 
  34.      * 压缩 
  35.      *  
  36.      * @param srcFile 
  37.      * @throws Exception 
  38.      */  
  39.     public static void compress(File srcFile) throws Exception {  
  40.         String name = srcFile.getName();  
  41.         String basePath = srcFile.getParent();  
  42.         String destPath = basePath + name + EXT;  
  43.         compress(srcFile, destPath);  
  44.     }  
  45.   
  46.     /** 
  47.      * 压缩 
  48.      *  
  49.      * @param srcFile 
  50.      *            源路径 
  51.      * @param destPath 
  52.      *            目标路径 
  53.      * @throws Exception 
  54.      */  
  55.     public static void compress(File srcFile, File destFile) throws Exception {  
  56.   
  57.         // 对输出文件做CRC32校验  
  58.         CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(  
  59.                 destFile), new CRC32());  
  60.   
  61.         ZipOutputStream zos = new ZipOutputStream(cos);  
  62.   
  63.         compress(srcFile, zos, BASE_DIR);  
  64.   
  65.         zos.flush();  
  66.         zos.close();  
  67.     }  
  68.   
  69.     /** 
  70.      * 压缩文件 
  71.      *  
  72.      * @param srcFile 
  73.      * @param destPath 
  74.      * @throws Exception 
  75.      */  
  76.     public static void compress(File srcFile, String destPath) throws Exception {  
  77.         compress(srcFile, new File(destPath));  
  78.     }  
  79.   
  80.     /** 
  81.      * 压缩 
  82.      *  
  83.      * @param srcFile 
  84.      *            源路径 
  85.      * @param zos 
  86.      *            ZipOutputStream 
  87.      * @param basePath 
  88.      *            压缩包内相对路径 
  89.      * @throws Exception 
  90.      */  
  91.     private static void compress(File srcFile, ZipOutputStream zos,  
  92.             String basePath) throws Exception {  
  93.         if (srcFile.isDirectory()) {  
  94.             compressDir(srcFile, zos, basePath);  
  95.         } else {  
  96.             compressFile(srcFile, zos, basePath);  
  97.         }  
  98.     }  
  99.   
  100.     /** 
  101.      * 压缩 
  102.      *  
  103.      * @param srcPath 
  104.      * @throws Exception 
  105.      */  
  106.     public static void compress(String srcPath) throws Exception {  
  107.         File srcFile = new File(srcPath);  
  108.   
  109.         compress(srcFile);  
  110.     }  
  111.   
  112.     /** 
  113.      * 文件压缩 
  114.      *  
  115.      * @param srcPath 
  116.      *            源文件路径 
  117.      * @param destPath 
  118.      *            目标文件路径 
  119.      *  
  120.      */  
  121.     public static void compress(String srcPath, String destPath)  
  122.             throws Exception {  
  123.         File srcFile = new File(srcPath);  
  124.   
  125.         compress(srcFile, destPath);  
  126.     }  
  127.   
  128.     /** 
  129.      * 压缩目录 
  130.      *  
  131.      * @param dir 
  132.      * @param zos 
  133.      * @param basePath 
  134.      * @throws Exception 
  135.      */  
  136.     private static void compressDir(File dir, ZipOutputStream zos,  
  137.             String basePath) throws Exception {  
  138.   
  139.         File[] files = dir.listFiles();  
  140.   
  141.         // 构建空目录  
  142.         if (files.length < 1) {  
  143.             ZipEntry entry = new ZipEntry(basePath + dir.getName() + PATH);  
  144.   
  145.             zos.putNextEntry(entry);  
  146.             zos.closeEntry();  
  147.         }  
  148.   
  149.         for (File file : files) {  
  150.   
  151.             // 递归压缩  
  152.             compress(file, zos, basePath + dir.getName() + PATH);  
  153.   
  154.         }  
  155.     }  
  156.   
  157.     /** 
  158.      * 文件压缩 
  159.      *  
  160.      * @param file 
  161.      *            待压缩文件 
  162.      * @param zos 
  163.      *            ZipOutputStream 
  164.      * @param dir 
  165.      *            压缩文件中的当前路径 
  166.      * @throws Exception 
  167.      */  
  168.     private static void compressFile(File file, ZipOutputStream zos, String dir)  
  169.             throws Exception {  
  170.   
  171.         /** 
  172.          * 压缩包内文件名定义 
  173.          *  
  174.          * <pre> 
  175.          * 如果有多级目录,那么这里就需要给出包含目录的文件名 
  176.          * 如果用WinRAR打开压缩包,中文名将显示为乱码 
  177.          * </pre> 
  178.          */  
  179.         ZipEntry entry = new ZipEntry(dir + file.getName());  
  180.   
  181.         zos.putNextEntry(entry);  
  182.   
  183.         BufferedInputStream bis = new BufferedInputStream(new FileInputStream(  
  184.                 file));  
  185.   
  186.         int count;  
  187.         byte data[] = new byte[BUFFER];  
  188.         while ((count = bis.read(data, 0, BUFFER)) != -1) {  
  189.             zos.write(data, 0, count);  
  190.         }  
  191.         bis.close();  
  192.   
  193.         zos.closeEntry();  
  194.     }  
  195.   
  196. }  


来做个简单的测试:

Java代码  收藏代码
  1. import static org.junit.Assert.*;  
  2.   
  3. import org.junit.Test;  
  4.   
  5. /** 
  6.  *  
  7.  * @author 梁栋 
  8.  * @version 1.0 
  9.  * @since 1.0 
  10.  */  
  11. public class ZipUtilsTest {  
  12.   
  13.     /** 
  14.      *   
  15.      */  
  16.     @Test  
  17.     public void test() throws Exception {  
  18.         // 压缩文件  
  19.         ZipUtils.compress("d:\\f.txt");  
  20.         // 压缩目录  
  21.         ZipUtils.compress("d:\\fd");  
  22.     }  
  23. }  



现在用WinRAR打开看看,是不是效果几乎一致?

当然,上述代码有所不足之处主要是中文名称乱码问题。用java原生ZIP实现压缩后得到的压缩包,与系统的字符集不同,文件/目录名将出现乱码。这是所有归档压缩都会遇到的问题。对于这种问题,Commons Copress提供了解决方案!

对于解压缩,请关注后续内容!

posted @ 2016-06-11 11:56  Tony.J  阅读(777)  评论(0编辑  收藏  举报