NIO里对性能提升最显著的是内存映射(memory mapping),内存访问的速度往往比文件访问的速度快几个数量级。
在内存映射之前,还需要看NIO的一些其他的特性。
缓冲区分片
slice()方法根据现有的缓冲区创建一个子缓冲区。也就是说,它创建一个新的缓冲区,新缓冲区与原来的缓冲区的一部分共享数据。
package nio; import java.nio.ByteBuffer; public class SliceTest { public static void main(String [ ] args) {
ByteBuffer buffer = ByteBuffer.allocate( 10 );
for (int i=0; i<buffer.capacity(); ++i) {
buffer.put( (byte)i );
}
buffer.position( 3 );
buffer.limit( 7 );
ByteBuffer slice = buffer.slice(); System.out.println("slice contains: ");
while (slice.remaining()>0) {
System.out.println(slice.get() );
} for (int i=0; i<slice.capacity(); ++i) {
byte b = slice.get( i );
b *= 11;
slice.put( i, b );
}
System.out.println("================");
buffer.position( 0 );
buffer.limit( buffer.capacity() ); while (buffer.remaining()>0) {
System.out.println( buffer.get() );
} } }
输出结果为:
slice contains:
3
4
5
6
================
0
1
2
33
44
55
66
7
8
9
分散和聚集
一个分散的读取就像一个常规通道读取,只不过它是将数据读到一个缓冲区数组中而不是读到单个缓冲区中。同样地,一个聚集写入是向缓冲区数组而不是向 单个缓冲区写入数据。
你可能在编写一个使用消息对象的网络应用程序,每一个消息被划分为固定长度的头部和固定长度的正文。您可以创建一个刚好可以容纳头部的缓冲区和另一个刚好可以容难正文的缓冲区。当您将它们放入一个数组中并使用分散读取来向它们读入消息时,头部和正文将整齐地划分到这两个缓冲区中。一个缓冲区读完后,剩余的数据会依次读入剩余的缓冲区中。
ByteBuffer header = ByteBuffer.allocate (32);
ByteBuffer body = ByteBuffer (640 * 480);
fileChannel.read (header);
fileChannel.read (body);
大多数NIO通道支持分散/聚集, 上面的可以写成:
ByteBuffer [] scatterBuffers = { header, colorMap, imageBody };
fileChannel.read (scatterBuffers);
ScatteringByteChannel
是一个具有两个附加读方法的通道:
long read( ByteBuffer[] dsts );
-
long read( ByteBuffer[] dsts, int offset, int length );
聚集写入 类似于分散读取,只不过是用来写入。它也有接受缓冲区数组的方法:
long write( ByteBuffer[] srcs );
long write( ByteBuffer[] srcs, int offset, int length );
聚集写对于把一组单独的缓冲区中组成单个数据流很有用。为了与上面的消息例子保持一致,您可以使用聚集写入来自动将网络消息的各个部分组装为单个数据流,以便跨越网络传输消息。
字符集
这是一个在处理大文本文件字符编码转换时碰到的问题,即使用CharsetDecoder.decode()方法解码一个MappedByteBuffer对象时,如果这个MBB对象的长度设置的不好,可能会出现“java.nio.charset.MalformedInputException:Malformed
input length is 2.”的错误。但是如果直接使用Charset.decode()方法,则不会出现这样的错误。两端代码片段如下:
File infile = new File(inFilename);
RandomAccessFile raf = new RandomAccessFile(infile, "r");
MappedByteBuffer mbb = raf.getChannel().map(FileChannel.MapMode.READ_ONLY,,);
Charset inCharset = Charset.forName("GBK");
Charset outCharset = Charset.forName("UTF-8"); CharsetDecoder inDecoder = inCharset.newDecoder();
CharsetEncoder outEncoder = outCharset.newEncoder(); CharBuffer cb = inDecoder.decode(mbb); ByteBuffer outbb = outEncoder.encode(cb); CharSequence str = new String(outbb.array());
System.out.println("str is :"+str);
直接使用Charset.decode()方法:
File infile = new File(inFilename);
RandomAccessFile raf = new RandomAccessFile(infile, "r");
MappedByteBuffer mbb = raf.getChannel().map(FileChannel.MapMode.READ_ONLY,0,6000);
Charset inCharset = Charset.forName("GBK");
Charset outCharset = Charset.forName("UTF-8"); //CharsetDecoder inDecoder = inCharset.newDecoder();
//CharsetEncoder outEncoder = outCharset.newEncoder(); CharBuffer cb = inCharset.decode(mbb); ByteBuffer outbb = outCharset.encode(cb); CharSequence str = new String(outbb.array());
System.out.println("str is :"+str);
文件锁定
读文件时将获得一个共享锁,写文件时将获得一个 排它锁。
要获取文件的一部分上的锁,你要调用一个打开的FileChannel上的lock()方法。注意,如果要获取一个排它锁,你必须以写方式打开文件。
RandomAccessFile raf = new RandomAccessFile( "usefilelocks.txt", "rw" );
FileChannel fc = raf.getChannel();
FileLock lock = fc.lock( start, end, false );
操作完成后,需要释放锁,最好放在finally中执行:
lock.release();
正则表达式
正则表达式并不是NIO特有的特性,但是使用正则表达式,使用Pattern和Matcher将可以快速地完成一些查找、替换、校验的工作。