Java学习笔记--NIO
参考资料:http://ifeve.com/buffers/
BIO/NIO/AIO的区别联系
http://stevex.blog.51cto.com/4300375/1284437
http://www.cnblogs.com/alipayhutu/archive/2012/05/09/2492037.html
1. NIO代码示例
public static void main(String[] args) throws IOException { RandomAccessFile aFile = new RandomAccessFile("f:\\node.txt","rw"); FileChannel inChannel = aFile.getChannel(); ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf); while (bytesRead != -1) { System.out.println("Read,字符数量是:" + bytesRead); buf.flip(); System.out.println("输出:"); while (buf.hasRemaining()) { System.out.print((char) buf.get()); } System.out.println(); buf.clear(); bytesRead = inChannel.read(buf); } aFile.close(); }
2. 输出结果:
Read,字符数量是:48 输出: <node id='-665' lat='38.92025517116' lon='121.58 Read,字符数量是:48 输出: 810979934' /> <node id='-8' lat='38.90686208459 //省略
3. 解释:
3.1 Buffer的分配
要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有一个allocate方法。下面是一个分配48字节capacity的ByteBuffer的例子。
ByteBuffer buf = ByteBuffer.allocate(48);
这是分配一个可存储1024个字符的CharBuffer:
CharBuffer buf = CharBuffer.allocate(1024);
3.2 向Buffer中写数据
写数据到Buffer有两种方式:
1. 从Channel写到Buffer。例:
buf.put(127);
2. 通过Buffer的put()方法写到Buffer里。例:
int bytesRead = inChannel.read(buf); //read into buffer.
返回值为-1证明读到末尾。put方法有很多版本,允许你以不同的方式把数据写入到Buffer中。例如, 写到一个指定的位置,或者把一个字节数组写入到Buffer。 更多Buffer实现的细节参考JavaDoc。
3.3 flip()方法
flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。
换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。
从Buffer中读取数据
从Buffer中读取数据有两种方式:
1. 从Buffer读取数据到Channel。例
//read from buffer into channel. int bytesWritten = inChannel.write(buf);
2. 使用get()方法从Buffer中读取数据。
byte aByte = buf.get();
3.4 clear()与compact()方法
一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。
1. clear()
如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。
如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。
2. compact
如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。
compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
3.5 mark()与reset()方法
通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position
4. Scatter/Gather
4.1 Scattering Reads
Scattering Reads是指数据从一个channel读取到多个buffer中。
代码示例如下:
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); ByteBuffer[] bufferArray = { header, body }; channel.read(bufferArray);
注意buffer首先被插入到数组,然后再将数组作为channel.read() 的输入参数。read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。
Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。
4.2 Gathering Writes
Gathering Writes是指数据从多个buffer写入到同一个channel。
代码示例如下:
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); //write data into buffers ByteBuffer[] bufferArray = { header, body }; channel.write(bufferArray);
buffers数组是write()方法的入参,write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。
5. 内存映射 MappedByteBuffer
FileChannel提供了map方法来把文件影射为内存映像文件: MappedByteBuffer map(int mode,long position,long size); 可以把文件的从position开始的size大小的区域映射为内存映像文件,mode指出了 可访问该内存映像文件的方式:READ_ONLY,READ_WRITE,PRIVATE.
a. READ_ONLY,(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)
b. READ_WRITE(读/写): 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。 (MapMode.READ_WRITE)
c. PRIVATE(专用): 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE)
三个方法:
a. fore();缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件
b. load()将缓冲区的内容载入内存,并返回该缓冲区的引用
c. isLoaded()如果缓冲区的内容在物理内存中,则返回真,否则返回假
三个特性:
调用信道的map()方法后,即可将文件的某一部分或全部映射到内存中,映射内存缓冲区是个直接缓冲区,继承自ByteBuffer,但相对于ByteBuffer,它有更多的优点:
a. 读取快
b. 写入快
c. 随时随地写入
例:
import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; public class MemoryMappedFileInJava { private static int count = 10485760; // 10 MB public static void main(String[] args) throws Exception { RandomAccessFile memoryMappedFile = new RandomAccessFile("f:\\largeFile.txt", "rw"); // Mapping a file into memory MappedByteBuffer out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, count); // Writing into Memory Mapped File for (int i = 0; i < count; i++) { out.put((byte) 'A'); } System.out.println("Writing to Memory Mapped File is completed"); // reading from memory file in Java for (int i = 0; i < 10; i++) { System.out.print((char) out.get(i)); } System.out.println("Reading from Memory Mapped File is completed"); memoryMappedFile.close(); } }
下面快速总结一下Java内存映射文件和IO
1). Java语言通过java.nio包支持内存映射文件和IO。
2). 内存映射文件用于对性能要求高的系统中,如繁忙的电子交易系统
3). 使用内存映射IO你可以将文件的一部分加载到内存中
4). 如果被请求的页面不在内存中,内存映射文件会导致页面错误
5). 将一个文件区间映射到内存中的能力取决于内存的可寻址范围。在32位机器中,不能超过4GB,即2^32比特。
6). Java中的内存映射文件比流IO要快(译注:对于大文件而言是对的,小文件则未必)
7). 用于加载文件的内存在Java的堆内存之外,存在于共享内存中,允许两个不同进程访问文件。顺便说一下,这依赖于你用的是direct还是non-direct字节缓存。
8). 读写内存映射文件是操作系统来负责的,因此,即使你的Java程序在写入内存后就挂掉了,只要操作系统工作正常,数据就会写入磁盘。
9). Direct字节缓存比non-direct字节缓存性能要好
10). 不要经常调用MappedByteBuffer.force()方法,这个方法强制操作系统将内存中的内容写入硬盘,所以如果你在每次写内存映射文件后都调用force()方法,你就不能真正从内存映射文件中获益,而是跟disk IO差不多。
11). 如果电源故障或者主机瘫痪,有可能内存映射文件还没有写入磁盘,意味着可能会丢失一些关键数据。
12). MappedByteBuffer和文件映射在缓存被GC之前都是有效的。sun.misc.Cleaner可能是清除内存映射文件的唯一选择。