继续上面的Buffer讲解。
4.缓冲区分片
在NIO中,除了可以分配或者包装一个缓冲区对象外,还可以根据现有的缓冲区对象来创建一个子缓冲区,即在现有缓冲区上切
出一片来作为一个新的缓冲区,但现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的,也就是说,子缓冲区相当于是
现有缓冲区的一个视图窗口。调用slice()方法可以创建一个子缓冲区,让我们通过例子来看一下:
/** * 缓冲区分片 */ public class BufferSlice { static public void main( String args[] ) throws Exception { ByteBuffer buffer = ByteBuffer.allocate( 10 ); // 缓冲区中的数据0-9 for (int i=0; i<buffer.capacity(); ++i) { buffer.put( (byte)i ); } // 创建子缓冲区 buffer.position( 3 ); buffer.limit( 7 ); ByteBuffer slice = buffer.slice(); // 改变子缓冲区的内容 for (int i=0; i<slice.capacity(); ++i) { byte b = slice.get( i ); b *= 10; slice.put( i, b ); } buffer.position( 0 ); buffer.limit( buffer.capacity() ); while (buffer.remaining()>0) { System.out.println( buffer.get() ); } } }
在该示例中,分配了一个容量大小为10 的缓冲区,并在其中放入了数据0-9,而在该缓冲区基础之上又创建了一个子缓冲区,并
改变子缓冲区中的内容,从最后输出的结果来看,只有子缓冲区“可见的”那部分数据发生了变化,并且说明子缓冲区与原缓冲区是
数据共享的,输出结果如下所示:
0
1
2
30
40
50
60
7
8
9
5.只读缓冲区
看名字就知道,这个缓冲区只可以读,不能修改。。可以通过调用缓冲区的 asReadOnlyBuffer()方法,将任何常规缓
冲区转 换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。如果原
缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化:
/** * 只读缓冲区 */ public class ReadOnlyBuffer { static public void main( String args[] ) throws Exception { ByteBuffer buffer = ByteBuffer.allocate( 10 ); // 缓冲区中的数据0-9 for (int i=0; i<buffer.capacity(); ++i) { buffer.put( (byte)i ); } // 创建只读缓冲区 ByteBuffer readonly = buffer.asReadOnlyBuffer(); // 改变原缓冲区的内容 for (int i=0; i<buffer.capacity(); ++i) { byte b = buffer.get( i ); b *= 10; buffer.put( i, b ); } readonly.position(0); readonly.limit(buffer.capacity()); // 只读缓冲区的内容也随之改变 while (readonly.remaining()>0) { System.out.println( readonly.get()); } } }
6.直接缓冲区
直接缓冲区是为加快I/O 速度,使用一种特殊方式为其分配内存的缓冲区,JDK文档中的描述为:给定一个直接字节缓冲区,Java虚拟机将尽最大努力直接对它执行本机I/O操作。也就是说,它会在每一次调用底层操作系统的本机I/O 操作之前(或之后),尝试避免将缓冲区的内容拷贝到一个中间缓冲区中或者从一个中间缓冲区中拷贝数据。要分配直接缓冲区,需要调用allocateDirect()方法,而不是allocate()方法,使用方式与普通缓冲区并无区别,如下面的拷贝文件示例:
/** * 直接缓冲区 * Zero Copy 减少了一个拷贝的过程 */ public class DirectBuffer { static public void main( String args[] ) throws Exception { //在Java里面存的只是缓冲区的引用地址 //管理效率 //首先我们从磁盘上读取刚才我们写出的文件内容 String infile = "D://test.txt"; FileInputStream fin = new FileInputStream( infile ); FileChannel fcin = fin.getChannel(); //把刚刚读取的内容写入到一个新的文件中 String outfile = String.format("D://testcopy.txt"); FileOutputStream fout = new FileOutputStream(outfile); FileChannel fcout = fout.getChannel(); // 使用allocateDirect,而不是allocate ByteBuffer buffer = ByteBuffer.allocateDirect(1024); while (true) { buffer.clear(); int r = fcin.read(buffer); if (r==-1) { break; } buffer.flip(); fcout.write(buffer); } } }
7.内存映射
内存映射是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的I/O 快的多。内存映射文件I/O 是通过使文件中的
数据出现为 内存数组的内容来完成的,这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。一般来说,
只有文件中实际读取或者写入的部分才会映射到内存中。如下面的示例代码:
/** * IO映射缓冲区 */ public class MappedBuffer { static private final int start = 0; static private final int size = 26; static public void main( String args[] ) throws Exception { RandomAccessFile raf = new RandomAccessFile( "E://test.txt", "rw" ); FileChannel fc = raf.getChannel(); //把缓冲区跟文件系统进行一个映射关联 //只要操作缓冲区里面的内容,文件内容也会跟着改变 MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE,start, size ); mbb.put( 0, (byte)97 ); //a mbb.put( 25, (byte)122 ); //z raf.close(); } }