8、新的I/O
内存映射文件。内存映射文件允许创建和修改那些因为太大而不能放入内存的文件,有了内存映射文件就可以假定整个文件放在内存中,而且可以完全把它当作非常大的数组来访问。可由RandomAccessFile开始,获得该文件上的通道,然后调用map()产生MappedByteBuffer,这是一种特殊类型的直接缓冲器,注意我们必须指定映射文件的初始位置和映射区域的长度,这意味着可以映射某个大文件的较小的部分。MappedByteBuffer由ByteBuffer继承而来,因此具有ByteBuffer的所有方法。“映射文件访问”往往可以更加显著地加快速度。
MappedByteBuffer out = new RandomAccessFile("test.dat", "rw").getChannel() .map(FileChannel.MapMode.READ_WRITE, 0, length);
文件加锁。文件加锁机制允许同步访问某个作为共享资源的文件。通过FileChannel调用tryLock()或lock(),就可以获得整个文件的FileLock。(SocketChannel、DatagramChannel和ServerSocketChannel不需要加锁,因为它们是从单进程实体继承而来,通常不在两个进程之间共享网络socket)tryLock()是非阻塞式的,它设法获取值,但是如果不能获得(当其他进程已经持有相同的锁,并不共享时),它将直接从方法调用方法返回。lock()则是阻塞式的,它要阻塞进程直至锁可以获得,或调用lock()的线程中断,或调用lock()的通道关闭。使用FileLock.release()可以释放锁。使用如下方法对文件的一部分上锁:
tryLock(long position, long size, boolean shared) or lock(long position, long size, boolean shared)
其中,加锁的区域由size-position决定,第三个参数指定是否是共享锁。锁的类型(共享或独占)可以通过 FileLock.isShared()进行查询。
9、压缩
Java I/O类库中的类支持读写压缩格式的数据流,也可用它们对其他的I/O类进行封装,已提供压缩功能。这些类不是从Reader和Writer类派生而来的,而是属于InputStream和OutputStream继承层次结构的一部分,因为压缩类库是按字节方式而不是字符方式处理的,不过有时会被迫要混合使用两种类型的数据流(可使用InputStreamReader和OutputStreamWriter在两种类型间方便地进行转换)。
尽管存在许多种压缩算法,但是Zip和GZip可能是最常用的。
用GZip进行简单压缩。GZip接口非常简单,因此如果只想对单个数据流(而不是一系列互异数据)进行压缩,那么它可能是比较适合的选择。压缩类的使用非常直观——直接将输出流封装成GZipOutputStream或ZipOutputStream,并将输入流封装成GZipInputStream或ZipInputStream即可,其他全部操作就是通常的I/O读写。
用Zip进行多文件保存。支持Zip格式的java库(标准Zip格式)更加全面,利用该库可方便地保存多个文件,它甚至有个独立的类,使读取Zip文件更加方便。Checksum类来计算和校验文件的校验和的方法,一共有两种Checksum类型:Adler32(它快一些)和CRC32(慢一些,但更准确)。
对于每一个要加入压缩档案的文件,都必须调用putNextEntry(),并传递给它一个ZipEntry对象。ZipEntry对象包含了一个功能广泛的接口,允许获取和设置Zip文件内该特定项上所有可利用数据:名字、压缩的和未压缩的文件大小、日期、CRC校验和、额外字段数据、注释、压缩方法以及是否它是一个目录入口等等。
CheckedInputStream和CheckedOutputStream都支持Adler32和CRC32两种类型的校验和,但是ZipEntry类只有支持一个CRC接口。
为了能够解压缩文件,ZipInputStream提供了一个getNextEntry()方法返回下一个ZipEntry(如果存在的话);解压缩文件有个更简便的方法——利用ZipFile对象读取文件,该对象有个entries()方法用来向ZipEntries返回一个Enumeration(枚举)。
Zip流的方法setComment(),写文件时写注释。
GZip或Zip库的使用并不仅仅局限于文件——它可以压缩任何东西,包括需要通过网络发送的数据。
10、对象序列化
java的对象序列化将那些实现了Serializbale接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。
java对象的序列化是非常有趣的,因为利用它可以实现轻量级持久性(lightweight persistence)。”持久性“意味着一个对象的生存周期并不取决于程序是否正在执行,它可以生存于程序的调用之间。之所以称其为”轻量级“,是因为不能用某种”persistent“(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽管将来可能实现),而是对象必须在程序中显示地序列化(serialize)和反序列化还原(deserialize)。需要更严格的持久性机制,可以考虑像Hibernate之类的工具。
对象序列化的概念加入到语言中是为了支持两种特性。一是Java的远程方法调用(Remote Method Invocation,RMI),它使存活于其他计算机上的对象使用起来就像是存活于本机上一样,当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值;二是对JavaBeans来说,对象序列化也是必需的,使用一个Bean时一般情况下是在设计阶段对它的状态信息进行配置,这种状态信息必须保存下来,并在程序启动时进行后期恢复,这种具体工作就是由对象序列化完成。
只要对象实现了Serializable接口(该接口是一个标记接口,不包括任何方法),对象序列化就能进行处理。
要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内,再只需调用writeObject()即可将对象序列化,并将其发送给OutputStream(对象序列化是基于字节的)。要反向进行该过程(反序列化,即将一个序列还原为一个对象),需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。最后获得的是一个引用,它指向一个向上转型的Object,所以向下转型才能直接设置它们。
对象序列化特别聪明的一个地方是它不仅仅保存了对象的全景图,而且能追踪对象内所包含的所有引用,并保存那些对象;接着又能对对象内包含的每个这样的引用进行追踪;以此类推。
在对象序列化还原的过程中,没有调用任何构造器,包括默认构造器,整个对象都是通过从InputStream中取得数据恢复而来的。
将一个对象从它的序列化状态中恢复过来,必须保证Java虚拟机能找到相关的.class文件。
序列化控制。也许考虑特殊的安全问题,不希望对象的某一部分序列化;或者一个对象被还原后,某子对象需要重新创建,从而不必将子对象序列化。
可通过实现Externalizable接口(没有任何东西自动序列化和反序列化)——代替实现Serializable接口——来对序列化过程进行控制。Externalizable接口继承了Serializable接口,同时增加了两个方法:(对所需显示地序列化)writeExternal()和(对所需显示地反序列化)readExternal(),这两个方法会在序列化和反序列化还原的过程中被自动调用,一边执行一些特殊操作。对于一个Externalizable对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用readExternal(),必须注意一点——所有默认的构造器被调用,才能使Externalizable对象产生正确的行为。
操作的是一个Serializable对象,那么所有序列化操作都会自动进行,为了能够予以控制,可以使用transient(瞬时)关键字逐个字段地关闭序列化。由于Externalizable对象在默认情况下不保存它们的任何字段,所以transient关键字只能和Serializable对象一起使用。
如果想保存系统状态,最安全的做法是将其作为一个原子操作进行序列化。将构成系统状态的所有对象都置入单一的容器内,并在一个操作中将该容器直接写出,然后同样只需一次方法调用即可将其恢复。
11、XML
对象序列化的一个重要限制是它只是Java的解决方案:只有Java程序才能反序列化这种对象。一种更具会操作性的解决方案是将数据转换为XML格式。
12、Preferences
Preferences API比对象序列化相比,前者与对象持久性更密切,因为它可以自动存储和读取信息,但它只能用于小的、受限的数据集合——只能存储基本类型和字符串,并且每个字符串的存储长度不能超过8K(不是很小,但并不想用它来创建任何重要的东西)。Preferences API用于存储和读取用户的偏好(Preferences)以及程序配置项的设置。
Preferences是一个键-值集合(类似映射),存储在一个节点层次结构中,尽管节点层次结构可用来创建更为复杂的结构,但通常是创建以你的类名命名的单一节点,然后将信息存储于其中。