压缩和序列化主要用在数据的存储和传输上,二者都是由IO流相关知识实现,这里统一介绍下。
全部章节传送门:
压缩
Java I/O类支持读写压缩格式的数据流,你可以用他们对其他的I/O流进行封装,以提供压缩功能。
GZIP
GZIP接口比较简单,适合对单个数据流进行压缩,在Linux系统中使用较多。
public static void main(String[] args) throws IOException {
String[] paths = new String[] {"123.txt", "qqq.txt", "file.txt"};
FileOutputStream f = new FileOutputStream("test.zip");
ZipOutputStream zos = new ZipOutputStream(f);
BufferedOutputStream out = new BufferedOutputStream(zos);
zos.setComment("A test of java Zipping");
for(String path : paths) {
System.out.println("Write file " + path);
BufferedInputStream in = new BufferedInputStream(new FileInputStream(path));
zos.putNextEntry(new ZipEntry(path));
byte[] b = new byte[1024];
int len = 0;
while((len = in.read(b, 0, b.length)) != -1) {
out.write(b, 0, len);
}
in.close();
out.flush();
}
out.close();
System.out.println("Read file ");
FileInputStream fi = new FileInputStream("test.zip");
ZipInputStream in2 = new ZipInputStream(fi);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntry ze;
while((ze = in2.getNextEntry()) != null) {
System.out.println("Read file " + ze);
int x;
while((x = bis.read()) != -1) {
System.out.write(x);
}
}
bis.close();
}
ZIP
ZIP格式可以压缩多个文件,而且可以和压缩工具进行协作,是经常使用的压缩方法。
public static void main(String[] args) throws IOException {
String[] paths = new String[] {"123.txt", "qqq.txt", "file.txt"};
FileOutputStream f = new FileOutputStream("test.zip");
ZipOutputStream zos = new ZipOutputStream(f);
BufferedOutputStream out = new BufferedOutputStream(zos);
zos.setComment("A test of java Zipping");
for(String path : paths) {
System.out.println("Write file " + path);
BufferedInputStream in = new BufferedInputStream(new FileInputStream(path));
zos.putNextEntry(new ZipEntry(path));
byte[] b = new byte[1024];
int len = 0;
while((len = in.read(b, 0, b.length)) != -1) {
out.write(b, 0, len);
}
in.close();
out.flush();
}
out.close();
System.out.println("Read file ");
FileInputStream fi = new FileInputStream("test.zip");
ZipInputStream in2 = new ZipInputStream(fi);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntry ze;
while((ze = in2.getNextEntry()) != null) {
System.out.println("Read file " + ze);
int x;
while((x = bis.read()) != -1) {
System.out.write(x);
}
}
bis.close();
}
JAR和WAR
JAR(Java Archive,Java 归档文件)是与平台无关的文件格式,它允许将许多文件组合成一个压缩文件。为 J2EE 应用程序创建的 JAR 文件是 EAR 文件(企业 JAR 文件)。
JAR 文件格式以流行的 ZIP 文件格式为基础。与 ZIP 文件不同的是,JAR 文件不仅用于压缩和发布,而且还用于部署和封装库、组件和插件程序,并可被像编译器和 JVM 这样的工具直接使用。在 JAR 中包含特殊的文件,如 manifests 和部署描述符,用来指示工具如何处理特定的 JAR。
如果一个Web应用程序的目录和文件非常多,那么将这个Web应用程序部署到另一台机器上,就不是很方便了,我们可以将Web应用程序打包成Web 归档(WAR)文件,这个过程和把Java类文件打包成JAR文件的过程类似。利用WAR文件,可以把Servlet类文件和相关的资源集中在一起进行发布。在这个过程中,Web应用程序就不是按照目录层次结构来进行部署了,而是把WAR文件作为部署单元来使用。
一个WAR文件就是一个Web应用程序,建立WAR文件,就是把整个Web应用程序(不包括Web应用程序层次结构的根目录)压缩起来,指定一个.war扩展名。下面我们将第2章的Web应用程序打包成WAR文件,然后发布
要注意的是,虽然WAR文件和JAR文件的文件格式是一样的,并且都是使用jar命令来创建,但就其应用来说,WAR文件和JAR文件是有根本区别的。JAR文件的目的是把类和相关的资源封装到压缩的归档文件中,而对于WAR文件来说,一个WAR文件代表了一个Web应用程序,它可以包含 Servlet、HTML页面、Java类、图像文件,以及组成Web应用程序的其他资源,而不仅仅是类的归档文件。
在命令行输入jar即可查看jar命令的使用方法。
PS C:\> jar
用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
选项:
-c 创建新档案
-t 列出档案目录
-x 从档案中提取指定的 (或所有) 文件
-u 更新现有档案
-v 在标准输出中生成详细输出
-f 指定档案文件名
-m 包含指定清单文件中的清单信息
-n 创建新档案后执行 Pack200 规范化
-e 为捆绑到可执行 jar 文件的独立应用程序
指定应用程序入口点
-0 仅存储; 不使用任何 ZIP 压缩
-P 保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件
-M 不创建条目的清单文件
-i 为指定的 jar 文件生成索引信息
-C 更改为指定的目录并包含以下文件
如果任何文件为目录, 则对其进行递归处理。
清单文件名, 档案文件名和入口点名称的指定顺序
与 'm', 'f' 和 'e' 标记的指定顺序相同。
示例 1: 将两个类文件归档到一个名为 classes.jar 的档案中:
jar cvf classes.jar Foo.class Bar.class
示例 2: 使用现有的清单文件 'mymanifest' 并
将 foo/ 目录中的所有文件归档到 'classes.jar' 中:
jar cvfm classes.jar mymanifest -C foo/ .
序列化
把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
- 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
- 在网络上传送对象的字节序列。
序列化API
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable的对象才能被序列化。对象序列化包括如下步骤:
- 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
- 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
- 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
- 通过对象输入流的readObject()方法读取对象。
创建一个可以可以序列化的对象。
package medium.io.serialize;
import java.io.Serializable;
public class Person implements Serializable{
private static final long serialVersionUID = -7702331032373972217L;
private int age;
private String name;
private String sex;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
然后进行序列化和反序列化测试。
package medium.io.serialize;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class TestObjSerializeAndDeserialize {
public static void main(String[] args) throws Exception{
SerializePerson();
Person p = DeserializePerson();
System.out.printf("name={%s},age={%d},sex={%s}", p.getName(),
p.getAge(), p.getSex());
}
private static void SerializePerson() throws FileNotFoundException,
IOException {
Person person = new Person();
person.setName("wyk");
person.setAge(25);
person.setSex("男");
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
new File("wyk.txt")));
oo.writeObject(person);
System.out.println("Person对象序列化成功!");
oo.close();
}
private static Person DeserializePerson() throws Exception, IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
new File("wyk.txt")));
Person person = (Person)ois.readObject();
System.out.println("Person对象序列化成功!");
return person;
}
}
serialVersionUID
serialVersionUID: 字面意思上是序列化的版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。
JAVA序列化的机制是通过判断类的serialVersionUID来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。
为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
Externalizable接口
控制序列化字段还可以使用Externalizable接口替代Serializable借口。此时需要定义一个默认构造器,否则将为得到一个异常(java.io.InvalidClassException: Person; Person; no valid constructor);还需要定义两个方法(writeExternal()和readExternal())来控制要序列化的字段。
如下为将Person类修改为使用Externalizable接口。
public class Person implements Externalizable {
private String name;
private int age;
/**
* 必须有无参数的构造器
*/
public Person() {
System.out.println("无参数的构造器");
}
public Person(String name,int age) {
System.out.println("带参数的构造器");
this.name=name;
this.age=age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "姓名是"+name+"年龄是"+age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeObject(name);
out.writeObject(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
// TODO Auto-generated method stub
this.name=(String)in.readObject();
this.age=(int)in.readObject();
}
}
transient 关键字
transient修饰符仅适用于变量,不适用于方法和类。在序列化时,如果我们不想序列化特定变量以满足安全约束,那么我们应该将该变量声明为transient。执行序列化时,JVM会忽略transient变量的原始值并将默认值(引用类型就是null,数字就是0)保存到文件中。因此,transient意味着不要序列化。
静态变量不是对象状态的一部分,因此它不参与序列化。所以将静态变量声明为transient变量是没有用处的。
package medium.io.serialize;
import java.io.Serializable;
public class Person implements Serializable{
private static final long serialVersionUID = -7702331032373972217L;
private transient int age; //无论值是什么,都会被序列化为0
private String name;
private String sex;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
网友评论