参考:Java核心技术卷2 高级特性 第二章
对象序列化是以特殊的文件格式存储对象数据的。当存储一个对象时,这个对象所属的类也必须存储。这个类的描述包含:
指纹是通过对类、超类、接口、域类型和方法签名按照规范方式排序,然后将安全散列算法(SHA)应用于这些数据而获得的。
SHA是一种可以为较大的信息快提供指纹的快速算法,不论是最初的数据块尺寸有多大,这种指纹总是20个字节的数据包。它是通过在数据上执行一个灵巧的操作序列而创建的,这个序列在本质上可以百分百地保证无论这些数据以何种方式发生变化,其指纹也都会跟着变化。但是序列化机制只使用了SHA码的前8个字节作为类的指纹。即便这样,当类的数据域或方法发生变化时,其指针跟着变化的可能性还是非常大。
了解确切的文件格式确实不那么重要,但是对象流对其所包含的所有对象都有详细描述,并且这些充足的细节可以用来重构对象和对象数组,因此了解它还是有很大益处的。
对象流输出中包含所有对象的类型和数据域,每个对象都被赋予一个序列号,相同对象的重复出现将被存储为对这个对象的序列号的引用。
某些数据域是不可以被序列化的,java有一种很简单的机制来防止这种域被序列化,就是将它们标记成是transient的。如果这些域属于不可序列化的类,也需要将它们标记成transient。瞬时的域在对象被序列化时总是被跳过的。
除了让序列化机制来保存和恢复对象数据,类还可以定义它自己的机制。为了做到这一点,这个类必须实现Externalize接口,需要它定义两个方法:
public void readExternal(ObjectInputStream in) throws IOException; ClassNotFoundException;
public void writeExternal(ObjectOutputStream out) throws IXException;
这些方法对包括超类数据在内的整个对象的存储和恢复负全责。
在写出对象时,序列化机制在输出流中仅仅只是记录该对象所属的类。在读入可外部化的类时,对象输入流将用无参构造器创建一个对象,然后调用readExternal方法。
在序列化和反序列化时,如果目标对象是唯一的,那么你必须加倍当心,这通常会在实现单例和类型安全的枚举时发生。
如果使用的是Java语言的enum接口,就不必担心序列化,它能够正常工作。
即使构造器是私有的,序列化机制也可以创建新的对象!
为了解决这个问题,必须定义称为readResolve的特殊序列化方法。如果定义了readResolve方法,在对象被序列化之后就会调用它。它必须返回一个对象,而该对象之后会称为readObject的返回值。
序列化机制有一种很有趣的用法:即提供了一种克隆对象的简便途径,只要对应的类是可序列化的即可。做法很简单,直接将对象序列化到输出流中,然后将其读回。这样产生的新对象是对现有对象的一个深拷贝。
大多数操作系统都可以利用虚拟内存实现来将一个文件或者文件的一部分映射到内存中。然后这个文件就可以当做是内存数组一样地访问,比传统的文件操作要快的多。
java.nio包使内存映射变得简单,需要做的:
首先,从文件中获得一个通道(channel),通道是用于磁盘文件的一种抽象,它使我们可以访问诸如内存映射、文件加锁机制以及文件间快速数据传递等操作系统特性。
FileChannel channel=FileChannel.open(path, options);
然后,通过调用FileChannel类的map方法从这个通道中获得一个ByteBuffer。你可以指定想要映射的文件区域与映射模式,支持的模式有三种:
一旦有了缓冲区,就可以使用ByteBuffer类和Buffer超类的方法读写数据了。
缓冲区支持顺序和随机数据访问,它有一个可以通过get和put操作来移动的位置。
在使用内存映射时,创建了单一的缓冲区横跨整个文件或者我们感兴趣的文件区域。还可以使用更多的缓冲区来读写大小适度的信息块。
缓冲区是由具有相同类型的数值构成的数组,Buffer类是一个抽象类,它有总多的具体子类,包括ByteBuffer、CharBuffer、DoubleBuffer、IntBuffer、LongBuffer和ShortBuffer。
实践中最常用到的是ByteBuffer和CharBuffer。每个缓冲区都具有:
这些值的关系:0<= 标记<=位置<=界限<=容量
文件加锁机制
多个同时执行的程序需要修改同一个文件时,这些程序需要以某种方式进行通信,不然文件很容易被破坏。文件锁可以解决这个问题,可以控制对文件或者文件中某个范围的字节的访问。
文件加锁机制是依赖于操作系统的,需要注意的几点: