利用JAVA API函数实现数据的压缩与解压缩.docx
《利用JAVA API函数实现数据的压缩与解压缩.docx》由会员分享,可在线阅读,更多相关《利用JAVA API函数实现数据的压缩与解压缩.docx(22页珍藏版)》请在冰点文库上搜索。
利用JAVAAPI函数实现数据的压缩与解压缩
利用JAVAAPI函数实现数据的压缩与解压缩
2002年2月
本文通过对数据压缩算法的简要介绍,然后以详细的示例演示了利用java.util.zip包实现数据的压缩与解压,并扩展到在网络传输方面如何应用java.util.zip包现数据压缩与解压
综述
许多信息资料都或多或少的包含一些多余的数据。
通常会导致在客户端与服务器之间,应用程序与计算机之间极大的数据传输量。
最常见的解决数据存储和信息传送的方法是安装额外的存储设备和扩展现有的通讯能力。
这样做是可以的,但无疑会增加组织的运作成本。
一种有效的解决数据存储与信息传输的方法是通过更有效率的代码来存储数据。
这篇文章简要的介绍了数据的压缩与解压缩,并展示了用java.util.zip包来实现数据的压缩与解压缩是多么的方便与高效。
当然用诸如WinZip,gzip,和Java压缩(或jar)之类的工具也可以实现数据的压缩与解压缩,这些工具都是独立的应用程序。
你也可以在JAVA应用程序中调用这些工具,但这并不是最直接的方法,也不是有效的解决方法。
尤其是你想更快速地实现数据的压缩与解压缩(例如在传输数据到远程机器之前)。
这篇文章包括以下内容:
∙给出一个关于数据压缩的简单的介绍
∙描述java.util.zip包
∙示例如何使用该包实现数据的压缩与解压缩
∙示例如何压缩串行化的对象并将其存储在磁碟上
∙示例如何通过数据压缩来增强"客户/服务"应用程序的性能
数据压缩概述
文件中数据冗余的最简单的类型是"字符的复制"。
让我们先来看下面一个字符串:
∙JJJJJJAAAAVVVVAAAAAA
这个字符串可以用更简洁的方式来编码,那就是通过替换每一个重复的字符串为单个的实例字符加上记录重复次数的数字来表示,上面的字符串可以被编码为下面的形式:
∙6J4A4V6A
在这里,"6J"意味着6个字符J,"4A"意味着4个字符A,以此类推。
这种字符串压缩方式称为"行程长度编码"方式,简称RLE。
再举一个例子,考虑一下矩形图像的存储。
一个单色位图,可以被存储为下面这种形式,如图1所示。
图1:
RLE方式下的位图信息
另外一种方式是将图像存为一个图元文件:
Rectangle11,3,20,5
上面的表示方法是讲矩形的起始坐标是(11,3),宽度是20,高度是5。
上述的矩形图像可以使用RLE编码方式压缩,通过对相同位记数表示如下:
0,40
0,40
0,101,200,10
0,101,10,181,10,10
0,101,10,181,10,10
0,101,10,181,10,10
0,101,200,10
0,40
上面第一行是讲图像的第一行由40个0组成。
第三行是讲图像的第三行是由10个0加上20个1再加上10个0组成,其它行以此类推。
大家注意,RLE方法需要将其表示的文件与编码文件分开。
所以,这种方法不能应用于所有的文件。
其它的压缩技术包括变长编码(也被称为哈夫曼编码),还有其它的方法。
要想了解更详细的信息,请参考有关数据和图像压缩技术方面的图书,一定会有收获的。
数据压缩有很多益处。
不管怎么说,最主要的好处就是减少存储方面的需求。
同样的,对于数据通信来讲,压缩数据在媒体中的将导致信息传输数据的提升。
数据的压缩能够通过软件在现有的硬件设备上实现或者通过带有压缩技术的特殊的硬件设备来实现。
图表2显示了基本的数据压缩结构图。
图2:
数据压缩结构图
ZIPVSGZIP
如果你是在Windows系统下工作,你可能会对工具WinZip很熟悉,是用来创建压缩档案和解开压缩档案的。
而在UNIX平台上,会有一些不同,命令tar用来创建一个档案文件(并不压缩),其它的程序(gzip或compress)用来创建一个压缩档案。
WinZip和PkZip之类的工具同时扮演着归档和压缩两个角色。
他们将文件压缩并将其归档。
另一方面,gzip并不将文件归档。
所以,在UNIX平台上,命令tar通常用来创建一个档案文件,然后命令gzip来将档案文件压缩。
Java.util.zip包
Java提供了java.util.zip包用来兼容ZIP格式的数据压缩。
它提供了一系列的类用来读取,创建,修改ZIP和GZIP格式的文件。
它还提供了工具类来计算任意输入流的数目,这可以用来验证输入数据的有效性。
该包提供了一个接口,十四个类,和两个异常处理类,如表1所示。
表1:
java.util.zip包
条目
类型
描述
Checksum
接口
被类Adler32和CRC32实现的接口
Adler32
类
使用Alder32算法来计算Checksum数目
CheckedInputStream
类
一个输入流,保存着被读取数据的Checksum
CheckedOutputStream
类
一个输出流,保存着被读取数据的Checksum
CRC32
类
使用CRC32算法来计算Checksum数目
Deflater
类
使用ZLIB压缩类,支持通常的压缩方式
DeflaterOutputStream
类
一个输出过滤流,用来压缩Deflater格式数据
GZIPInputStream
类
一个输入过滤流,读取GZIP格式压缩数据
GZIPOutputStream
类
一个输出过滤流,读取GZIP格式压缩数据
Inflater
类
使用ZLIB压缩类,支持通常的解压方式
InlfaterInputStream
类
一个输入过滤流,用来解压Inlfater格式的压缩数据
ZipEntry
类
存储ZIP条目
ZipFile
类
从ZIP文件中读取ZIP条目
ZipInputStream
类
一个输入过滤流,用来读取ZIP格式文件中的文件
ZipOutputStream
类
一个输出过滤流,用来向ZIP格式文件口写入文件
DataFormatException
异常类
抛出一个数据格式错误
ZipException
异常类
抛出一个ZIP文件
注意:
ZLIB压缩类最初是作为可移植的网络图像文件格式(PNG)标准的一部分开发的,是不受专利保护的。
从ZIP文件中解压缩和提取数据
java.util.zip包提供了数据压缩与解压缩所需要的类。
ZIP文件的解压缩实质上就是从输入流中读取数据。
Java.util.zip包提供了类ZipInputStream来读取ZIP文件。
ZipInputStream流的创建与其它输入流的创建没什么两样。
举个例子,下面的代码段创建了一个输入流来读取ZIP格式的文件:
FileInputStreamfis=newFileInputStream("figs.zip");
ZipInputStreamzin=newZipInputStream(newBufferedInputStream(fis));
ZIP输入流打开后,你可以使用getNextEntry方法来读取ZIP文件中的条目数,该方法返回一个ZipEntry对象。
如果到达文件的尾部,getNextEntry返回null:
ZipEntryentry;
while((entry=zin.getNextEntry())!
=null){
//extractdata
//openoutputstreams
}
现在,你应该建立一个输出流,如下所示:
intBUFFER=2048;
FileOutputStreamfos=newFileOutputStream(entry.getName());
BufferedOutputStreamdest=newBufferedOutputStream(fos,BUFFER);
注意:
在这段代码中我们用BufferedOutputStream代替了ZIPOutputStream。
ZIPOutputStream和GZIPOutputStream使用内置的512字节缓冲。
当缓冲区的大小大于512字节时,使用BufferedOutputStream才是正确的(例子中设置为2048)。
ZIPOutputStream不允许你设置缓冲区的大小,GZIPOutputStream也是一样,但创建GZIPOutputStream对象时可以通过构造函数的参数指定内置的缓冲尺寸。
这段代码中,使用ZIP内含的条目名称创建一个文件输出流。
可以使用entry.getName来得到它的返回句柄。
接着读出被压缩的源数据,然后写入输出流:
while((count=zin.read(data,0,BUFFER))!
=-1){
//System.out.write(x);
dest.write(data,0,count);
}
最后,不要忘记关闭输入和输出流:
dest.flush();
dest.close();
zin.close();
例程1的源程序UnZip.java显示如何解压缩并从ZIP档案中将文件释放出来。
测试这个例子,编译这个类,并运行它,传给它一个ZIP格式的文件作为参数:
prompt>javaUnZipsomefile.zip
注意:
somefile.zip应该是一个ZIP压缩档案,可以用任何一种ZIP压缩工具来创建,例如WinZip。
例程1源代码:
UnZip.java
importjava.io.*;
importjava.util.zip.*;
publicclassUnZip{
staticfinalintBUFFER=2048;
publicstaticvoidmain(Stringargv[]){
try{
BufferedOutputStreamdest=null;
FileInputStreamfis=new
FileInputStream(argv[0]);
ZipInputStreamzis=new
ZipInputStream(newBufferedInputStream(fis));
ZipEntryentry;
while((entry=zis.getNextEntry())!
=null){
System.out.println("Extracting:
"+entry);
intcount;
bytedata[]=newbyte[BUFFER];
//writethefilestothedisk
FileOutputStreamfos=new
FileOutputStream(entry.getName());
dest=new
BufferedOutputStream(fos,BUFFER);
while((count=zis.read(data,0,BUFFER))
!
=-1){
dest.write(data,0,count);
}
dest.flush();
dest.close();
}
zis.close();
}catch(Exceptione){
e.printStackTrace();
}
}
}
有一点值得大家注意,类ZipInputStream读出ZIP文件序列(简单地说就是读出这个ZIP文件压缩了多少文件),而类ZipFile使用内嵌的随机文件访问机制读出其中的文件内容,所以不必顺序的读出ZIP压缩文件序列。
注意:
ZIPInputStream和ZipFile之间另外一个基本的不同点在于高速缓冲的使用方面。
当文件使用ZipInputStream和FileInputStream流读出的时候,ZIP条目不使用高速缓冲。
然而,如果使用ZipFile(文件名)来打开文件,它将使用内嵌的高速缓冲,所以如果ZipFile(文件名)被重复调用的话,文件只被打开一次。
缓冲值在第二次打开进使用。
如果你工作在UNIX系统下,这是什么作用都没有的,因为使用ZipFile打开的所有ZIP文件都在内存中存在映射,所以使用ZipFile的性能优于ZipInputStream。
然而,如果同一ZIP文件的内容在程序执行期间经常改变,或是重载的话,使用ZipInputStream就成为你的首选了。
下面显示了使用类ZipFile来解压一个ZIP文件的过程:
1.通过指定一个被读取的ZIP文件,或者是文件名,或者是一个文件对象来创建一个ZipFile对象:
ZipFilezipfile=newZipFile("figs.zip");
2.使用entries方法,返回一个枚举对象,循环获得文件的ZIP条目对象:
while(e.hasMoreElements()){
entry=(ZipEntry)e.nextElement();
//readcontentsandsavethem
}
3.ZIP条目作为参数传递给getInputStream方法,可以读取ZIP文件中指定条目的内容,能过其返回的输入流(InputStram)对象可以方便的读出ZIP条目的内容:
is=newBufferedInputStream(zipfile.getInputStream(entry));
4.获取ZIP条目的文件名,创建输出流,并保存:
bytedata[]=newbyte[BUFFER];
FileOutputStreamfos=newFileOutputStream(entry.getName());
dest=newBufferedOutputStream(fos,BUFFER);
while((count=is.read(data,0,BUFFER))!
=-1){
dest.write(data,0,count);
}
5.最后关闭所有的输入输出流dest.flush();
dest.close();
is.close();
完整的程序代码如例程2所示。
再次编译这个文件,并传递一个ZIP格式的文件做为参数:
prompt>javaUnZip2somefile.zip
例程2源码:
UnZip2.java
importjava.io.*;
importjava.util.*;
importjava.util.zip.*;
publicclassUnZip2{
staticfinalintBUFFER=2048;
publicstaticvoidmain(Stringargv[]){
try{
BufferedOutputStreamdest=null;
BufferedInputStreamis=null;
ZipEntryentry;
ZipFilezipfile=newZipFile(argv[0]);
Enumeratione=zipfile.entries();
while(e.hasMoreElements()){
entry=(ZipEntry)e.nextElement();
System.out.println("Extracting:
"+entry);
is=newBufferedInputStream
(zipfile.getInputStream(entry));
intcount;
bytedata[]=newbyte[BUFFER];
FileOutputStreamfos=new
FileOutputStream(entry.getName());
dest=new
BufferedOutputStream(fos,BUFFER);
while((count=is.read(data,0,BUFFER))
!
=-1){
dest.write(data,0,count);
}
dest.flush();
dest.close();
is.close();
}
}catch(Exceptione){
e.printStackTrace();
}
}
}
将数据压缩归档入一ZIP文件
类ZipOutputStream能够用来将数据压缩成一个ZIP文件。
ZipOutputStream将数据写入ZIP格式的输出流。
下面的步骤与创建一个ZIP文件相关。
1、第一步是创建一个ZipOutputStream对象,我们将要写入输出流的文件作为参数传给它。
下面的代码演示了如何创建一个名为"myfigs.zip"的ZIP文件。
FileOutputStreamdest=new
FileOutputStream("myfigs.zip");
ZipOutputStreamout=newZipOutputStream(newBufferedOutputStream(dest));
2、一但目标输出流创建后,下一步就是打开数据源文件。
在这个例子中,源数据文件是指那些当前目录下的文件。
命令list用来得到当前目录下文件列表:
Filef=newFile(".");
Stringfiles[]=f.list();
for(inti=0;iSystem.out.println("Adding:
"+files[i]);
FileInputStreamfi=newFileInputStream(files[i]);
//createzipentry
//addentriestoZIPfile
}
注意:
这个例程能够压缩当前目录下的所有文件。
它不能处理子目录。
作为一个练习,你可以修改例程3来处理子目录。
3、为读出的数据创建一个ZIP条目列表:
ZipEntryentry=newZipEntry(files[i]))
4、在你将数据写入ZIP输出流之前,你必须使用putNextEntry方法将ZIP条目列表写入输出流:
out.putNextEntry(entry);
5、将数据写入ZIP文件:
intcount;
while((count=origin.read(data,0,BUFFER))!
=-1){
out.write(data,0,count);
}
6、最后关闭所有的输入输出流:
origin.close();
out.close();
完整的程序代码如例程3所示。
例程3源代码:
Zip.java
importjava.io.*;
importjava.util.zip.*;
publicclassZip{
staticfinalintBUFFER=2048;
publicstaticvoidmain(Stringargv[]){
try{
BufferedInputStreamorigin=null;
FileOutputStreamdest=new
FileOutputStream("c:
\\zip\\myfigs.zip");
ZipOutputStreamout=newZipOutputStream(new
BufferedOutputStream(dest));
//out.setMethod(ZipOutputStream.DEFLATED);
bytedata[]=newbyte[BUFFER];
//getalistoffilesfromcurrentdirectory
Filef=newFile(".");
Stringfiles[]=f.list();
for(inti=0;iSystem.out.println("Adding:
"+files[i]);
FileInputStreamfi=new
FileInputStream(files[i]);
origin=new
BufferedInputStream(fi,BUFFER);
ZipEntryentry=newZipEntry(files[i]);
out.putNextEntry(entry);
intcount;
while((count=origin.read(data,0,
BUFFER))!
=-1){
out.write(data,0,count);
}
origin.close();
}
out.close();
}catch(Exceptione){
e.printStackTrace();
}
}
}
注意:
条目列表可以以两种方式加入ZIP文件中,一种是压缩方式(DEFLATED),另一种是不压缩方式(STORED),系统默认的存储方式为压缩方式(DEFLATED)。
SetMethod方法可以用来设置它的存储方式。
例如,设置存储方式为DEFLATED(压缩)应该这样做:
out.setMethod(ZipOutputStream.DEFLATED)设置存储方式为(不压缩)应该这样做:
out.setMethod(ZipOutputStream.STORED)。
ZIP文件属性
类ZipEntry描述了存储在ZIP文件中的压缩文件。
类中包含有多种方法可以用来设置和获得ZIP条目的信息。
类ZipEntry是被ZipFile和ZipInputStream使用来读取ZIP文件,ZipOutputStream来写入ZIP文件的。
ZipEntry中最有用的一些方法显示在下面的表格2中,并且有相应的描述。
表格2:
类ZipEntry中一些有用的方法
方法签名
描述
publicStringgetComment()
返回条目的注释,没有返回null
publiclonggetCompressedSize()
返回条目压缩后的大小,未知返回-1
publicintgetMethod()
返回条目的压缩方式,没有指定返回-1
publicStringgetName()
返回条目的名称
publiclonggetSize()
返回未被压缩的条目的大小,未知返回-1
publiclonggetTime()
返回条目的修改时间,没有指定返回-1
publicvoidsetComment(Stringc)
设置条目的注释
publicvoidsetMethod(intmethod)
设置条目的压缩方式
publicvoidsetSize(longsize)
设置没有压缩的条目的大小
publicvoidsetTime(longtime)
设置条目的修改时间
求和校验
java.ut