Java I/O系统 札记[2]
10.新I/O
nio速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。我们并没有直接和通道交互,只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器换得数据,要么向缓冲器发送数据。
唯一直接与通道交互的缓冲器是ByteBuffer----也就是说,可以存储未加工字节的缓冲器。
旧I/O的FileInputStream、FileOutputStream和RandomAccessFile被修改用以产生FileChannel。Reader和Writer这种字符模式类不能用于产生通道,但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Writer。
可以参考:http://alicsd.iteye.com/blog/834447
内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们可以假定整个文件都放在内存中,而且可以完全把它当作非常大的数组来访问。
public class LargeMappedFiles { static int length = 0x8FFFFFF; // 128 Mb public static void main(String[] args) throws Exception { MappedByteBuffer out = new RandomAccessFile("test.dat", "rw").getChannel() .map(FileChannel.MapMode.READ_WRITE, 0, length); for(int i = 0; i < length; i++) out.put((byte)'x'); System.out.println("Finished writing"); for(int i = length/2; i < length/2 + 6; i++) System.out.print((char)out.get(i)); } }
注意必须指定映射文件的初始位置和映射区域的长度。
尽管“旧”的I/O流在用nio实现后性能有所提高,但是“映射文件访问”往往可以更加显著地加快速度。
文件加锁机制允许同步访问某个作文共享资源的文件。不过,竞争同一文件的两个线程可能在不同的JVM,或者一个是Java线程,另一个是操作系统中其他的某个本地线程。文件锁对其他的操作系统进程是可见的,因为Java的文件加锁直接映射到了本地操作系统的加锁工具。
public class FileLocking { public static void main(String[] args) throws Exception { FileOutputStream fos= new FileOutputStream("file.txt"); FileLock fl = fos.getChannel().tryLock(); if(fl != null) { System.out.println("Locked File"); Thread.sleep(100); fl.release(); System.out.println("Released Lock"); } fos.close(); } }
通过对FileChannel调用tryLock()或lock()就可以获得整个文件的FileLock。
tryLock()是非阻塞式的,设法获得锁,如果不能获得将直接从方法调用返回。
lock()则是阻塞式的,它要阻塞进程直至锁可以获得,或调用lock()的线程中断,或调用lock()的通道关闭。
使用FileLock.release()可以释放锁。
SocketChannel、DatagramChannel和ServerSocketChannel不需要加锁,因为他们是从单进程实体继承而来,通常不在两个进程之间共享网络socket。
锁可分为独占锁和共享锁,可以通过FileLock.isShared()进行查询。
数据库中独占锁和共享锁解释如下:
共享锁(S锁):如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
排他锁(X锁):如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
可以对映射文件的部分加锁,只能对文件通道上加锁,不能获得缓冲器上的锁。
12.对象序列化
Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行;这意味着序列化机制能弥补不同操作系统之间的差异。
对象序列化是为了支持两种主要特性:Java的远程方法调用(Remote Method Invocation,RMI)和Java Beans序列化。
只要对象实现了Serializable接口(该接口仅是一个标记接口,不包括任何方法),对象的序列化处理就会非常简单。
要序列化一个对象,首先要创建某些OuputStream对象,然后将其封装在一个ObjectOutputStream对象内,这时只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream。反之需要讲一个InputStream封装在ObjectInputStream内,然后调用readObject()。
对象序列化特别“聪明”的一个地方史它不仅保存了对象的“全景图”,而且能追踪对象内所包含的所有引用,并保存那些对象,接着又能对对象内的每个这样的引用进行追踪,以此类推。称为“对象网”。
可通过实现Externalizable接口----代替实现Serializable接口-----来对序列化过程进行扩展,主要添加了writeExternal()和readExternal()。
transient关键字:修饰的字段被关闭序列化,意思是“不用麻烦你保存或恢复数据-----我自己会处理的”。Externalizable对象在默认情况下不保存它们的任何字段,所以transient关键字只能和Serializable对象一起使用。
Externalizable的替代方法:
实现Serializable接口,并添加名为writeObject()和readObject()方法,必须具有准确的方法特征签名:
private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException;
这样一旦对象被序列化或反序列化,就会自动地分别调用这两个方法,而不是默认的序列化机制。
在调用ObjectOutputStream.writeObject()时,会检查所传递的Serializable对象,看看是否实现了它自己的writeObject(),是则跳过正常的序列化过程并调用它的writeObject(),readObject()类似。在自实现的writeObject()内部可以调用defaultWriteObject()选择执行默认的writeObject()。
利用序列化可以实现对任何可Serializable对象的“深度复制”。
如果想序列化static值,必须自己动手去实现。
Preferences只能用于小的、受限的数据集合-----只能存储基本类型和字符串,并且每个字符串的存储长度不能超过8K。