利用Java实现压缩与解压缩(zip、gzip)支持汉语言路径
利用Java实现压缩与解压缩(zip、gzip)支持中文路径zip扮演着归档和压缩两个角色;gzip并不将文件归档,仅只是
利用Java实现压缩与解压缩(zip、gzip)支持中文路径
zip扮演着归档和压缩两个角色;gzip并不将文件归档,仅只是对单个文件进行压缩,所以,在UNIX平台上,命令tar通常用来创建一个档案文件,然后命令gzip来将档案文件压缩。
Java I/O类库还收录了一些能读写压缩格式流的类。要想提供压缩功能,只要把它们包在已有的I/O类的外面就行了。这些类不是Reader和Writer,而是InputStream和OutStreamput的子类。这是因为压缩算法是针对byte而不是字符的。
相关类与接口:
Checksum接口:被类Adler32和CRC32实现的接口
Adler32:使用Alder32算法来计算Checksum数目
CRC32:使用CRC32算法来计算Checksum数目
CheckedInputStream:InputStream派生类,可得到输入流的校验和Checksum,用于校验数据的完整性
CheckedOutputStream:OutputStream派生类,可得到输出流的校验和Checksum,用于校验数据的完整性
DeflaterOutputStream:压缩类的基类。
ZipOutputStream:DeflaterOutputStream的一个子类,把数据压缩成Zip文件格式。
GZIPOutputStream:DeflaterOutputStream的一个子类,把数据压缩成GZip文件格式
InflaterInputStream:解压缩类的基类
ZipInputStream:InflaterInputStream的一个子类,能解压缩Zip格式的数据
GZIPInputStream:InflaterInputStream的一个子类,能解压缩Zip格式的数据
ZipEntry类:表示 ZIP 文件条目
ZipFile类:此类用于从 ZIP 文件读取条目
用GZIP进行对单个文件压缩
GZIP的接口比较简单,因此如果你只需对一个流进行压缩的话,可以使用它。当然它可以压缩字符流,与可以压缩字节流,下面是一个对GBK编码格式的文本文件进行压缩的。
压缩类的用法非常简单;只要用GZIPOutputStream 或ZipOutputStream把输出流包起来,再用GZIPInputStream 或ZipInputStream把输入流包起来就行了。剩下的都是些普通的I/O操作。
import?java.io.BufferedInputStream;??import?java.io.BufferedOutputStream;??import?java.io.File;??import?java.io.FileInputStream;??import?java.io.FileNotFoundException;??import?java.io.FileOutputStream;??import?java.io.IOException;??import?java.util.Enumeration;??import?java.util.zip.CRC32;??import?java.util.zip.CheckedInputStream;??import?java.util.zip.CheckedOutputStream;??import?java.util.zip.Deflater;??import?java.util.zip.ZipException;??import?java.util.zip.ZipInputStream;????import?org.apache.tools.zip.ZipEntry;??import?org.apache.tools.zip.ZipFile;??import?org.apache.tools.zip.ZipOutputStream;????/**??*???*?提供对单个文件与目录的压缩,并支持是否需要创建压缩源目录、中文路径??*???*?@author?jzj??*/??public?class?ZipCompress?{????????private?static?boolean?isCreateSrcDir?=?true;//是否创建源目录????????/**??????*?@param?args??????*?@throws?IOException??????*/??????public?static?void?main(String[]?args)?throws?IOException?{??????????String?src?=?"m:/新建文本文档.txt";//指定压缩源,可以是目录或文件??????????String?decompressDir?=?"e:/tmp/decompress";//解压路径??????????String?archive?=?"e:/tmp/test.zip";//压缩包路径??????????String?comment?=?"Java?Zip?测试.";//压缩包注释????????????//----压缩文件或目录??????????writeByApacheZipOutputStream(src,?archive,?comment);????????????/*??????????*?读压缩文件,注释掉,因为使用的是apache的压缩类,所以使用java类库中??????????*?解压类时出错,这里不能运行??????????*/??????????//readByZipInputStream();??????????//----使用apace?ZipFile读取压缩文件??????????readByApacheZipFile(archive,?decompressDir);??????}????????public?static?void?writeByApacheZipOutputStream(String?src,?String?archive,??????????????String?comment)?throws?FileNotFoundException,?IOException?{??????????//----压缩文件:??????????FileOutputStream?f?=?new?FileOutputStream(archive);??????????//使用指定校验和创建输出流??????????CheckedOutputStream?csum?=?new?CheckedOutputStream(f,?new?CRC32());????????????ZipOutputStream?zos?=?new?ZipOutputStream(csum);??????????//支持中文??????????zos.setEncoding("GBK");??????????BufferedOutputStream?out?=?new?BufferedOutputStream(zos);??????????//设置压缩包注释??????????zos.setComment(comment);??????????//启用压缩??????????zos.setMethod(ZipOutputStream.DEFLATED);??????????//压缩级别为最强压缩,但时间要花得多一点??????????zos.setLevel(Deflater.BEST_COMPRESSION);????????????File?srcFile?=?new?File(src);????????????if?(!srcFile.exists()?||?(srcFile.isDirectory()?&&?srcFile.list().length?==?0))?{??????????????throw?new?FileNotFoundException(??????????????????????"File?must?exist?and??ZIP?file?must?have?at?least?one?entry.");??????????}??????????//获取压缩源所在父目录??????????src?=?src.replaceAll("\\\",?"/");??????????String?prefixDir?=?null;??????????if?(srcFile.isFile())?{??????????????prefixDir?=?src.substring(0,?src.lastIndexOf("/")?+?1);??????????}?else?{??????????????prefixDir?=?(src.replaceAll("/$",?"")?+?"/");??????????}????????????//如果不是根目录??????????if?(prefixDir.indexOf("/")?!=?(prefixDir.length()?-?1)?&&?isCreateSrcDir)?{??????????????prefixDir?=?prefixDir.replaceAll("[^/]+/$",?"");??????????}????????????//开始压缩??????????writeRecursive(zos,?out,?srcFile,?prefixDir);????????????out.close();??????????//?注:校验和要在流关闭后才准备,一定要放在流被关闭后使用??????????System.out.println("Checksum:?"?+?csum.getChecksum().getValue());??????????BufferedInputStream?bi;??????}????????/**??????*?使用?org.apache.tools.zip.ZipFile?解压文件,它与?java?类库中的??????*?java.util.zip.ZipFile?使用方式是一新的,只不过多了设置编码方式的??????*?接口。??????*???????*?注,apache?没有提供?ZipInputStream?类,所以只能使用它提供的ZipFile??????*?来读取压缩文件。??????*?@param?archive?压缩包路径??????*?@param?decompressDir?解压路径??????*?@throws?IOException??????*?@throws?FileNotFoundException??????*?@throws?ZipException??????*/??????public?static?void?readByApacheZipFile(String?archive,?String?decompressDir)??????????????throws?IOException,?FileNotFoundException,?ZipException?{??????????BufferedInputStream?bi;????????????ZipFile?zf?=?new?ZipFile(archive,?"GBK");//支持中文????????????Enumeration?e?=?zf.getEntries();??????????while?(e.hasMoreElements())?{??????????????ZipEntry?ze2?=?(ZipEntry)?e.nextElement();??????????????String?entryName?=?ze2.getName();??????????????String?path?=?decompressDir?+?"/"?+?entryName;??????????????if?(ze2.isDirectory())?{??????????????????System.out.println("正在创建解压目录?-?"?+?entryName);??????????????????File?decompressDirFile?=?new?File(path);??????????????????if?(!decompressDirFile.exists())?{??????????????????????decompressDirFile.mkdirs();??????????????????}??????????????}?else?{??????????????????System.out.println("正在创建解压文件?-?"?+?entryName);??????????????????String?fileDir?=?path.substring(0,?path.lastIndexOf("/"));??????????????????File?fileDirFile?=?new?File(fileDir);??????????????????if?(!fileDirFile.exists())?{??????????????????????fileDirFile.mkdirs();??????????????????}??????????????????BufferedOutputStream?bos?=?new?BufferedOutputStream(new?FileOutputStream(??????????????????????????decompressDir?+?"/"?+?entryName));????????????????????bi?=?new?BufferedInputStream(zf.getInputStream(ze2));??????????????????byte[]?readContent?=?new?byte[1024];??????????????????int?readCount?=?bi.read(readContent);??????????????????while?(readCount?!=?-1)?{??????????????????????bos.write(readContent,?0,?readCount);??????????????????????readCount?=?bi.read(readContent);??????????????????}??????????????????bos.close();??????????????}??????????}??????????zf.close();??????}????????/**??????*?使用?java?api?中的?ZipInputStream?类解压文件,但如果压缩时采用了??????*?org.apache.tools.zip.ZipOutputStream时,而不是?java?类库中的??????*?java.util.zip.ZipOutputStream时,该方法不能使用,原因就是编码方??????*?式不一致导致,运行时会抛如下异常:??????*?java.lang.IllegalArgumentException??????*?at?java.util.zip.ZipInputStream.getUTF8String(ZipInputStream.java:290)??????*???????*?当然,如果压缩包使用的是java类库的java.util.zip.ZipOutputStream??????*?压缩而成是不会有问题的,但它不支持中文??????*???????*?@param?archive?压缩包路径??????*?@param?decompressDir?解压路径??????*?@throws?FileNotFoundException??????*?@throws?IOException??????*/??????public?static?void?readByZipInputStream(String?archive,?String?decompressDir)??????????????throws?FileNotFoundException,?IOException?{??????????BufferedInputStream?bi;??????????//----解压文件(ZIP文件的解压缩实质上就是从输入流中读取数据):??????????System.out.println("开始读压缩文件");????????????FileInputStream?fi?=?new?FileInputStream(archive);??????????CheckedInputStream?csumi?=?new?CheckedInputStream(fi,?new?CRC32());??????????ZipInputStream?in2?=?new?ZipInputStream(csumi);??????????bi?=?new?BufferedInputStream(in2);??????????java.util.zip.ZipEntry?ze;//压缩文件条目??????????//遍历压缩包中的文件条目??????????while?((ze?=?in2.getNextEntry())?!=?null)?{??????????????String?entryName?=?ze.getName();??????????????if?(ze.isDirectory())?{??????????????????System.out.println("正在创建解压目录?-?"?+?entryName);??????????????????File?decompressDirFile?=?new?File(decompressDir?+?"/"?+?entryName);??????????????????if?(!decompressDirFile.exists())?{??????????????????????decompressDirFile.mkdirs();??????????????????}??????????????}?else?{??????????????????System.out.println("正在创建解压文件?-?"?+?entryName);??????????????????BufferedOutputStream?bos?=?new?BufferedOutputStream(new?FileOutputStream(??????????????????????????decompressDir?+?"/"?+?entryName));??????????????????byte[]?buffer?=?new?byte[1024];??????????????????int?readCount?=?bi.read(buffer);????????????????????while?(readCount?!=?-1)?{??????????????????????bos.write(buffer,?0,?readCount);??????????????????????readCount?=?bi.read(buffer);??????????????????}??????????????????bos.close();??????????????}??????????}??????????bi.close();??????????System.out.println("Checksum:?"?+?csumi.getChecksum().getValue());??????}????????/**??????*?递归压缩??????*???????*?使用?org.apache.tools.zip.ZipOutputStream?类进行压缩,它的好处就是支持中文路径,??????*?而Java类库中的?java.util.zip.ZipOutputStream?压缩中文文件名时压缩包会出现乱码。??????*?使用?apache?中的这个类与?java?类库中的用法是一新的,只是能设置编码方式了。??????*????????*?@param?zos??????*?@param?bo??????*?@param?srcFile??????*?@param?prefixDir??????*?@throws?IOException??????*?@throws?FileNotFoundException??????*/??????private?static?void?writeRecursive(ZipOutputStream?zos,?BufferedOutputStream?bo,??????????????File?srcFile,?String?prefixDir)?throws?IOException,?FileNotFoundException?{??????????ZipEntry?zipEntry;????????????String?filePath?=?srcFile.getAbsolutePath().replaceAll("\\\",?"/").replaceAll(??????????????????"//",?"/");??????????if?(srcFile.isDirectory())?{??????????????filePath?=?filePath.replaceAll("/$",?"")?+?"/";??????????}??????????String?entryName?=?filePath.replace(prefixDir,?"").replaceAll("/$",?"");??????????if?(srcFile.isDirectory())?{??????????????if?(!"".equals(entryName))?{??????????????????System.out.println("正在创建目录?-?"?+?srcFile.getAbsolutePath()??????????????????????????+?"??entryName="?+?entryName);????????????????????//如果是目录,则需要在写目录后面加上?/???????????????????zipEntry?=?new?ZipEntry(entryName?+?"/");??????????????????zos.putNextEntry(zipEntry);??????????????}????????????????File?srcFiles[]?=?srcFile.listFiles();??????????????for?(int?i?=?0;?i?<?srcFiles.length;?i++)?{??????????????????writeRecursive(zos,?bo,?srcFiles[i],?prefixDir);??????????????}??????????}?else?{??????????????System.out.println("正在写文件?-?"?+?srcFile.getAbsolutePath()?+?"??entryName="??????????????????????+?entryName);??????????????BufferedInputStream?bi?=?new?BufferedInputStream(new?FileInputStream(srcFile));????????????????//开始写入新的ZIP文件条目并将流定位到条目数据的开始处??????????????zipEntry?=?new?ZipEntry(entryName);??????????????zos.putNextEntry(zipEntry);??????????????byte[]?buffer?=?new?byte[1024];??????????????int?readCount?=?bi.read(buffer);????????????????while?(readCount?!=?-1)?{??????????????????bo.write(buffer,?0,?readCount);??????????????????readCount?=?bi.read(buffer);??????????????}??????????????//注,在使用缓冲流写压缩文件时,一个条件完后一定要刷新一把,不??????????????//然可能有的内容就会存入到后面条目中去了??????????????bo.flush();??????????????//文件读完后关闭??????????????bi.close();??????????}??????}??}??
?要想把文件加入压缩包,你必须将ZipEntry对象传给putNextEntry( )。ZipEntry是一个接口很复杂的对象,它能让你设置和读取Zip文件里的某条记录的信息,这些信息包括:文件名,压缩前和压缩后的大小,日期,CRC校验码,附加字段,注释,压缩方法,是否是目录。虽然标准的Zip格式是支持口令的,但是Java的Zip类库却不支持。而且ZipEntry却只提供了CRC的接口,而CheckedInputStream和CheckedOutputStream却支持Adler32和CRC32两种校验码。虽然这是底层的Zip格式的限制,但却妨碍了你使用更快的Adler32了。
要想提取文件,可以用ZipInputStream的getNextEntry( )方法。只要压缩包里还有ZipEntry,它就会把它提取出来。此外还有一个更简洁的办法,你可以用ZipFile对象去读文件。ZipFile有一个entries()方法,它可以返回ZipEntries的Enumeration。然后通过zipFile. getInputStream(ZipEntry entry)获取压缩流就可以读取相应条目了。
要想读取校验码,必须先获取Checksum对象。我们这里用的是CheckedOutputStream和CheckedInputStream,不过你也可以使用Checksum。java.util.zip包中比较重要校验算法类是Adler32和CRC32,它们实现了java.util.zip.Checksum接口,并估算了压缩数据的校验和(checksum)。在运算速度方面,Adler32算法比CRC32算法要有一定的优势;但在数据可信度方面,CRC32算法则要更胜一筹。GetValue方法可以用来获得当前的checksum值,reset方法能够重新设置checksum为其缺省的值。
校验和一般用来校验文件和信息是否正确的传送。举个例子,假设你想创建一个ZIP文件,然后将其传送到远程计算机上。当到达远程计算机后,你就可以使用checksum检验在传输过程中文件是否发生错误,有点像下载文件后我们可以使用哈希值来校验文件下载过程是否出错了。
Zip类里还有一个让人莫名其妙的setComment( )方法。如ZipCompress.java所示,写文件的时候,你可以加注释,但是读文件的时候,ZipInputSream却不提供接口。看来它的注释功能完全是针对条目的,是用ZipEntry实现的。
当然,GZIP和Zip不光能用来压缩文件——它还能压缩任何东西,包括要通过网络传输的数据。
(转自:http://jiangzhengjun.iteye.com/blog/517186)