NIO概述
介绍
IO与CPU时间的比较
Java NIO(New IO),可以替代标准的Java IO API。 NIO与原来的IO有同样的作用和目的,但是使用 的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
IO与NIO有什么区别
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(块)(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
NIO核心
- Buffer
- Channel
Channel通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用NIO 系统,需要获取 用于连接 IO 设备的通道以及用于容纳数据的缓冲区Buffer。然后操作缓冲区,对数据进行处理。
一句话概括总结:
Channel负责传输,Buffer负责存储
Buffer
概述
Java NIO中的Buffer用于跟NIO通道进行交互。
数据是从通道读入缓冲区,从缓冲区写入数据通道(说白了就是存储数据)
- 固定大小的数据容器,本质是存储器,定长数组。
- NIO中,所有的数据都是用缓冲区处理的,之前学习的BIO是用流处理的。
Channel → Buffer
Buffer → Channel
Buffer分类
除了boolean之外的基本数据类型都有1个对应的Buffer 比如 IntBuffer 放int数据(除了boolean)
ByteBuffer 最常用
IntBuffer
ShotBuffer
LongBuffer
DoubleBuffer
FloatBuffer
CharBuffer
继承关系
Buffer的实例化
前面列出的7种类型的Buffer没有一种能够直接实例化,因为它们都是抽象类,但是都包含静态的工厂方法,来创建相应类的新实例
新缓冲区的创建主要以分配(allocate)和包装(wrap)操作创建。
这里以ByteBuffer为例:
- public static ByteBuffer allocate(int capacity)
- allocateDirect(int capacity)
- public static ByteBuffer wrap(byte[] array)
- public static ByteBuffer wrap(byte[] array, int offset, int length)
// 实例化一个ByteBuffer对象 分配给它16字节
ByteBuffer buf = ByteBuffer.allocate(16);
// 其他类型的Buffer类似,只不过单位不一样
四个属性(指针)
-
capacity
- 表示Buffer最大数据容量,创建后不能更改.一旦Buffer满了,需要将其清空(通过读数据或者清除数据, 才能继续往里写数据)
-
limit
- 是第一个不应该读取或写入的元素的索引。缓冲区的限制不能为负,并且不能大于其容量
- 写数据到Buffer时,limit表示可对Buffer最多写入多少个数据(写模式下 limit = capacity)
- 读数据时,limit表示Buffer里有多少可读数据,可以读取到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
-
position
- 写数据时,position表示写入数据的当前位置,初始值为0.当数据写入到Buffer中后,position会向后移动到1个可插入数据的Buffer单元.position最大值可为capacity-1
- 读数据时,position表示读入数据的当前位置,如position=2时,表示已经读入了3个值,或从第3个位置开始读取.通过flip()切换到读模式时position会被重置为0,当Buffer从position读入数据后,position会移动到下一个可读入的数据Buffer单元
-
mark
- 跟reset()结合进行标记,通过Buffer中的mark()方法,指定一个Buffer的特定的position,之后可以通过reset()方法恢复到这个position.
这4个属性遵循如下关系
0<=mark<=position<=limit<=capacity
Buffer存取数据
缓冲区的存取,以ByteBuffer为例
put操作(存)
abstract ByteBuffer | put(byte b) 将给定的字节写入此缓冲区的当前位置,然后该位置递增 。 position+1 |
---|---|
abstract ByteBuffer | put(int index, byte b) 将给定字节写入此缓冲区的给定索引处。position不变 |
ByteBuffer | put(byte[] src, int offset, int length) 此方法将把给定源数组中的字节字传输到此缓冲区中。 |
ByteBuffer | put(byte[] src) 此方法将给定的源 byte 数组的所有内容传输到此缓冲区中。 |
get操作(取)
abstract byte | get() 读取此缓冲区当前位置的字节,然后该位置递增。 |
---|---|
abstract byte | get(int index) 读取指定索引处的字节。 |
ByteBuffer | get(byte[] dst, int offset, int length) 此方法将此缓冲区的字节传输到给定的目标数组中 |
ByteBuffer | get(byte[] dst) 此方法将此缓冲区的字节传输到给定的目标数组中 |
常用方法
Buffer flip() | 反转此缓冲区。首先将limit设置为当前位置,然后将position设置为 0。如果已定义了mark,则丢弃该mark。( 修改 position为0 limit为之前position的位置 mark = -1 ) |
---|---|
Buffer rewind() | 重绕此缓冲区。将position设置为 0 并丢弃mark。 |
Buffer clear() | 清除此缓冲区,将position设置为 0,将limit设置为capacity,并丢弃mark。 mark=-1 |
boolean hasRemaining() | 告知在当前位置和限制之间是否有元素(position到limit区间内)。 当且仅当此缓冲区中至少还有一个元素时返回 true |
int remaining() | 返回当前位置与限制之间的元素数(position到limit区间内) |
int limit() | 返回 Buffer 的界限(limit) 的位置 |
Buffer limit(int n) | 将设置缓冲区界限为 n, 并返回一个具有新 limit 的缓冲区对象 |
Buffer mark() | 对缓冲区设置标记 |
Buffer reset() | 将位置 position 转到以前设置的 mark 所在的位置 |
int position() | 返回缓冲区的当前位置 position |
Buffer position(int n) | 将设置缓冲区的当前位置为 n , 并返回修改后的 Buffer 对象 |
flip() VS rewind() VS clear()
- 相同:设置position为0,丢弃mark
- 不同:
- flip方法会修改limit值为position
- rewind不会修改limit值
- clear修改limit值为capacity
Channel
通道表示到实体,如硬件设备、文件、网络套接字(socket)或可以执行一个或多个不同 I/O 操作(如读取或写入)的程序组件的开放的连接
通道可处于打开或关闭状态。创建通道时它处于打开状态,一旦将其关闭,则保持关闭状态
java.nio.channels 包定义 的。Channel 表示IO 源与目标打开的连接。 Channel 类似于传统的“流” 。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。
Channel的几个重要类型
-
FileChannel
- FileChannel 从文件中读写数据
-
DatagramChannel
- DatagramChannel 能通过 UDP 读写网络中的数据。 (UDP Client/Server)
-
SocketChannel
- SocketChannel 能通过 TCP 读写网络中的数据。 (TCP Client)
-
ServerSocketChannel
- ServerSocketChannel 可以监听新进来的 TCP 连接,像 Web 服务器那样。对 每一个新进来的连接都会创建一个 SocketChannel。 正如你所看到的,这些通道涵盖了 UDP 和 TCP 网络 IO,以及文件 IO(TCP Server)
FileChannel
用于读取、写入、映射和操作文件的通道
文件通道在其文件中有一个当前 position,可对其进行查询和修改。
实例化FileChannel(无法new)
- RandomAccessFile(可读可写)
- FileInputStream(可读 单向)
- FileOutputStream(可写 单向)
可通过以上实例的getChannel方法获取到FileChannel的实例
// 举例
FileChannel inChannel = new FileInputStream("a.mp4").getChannel();
FileChannel outChannel = new FileOutputStream("a.mp4").getChannel();
FileChannel rafIn = new RandomAccessFile("aa.mp4", "r").getChannel();
内存映射文件
内存映射文件:内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件,此时就可以假定整个文件都放在内存中,而且可以完全把它当成非常大的数组来访问(随机访问)
FileChannel中的map()方法将文件的部分或全部映射到内存
映射文件的三种模式(MapMode)
-
READ_ONLY
- 产生只读缓冲区,对缓冲区的写入操作将导致ReadOnlyBufferException
-
READ_WRITE
- 产生可写缓冲区,写或修改文件内容的话是会立刻反映到磁盘文件中去的,别的进程如果共享了同一个映射文件,那么也会立即看到变化
-
PRIVATE
- 此模式下,对内存映射的修改,不会反应到磁盘文件中,而是创建修改后缓冲区的私有副本。
注意:
请求的映射模式将受到被调用map方法FileChannel对象的访问权限所限制.
数据传输
- 写write()
- 读read()
write Demo
@Test
public void writeTest() throws IOException {
// 创建通道
FileChannel outChannel = new FileOutputStream("c.txt").getChannel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(64);
// 向缓冲区写数据
buffer.put("你不知道的事".getBytes());
buffer.flip();
// 向通道中写数据
outChannel.write(buffer);
outChannel.close();
}
read Demo
@Test
public void readTest() throws IOException{
// 创建通道
FileChannel inChannel = new FileInputStream("c.txt").getChannel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(16);
// 将数据从通道读入到缓冲区
inChannel.read(buffer);
buffer.flip();
// 把数据从缓冲区取出来
byte[] bytes = new byte[1024];
buffer.get(bytes, 0, buffer.limit());
System.out.println(new String(bytes,0,buffer.limit()));
}
Scatter/Gather
- 分散(scatter)从 Channel 中读取是指在读操作时将读取的数据写入多个 buffer 中。 因此,Channel 将从 Channel 中读取的数据“分散(scatter)”到多个 Buffer 中。
- 聚集(gather)写入 Channel 是指在写操作时将多个 buffer 的数据写入同一个 Channel,因此,Channel 将多个 Buffer 中的数据“聚集(gather)”后发送到 Channel。
分散
Demo
@Test
public void scatterTest() throws IOException{
// 分散测试
// 创建一个通道
FileChannel inChannel = new FileInputStream("a.txt").getChannel();
// 创建2个缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(8);
ByteBuffer buffer2 = ByteBuffer.allocate(8);
ByteBuffer[] byteBuffer = {buffer1,buffer2};
// 数据读入到缓冲区
long readCount = inChannel.read(byteBuffer);
System.out.println("readCount = " + readCount);
System.out.println("buffer1.capacity() = " + buffer1.capacity()+ " position = " + buffer1.position());
System.out.println("buffer2.capacity() = " + buffer2.capacity()+ " position = " + buffer2.position());
}
聚集
Demo
@Test
public void getherTest() throws IOException{
// 聚集测试
// 创建1个通道
FileChannel outChannel = new FileOutputStream("b.txt").getChannel();
// 创建2个缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(8);
ByteBuffer buffer2 = ByteBuffer.allocate(8);
// 填充数据
buffer1.put("first".getBytes());
buffer1.flip();
buffer2.put("second".getBytes());
buffer2.flip();
ByteBuffer[] byteBuffer = {buffer1, buffer2};
// 多个Buffer写入1个channel
outChannel.write(byteBuffer);
}
文件复制
通道之间进行传输(零拷贝:无需内核态与用户态切换)
- transferForm(srcChannle,position,channelSize)
- transferTo(position,channelSize,destChannel)
关闭Channel
- 只能使用1次
- close关闭 , 幂等的
- 可以通过isOpen() 判断是否关闭