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

去年整理了一篇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);  
CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(destPath), new CRC32());
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);  
		 /**
		 * 压缩包内文件名定义
		 * 
		 * <pre>
		 * 如果有多级目录,那么这里就需要给出包含目录的文件名
		 * 如果用WinRAR打开压缩包,中文名将显示为乱码
		 * </pre>
		 */
		ZipEntry entry = new ZipEntry(dir + file.getName());

		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();  
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
				file));

		int count;
		byte data[] = new byte[BUFFER];
		while ((count = bis.read(data, 0, BUFFER)) != -1) {
			zos.write(data, 0, count);
		}
		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. }  
	/**
	 * 压缩
	 * 
	 * @param srcFile
	 *            源路径
	 * @param zos
	 *            ZipOutputStream
	 * @param basePath
	 *            压缩包内相对路径
	 * @throws Exception
	 */
	private static void compress(File srcFile, ZipOutputStream zos,
			String basePath) throws Exception {
		if (srcFile.isDirectory()) {
			compressDir(srcFile, zos, basePath);
		} else {
			compressFile(srcFile, zos, basePath);
		}
	}

	/**
	 * 压缩目录
	 * 
	 * @param dir
	 * @param zos
	 * @param basePath
	 * @throws Exception
	 */
	private static void compressDir(File dir, ZipOutputStream zos,
			String basePath) throws Exception {

		File[] files = dir.listFiles();

		// 构建空目录
		if (files.length < 1) {
			ZipEntry entry = new ZipEntry(basePath + dir.getName() + PATH);

			zos.putNextEntry(entry);
			zos.closeEntry();
		}

		for (File file : files) {
			// 递归压缩
			compress(file, zos, basePath + dir.getName() + PATH);
		}
	}

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:[email protected]">梁栋</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. }  
/**
 * 2010-4-12
 */
package org.zlex.commons.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * ZIP压缩工具
 * 
 * @author  <a href="mailto:[email protected]">梁栋</a>   
 * @since 1.0
 */
public class ZipUtils {

	public static final String EXT = ".zip";
	private static final String BASE_DIR = "";

	// 符号"/"用来作为目录标识判断符
	private static final String PATH = "/";
	private static final int BUFFER = 1024;

	/**
	 * 压缩
	 * 
	 * @param srcFile
	 * @throws Exception
	 */
	public static void compress(File srcFile) throws Exception {
		String name = srcFile.getName();
		String basePath = srcFile.getParent();
		String destPath = basePath + name + EXT;
		compress(srcFile, destPath);
	}

	/**
	 * 压缩
	 * 
	 * @param srcFile
	 *            源路径
	 * @param destPath
	 *            目标路径
	 * @throws Exception
	 */
	public static void compress(File srcFile, File destFile) throws Exception {

		// 对输出文件做CRC32校验
		CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(
				destFile), new CRC32());

		ZipOutputStream zos = new ZipOutputStream(cos);

		compress(srcFile, zos, BASE_DIR);

		zos.flush();
		zos.close();
	}

	/**
	 * 压缩文件
	 * 
	 * @param srcFile
	 * @param destPath
	 * @throws Exception
	 */
	public static void compress(File srcFile, String destPath) throws Exception {
		compress(srcFile, new File(destPath));
	}

	/**
	 * 压缩
	 * 
	 * @param srcFile
	 *            源路径
	 * @param zos
	 *            ZipOutputStream
	 * @param basePath
	 *            压缩包内相对路径
	 * @throws Exception
	 */
	private static void compress(File srcFile, ZipOutputStream zos,
			String basePath) throws Exception {
		if (srcFile.isDirectory()) {
			compressDir(srcFile, zos, basePath);
		} else {
			compressFile(srcFile, zos, basePath);
		}
	}

	/**
	 * 压缩
	 * 
	 * @param srcPath
	 * @throws Exception
	 */
	public static void compress(String srcPath) throws Exception {
		File srcFile = new File(srcPath);

		compress(srcFile);
	}

	/**
	 * 文件压缩
	 * 
	 * @param srcPath
	 *            源文件路径
	 * @param destPath
	 *            目标文件路径
	 * 
	 */
	public static void compress(String srcPath, String destPath)
			throws Exception {
		File srcFile = new File(srcPath);

		compress(srcFile, destPath);
	}

	/**
	 * 压缩目录
	 * 
	 * @param dir
	 * @param zos
	 * @param basePath
	 * @throws Exception
	 */
	private static void compressDir(File dir, ZipOutputStream zos,
			String basePath) throws Exception {

		File[] files = dir.listFiles();

		// 构建空目录
		if (files.length < 1) {
			ZipEntry entry = new ZipEntry(basePath + dir.getName() + PATH);

			zos.putNextEntry(entry);
			zos.closeEntry();
		}

		for (File file : files) {

			// 递归压缩
			compress(file, zos, basePath + dir.getName() + PATH);

		}
	}

	/**
	 * 文件压缩
	 * 
	 * @param file
	 *            待压缩文件
	 * @param zos
	 *            ZipOutputStream
	 * @param dir
	 *            压缩文件中的当前路径
	 * @throws Exception
	 */
	private static void compressFile(File file, ZipOutputStream zos, String dir)
			throws Exception {

		/**
		 * 压缩包内文件名定义
		 * 
		 * <pre>
		 * 如果有多级目录,那么这里就需要给出包含目录的文件名
		 * 如果用WinRAR打开压缩包,中文名将显示为乱码
		 * </pre>
		 */
		ZipEntry entry = new ZipEntry(dir + file.getName());

		zos.putNextEntry(entry);

		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
				file));

		int count;
		byte data[] = new byte[BUFFER];
		while ((count = bis.read(data, 0, BUFFER)) != -1) {
			zos.write(data, 0, count);
		}
		bis.close();

		zos.closeEntry();
	}

}

来做个简单的测试:
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. }  
import static org.junit.Assert.*;

import org.junit.Test;

/**
 * 
 * @author 梁栋
 * @version 1.0
 * @since 1.0
 */
public class ZipUtilsTest {

	/**
	 *  
	 */
	@Test
	public void test() throws Exception {
		// 压缩文件
		ZipUtils.compress("d:\\f.txt");
		// 压缩目录
		ZipUtils.compress("d:\\fd");
	}
}


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

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

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

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

你可能感兴趣的:(java,算法)