压缩
1.Java I/O类库中的类支持读写压缩格式的数据流。由于压缩类库是按字节方式处理的而不是字符方式,因此这些类不适从Reader和Writer派生而来,而是属于InputStream和OutputStream继承结构的一部分。
压缩类 | 功能 |
---|---|
CheckedInputStream | GetCheckSum()为任何InputStream产生校验和(不仅是解压缩) |
CheckedOutputStream | GetCheckSum为任何OutputStream产生校验和(不仅是压缩) |
DeflaterOutputStream | 压缩的基类 |
ZipOutputStream | 一个DeflaterOutputStream ,用于将数据压缩成Zip文件格式 |
GZIPOutputStream | 一个DeflaterOutputStream,用于将数据压缩成GZIP格式 |
InflaterInputStream | 解压缩的基类 |
ZipInputStream | 一个InflaterInputStream,用于解压缩Zip文件格式的数据 |
GZIPInputStream | 一个InflaterInputStream,用于解压缩GZIP文件格式的数据 |
一.用GZIP进行简单压缩
1.GZIP接口非常简单,因此如果我们只想对单个数据流进行压缩,那么它可能是比较适合的选择:
public class GZIPCompress {
public static void main(String... args) throws IOException {
if (args.length == 0) {
System.exit(1);
}
BufferedReader in = new BufferReader(new FileReader(args[0]));
BufferedOutputStream out = new BufferedOutputStream(
new GZIPOutputStream(new FileOutputStream("test.gz")));
int c;
while ((c = in.read()) != -1) {
out.wirte(c);
}
in.close();
out.close();
}
}
二.用Zip进行多文件保存
1.支持Zip格式的Java库更加全面,利用该库可以方便地保存多个文件:
public class ZipCompress {
public static void main(String... args) throws IOException {
FileOutputStream f = new FileOutputStream("test.zip");
CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
ZipOutputStream zos = new ZipOutputStream(csum);
BufferedOutputStream out = new BufferedOutputStream(zos);
zos.setComment("A test of Java Zipping");
for (String arg : args) {
BufferedReader in = new BufferedReader(new FileReader(arg));
zos.putNextEntity(new ZipEntity(arg));
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
in.close();
out.flush();
}
out.close();
FileInputStream fi = new FileInputStream("test.zip");
CheckedInputStream csumi = new CheckInputStream(fi, new Adler32());
ZipInputStream in2 = new ZipInputStream(csumi);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntity ze;
while ((ze = in2.getNextEntity) != null) {
System.out.println("Reading file " + ze);
int x;
while ((x = bis.read()) != -1) {
System.out.write(x);
}
bis.close();
ZipFile zf = new ZipFile("test.zip");
Enumeration e = zf.entries();
while (e.hasMoreElements()) {
ZipEntity ze2 = (ZipEntity) e.nextElement();
System.out.println("File: " + ze2);
}
}
}
}
2.虽然上面的代码只展示了一种类型,但是一共有两种Checksum类型:Adler32(它快一些)和CRC32(慢一些,但更准确)。
3.对于每一个要加入压缩档案的文件,都必须调用putNextEntity(),并将其传递给一个ZipEntity对象。ZipEntity对象包含了一个功能很广的接口,允许你获取和设置Zip文件内该特定项上所有可利用的数据:名字、压缩的和未压缩的文件大小、日期、CRC校验和、】额外字段数据、注释、压缩方法以及它是否是一个目录入口等等。
4.为了能够解压缩文件,ZipInputStream提供了一个getNextEntity()方法返回下一个ZIpEntity(如果存在的话)。解压缩文件有一个更简便的方法——利用ZipFile对象读取文件。该对象有一个entries()方法用来向ZipEntries返回一个Enumeration(枚举)。
三.Java档案文件
1.Zip格式也被应用于JAR文件格式中。这种文件格式就像Zip一样,可以将一组文件压缩到单个压缩文件中,另外一个JAR文件还有一张描述了所有文件的“文件清单”(可自行创建文件清单,也可以由jar程序自动生成)。
2.JDK自带的jar程序可根据我们的选择自动压缩文件。可以使用命令行的形式调试它:
jar [options] destination [manifest] inputfile(s)
其中option只是一个字母集合,选项有:
option | 意义 |
---|---|
c | 创建一个新的或空的压缩文档 |
t | 列出目录表 |
x | 解压所有文件 |
x file | 解压该文件 |
f | 意指:“我打算指定一个文件名。”如果没有用这个选项,jar假设所有的输入都来自于标准输入;或者在创建一个文件时,输出对象也假设为标准输出 |
m | 表示第一个参数将是用户自建的清单文件的名字 |
v | 产生详细输出,描述jar所做的工作 |
O | 只储存文件,不压缩文件 |
M | 不自动创建文件清单 |
对象序列化
1.Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够将这个字节序列完全恢复为原来的对象。
2.要序列化一个对象,首先要创建某些OupputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需调用writeObject()即可将对象序列化,并将其发送给OutputStream。要反向进行该过程,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。我们最后得到的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接使用它。
二.序列化控制
1.默认序列化并不难操作,但在一些特殊情况下,需要通过实现Externalizable接口——代替实现Serializable接口——来对序列化过程进行控制。这个Exterbalizable接口继承了Serializable接口,同时添加了两个方法:writeExternal()和readExternal()。着两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作:
class Blip implements Externalizable {
public Blip () {
System.out.println("Blip Constructor");
}
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip.writeExternal");
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Blip.readExternal");
}
}
public class Blips {
public static void main(String... args) {
Blip b = new Blip();
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Blips.out"));
o.writeObject(b);
o.close();
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Blip.out"));
b = (Blip) in.readObject();
}
}
/*
Output:
Blip Constructor
Blip.writeExternal
Blip Constructor
Blip.readExternal
*/
2.正如上面的程序所示,恢复一个Externalizable对象和恢复一个Serializable对象不同。对于Serializable对象,对象完全以它存储的二进制位为基础来构造,而不调用构造器。而对于一个Externalizable对象,所有普通的默认构造器都会被调用(包括字段定义时的初始化),然后调用readExternal()。必须注意一点——所有默认构造器都会被调用,才能使Externalizable对象产生正确的行为。
3.为了正常运行,我们不仅需要在writeExternal()方法中奖来自对象的重要信息写入,还必须在readExternal()方法中恢复数据。Externalizable对象的默认构造行为并非是某种自动发生的存储与恢复操作。
4.当我们对序列化进行控制时,可能某个特定子对象不想让Java的序列化机制自动保存 与恢复。有一种方法可防止对象的敏感部分被序列化,就是将类实现为Externalizable;另外一种情况就是当我们在操作一个Serializable对象时,为了能够予以控制,可以用transient关键字逐个字段地关闭序列化:
public class Login implements Serializable {
private String username;
private transient String password;
private Login(String name, String pwd) {
username = name;
password = pwd;
}
public String toString() {
return "login info: \n username: " + username +
"\n date: " + date +
"\n password: " + password;
}
public static void main(String... args) {
Login a = new Login("Hulk", "myLittlePony");
System.out.println("login a = " + a);
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Login.out"));
o.writeObject(a);
o.close();
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Login.out"));
System.out.println("login a = " + a);
}
}
/*
Output:
Login a = login info:
username: Hulk
password: myLittlePony
Login a = login info:
username: Hulk
password: null
*/
由于Externalizable对象在默认情况下不保存它们的任何字段,所以transient关键字只能和Serializable对象一起使用。
5.如果不是特别坚持实现Externalizable接口,那么还有另一种方法。我们可以实现Serializable接口,并添加名为writeObject()和readObject()的方法。这样一旦对象被序列化或者被反序列化还原,就会自动地分别调用这两个方法,而不是默认的序列化机制。这些方法必须具有准确的方法特征签名:
private void writeObject(ObjectOutputStream stream) throws IOException
private void readObject(ObjectInputStream stream) throws IOException
三.使用“持久性”
1.我们可以通过一个字节数组来使用对象序列化,从而实现任何可Serializable对象的“深度复制”——深度复制意味着我们复制的时整个对象网,而不仅仅是基本对象及其引用。
2.只要将任何对象序列化到单一流中,就可以恢复与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。
XML
1.一种相比对象序列化更具互操作性的解决方案是将数据转换为XML格式,这可以使其被各种各样的平台和语言使用。
2.虽然JDK包含了javax.xml.*类库,但是《Thinking in Java》选择使用开源的XOM类库,因为它看起来最简单,同时也是最直观的用Java产生的修改XML方式,另外XOM还强调了XML的正确性,相关用法详见官网和github主页。
Preferences
1.Preferences API与对象序列化相比。前者与对象的持久性更密切,因为它可以自动存储和读取信息。不过它只能用于小的、受限的数据集合——我们只能存储基本类型和字符串,并且每个字符的存储长度不能超过8K。
2.Preferences是一个键-值集合,存储在一个节点层次结构中:
public class PreferencesDemo {
public static void main(String... args) throws Exception {
Preferences prefs = Preferences.userNodeForPackage(PreferencesDemo.class);
prefs.put("Location", "Oz");
prefs.put("Footwear", "Ruby Slippers");
prefs.putInt("Companions", 4);
prefs.putBoolean("Are there witches?", true);
int usageCount = prefs.getInt("UsageCount", 0);
usageCount++;
prefs.putInt("UsageCount", usageCount);
for (String key : prefs.keys())
System.out.println(key + " : " + prefs.get(key, null));
System.out.println("How many companions does Dorothy have? " + prefs.getInt("Companions", 0));
}
}
/*
Output:
Location : Oz
Footwear : Ruby Slippers
Companions : 4
Are there witches? : true
How many companions does Dorothy have? 4
*/
3.Preferences API利用合适的系统资源完成了数据存储的任务,这些资源会随操作系统的不同而不同,所以我们不一定会在执行程序后发现存储数据的本地文件。