Java NIO(New IO 或 Non Blocking IO)是从 Java 1.4 版本开始引入的一个新的
IO API,可以替代标准的 Java IO API。NIO 支持面向缓冲区的、基于通道的 IO 操
作。NIO 将以更加高效的方式进行文件的读写操作。
Java NIO 由以下几个核心部分组成:
Channels
Buffers
Selectors
Channel
,Buffer
和 Selector
构成Pipe
和 FileLock
,只不过是与三个核心组件共同使用的Channel
,文章会持续更新Java NIO 中的 Buffer 用于和 NIO 通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成 NIO Buffer
对象,并提供了一组方法,用来方便的访问该块内存。缓冲区实际上是
一个容器对象,更直接的说,其实就是一个数组,在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流 I/O系统中,所有数据都是直接写入或者直接将数据读取到 Stream
对象中。在 NIO 中,所有的缓冲区类型都继承于抽象类 Buffer
,最常用的就是 ByteBuffer,对于 Java 中的基本类型,基本都有一个具体 Buffer
类型与之相对应,它们之间的继承关系如下图所示:
(1)写入数据到 Buffer
(2)调用 flip()
方法
(3)从 Buffer
中读取数据
(4)调用 clear()
方法或者 compact()
方法
当向 buffer
写入数据时,buffer
会记录下写了多少数据。一旦要读取数据,需要通过flip()
方法将 Buffer
从写模式切换到读模式。在读模式下,可以读取之前写入到 buffer
的所有数据。一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear()
或 compact()
方法。clear()
方法会清空整个缓冲
区。compact()
方法只会清除已经读过的数据。任何未读的数据都被到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
@Test public void buffer01() throws Exception { //FileChannel RandomAccessFile aFile = new RandomAccessFile("d:\\atguigu\\01.txt","rw"); FileChannel channel = aFile.getChannel(); //创建buffer,大小 ByteBuffer buffer = ByteBuffer.allocate(1024); //读 int bytesRead = channel.read(buffer); while(bytesRead != -1) { //read模式 buffer.flip(); while(buffer.hasRemaining()) { System.out.println((char)buffer.get()); } buffer.clear(); bytesRead = channel.read(buffer); } aFile.close(); }
@Test public void buffer02() throws Exception { //创建buffer IntBuffer buffer = IntBuffer.allocate(8); //buffer放 for (int i = 0; i < buffer.capacity(); i++) { int j = 2*(i+1); buffer.put(j); } //重置缓冲区 buffer.flip(); //获取 while(buffer.hasRemaining()) { int value = buffer.get(); System.out.println(value+" "); } }
为了理解 Buffer 的工作原理,需要熟悉它的三个属性:
Capacity
Position
limit
position
和 limit
的含义取决于 Buffer
处在读模式还是写模式。不管 Buffer
处在什么模式,capacity
的含义总是一样的。
这里有一个关于 capacity
,position
和 limit
在读写模式中的说明
(1)capacity
作为一个内存块,Buffer 有一个固定的大小值,也叫“capacity
”.你只能往里写capacity 个 byte、long,char
等类型。一旦 Buffer 满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
(2)position
1)写数据到 Buffer 中时,position
表示写入数据的当前位置,position
的初始值为0。当一个 byte、long 等数据写到 Buffer 后, position
会向下移动到下一个可插入数据的 Buffer 单元。position 最大可为 capacity – 1(因为 position 的初始值为0).
2)读数据到 Buffer 中时,position
表示读入数据的当前位置,如 position=2 时表示已开始读入了 3 个 byte,或从第 3 个 byte 开始读取。通过 ByteBuffer.flip()
切换到读模式时 position 会被重置为 0,当 Buffer
从 position
读入数据后,position 会下移到下一个可读入的数据 Buffer 单元。
(3)limit
1)写数据时,limit
表示可对 Buffer 最多写入多少个数据。写模式下,limit 等于Buffer 的 capacity。
2)读数据时,limit
表示 Buffer
里有多少可读数据(not null 的数据),因此能读到之前写入的所有数据(limit 被设置成已写数据的数量,这个值在写模式下就是position
)。
Java NIO 有以下 Buffer 类型
这些 Buffer 类型代表了不同的数据类型。换句话说,就是可以通过 char,short,int,long,floa
t 或 double
类型来操作缓冲区中的字节。
ByteBuffer slice = buffer.slice();
创建分片,即子缓冲区
@Test public void b01() { 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(); //改变子缓冲区内容 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()); } }
创建只读缓冲区
ByteBuffer readonly = buffer.asReadOnlyBuffer();
//只读缓冲区 @Test public void b02() { ByteBuffer buffer = ByteBuffer.allocate(10); 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()); } }
创建直接缓冲区
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
@Test public void b03() throws Exception { String infile = "d:\\atguigu\\01.txt"; FileInputStream fin = new FileInputStream(infile); FileChannel finChannel = fin.getChannel(); String outfile = "d:\\atguigu\\02.txt"; FileOutputStream fout = new FileOutputStream(outfile); FileChannel foutChannel = fout.getChannel(); //创建直接缓冲区 ByteBuffer buffer = ByteBuffer.allocateDirect(1024); while (true) { buffer.clear(); int r = finChannel.read(buffer); if(r == -1) { break; } buffer.flip(); foutChannel.write(buffer); } }
@Test public void b04() throws Exception { RandomAccessFile raf = new RandomAccessFile("d:\\atguigu\\01.txt", "rw"); FileChannel fc = raf.getChannel(); MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, start, size); mbb.put(0, (byte) 97); mbb.put(1023, (byte) 122); raf.close(); }