Java NIO
1. Java NIO 概念
Java NIO(New IO) 是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
2. NIO与IO的主要区别
IO | NIO |
---|---|
面向流 | 面向缓冲区 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
NIO面向缓冲区数据流通图
通道负责连通,搭建缓冲区流通路径
缓冲区用于来回运送数据
NIO核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到IO设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
3. 缓冲区 (Buffer)
缓冲区就是数组,用于存储不同数据类型的数据。
3.1 缓冲区的类型
根据数据类型不同(除了boolean类型),提供了相应类型的缓冲区:1
2
3
4
5
6
7ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
- 上述各类型缓冲区管理方式几乎一致,通过
allocate()
获取缓冲区1
ByteBuffer buf = ByteBuffer.allocate(1024);
3.2 缓冲区存取数据的两个核心方法
put()
存入数据到缓冲区中get()
获取缓冲区中的数据
3.3 缓冲区中的四个核心属性
capacity
容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变,即为数组长度。limit
界限,表示缓冲区中可以操作数据的大小。limit值后面的数据不能进行读写。position
位置,表示缓冲区中正在操作数据的位置。mark
标记 , 表示记录当前position的位置,可以通过reset()
恢复到mark的位置。0 <= mark <= position <= limit <= capacity
3.3.1 position、limit、capacity 值的关系
当
allocate(10)
分配10个字节大小的缓冲区后,position的位置为0,capacity(容量)的值为10,limit(界限)值为10。当使用
put(5)
方法进入写数据模式
时,position指针的位置在填入数据后的第一个空闲位置,此时位置为5,capacity总容量值不变,依然为10,limit界限值还是为10。当使用方法
flip()
后进入读数据模式
,此时的position指针的位置为使用区的开始位置,即为0。limit界限的值则为5,因为在读取模式中,读取的应为使用区,所以界限为5,超过5就是空闲区了,最多取到5。capacity总容量的值依然为10变。
3.4 缓冲区Buffer类中的常用方法
Modifier and Type | Method and Description |
---|---|
abstract Object |
array() 返回支持此缓冲区的数组 (可选操作) 。 |
abstract int |
arrayOffset() 返回该缓冲区的缓冲区的第一个元素的背衬数组中的偏移量 (可选操作) 。 |
int |
capacity() 返回此缓冲区的容量。 |
Buffer |
clear() 清除此缓冲区。但是其中的元素并没有消失,只是处于在 被遗忘状态 ,因为position 、limit 值全部归零,和刚刚分配时一致。 |
Buffer |
flip() 翻转这个缓冲区。 |
abstract boolean |
hasArray() 告诉这个缓冲区是否由可访问的数组支持。 |
boolean |
hasRemaining() 告诉当前位置和极限之间是否存在任何元素。 |
abstract boolean |
isDirect() 告诉这个缓冲区是否为 direct 。 |
abstract boolean |
isReadOnly() 告知这个缓冲区是否是只读的。 |
int |
limit() 返回此缓冲区的限制。 |
Buffer |
limit(int newLimit) 设置此缓冲区的限制。 |
Buffer |
mark() 将此缓冲区的标记设置在其位置。 |
int |
position() 返回此缓冲区的位置。 |
Buffer |
position(int newPosition) 设置这个缓冲区的位置。 |
int |
remaining() 返回当前位置和限制之间的元素数。 |
Buffer |
reset() 将此缓冲区的位置重置为先前标记的位置。 |
Buffer |
rewind() 倒带这个缓冲区。 |
测试:
1 | String str = "hxh"; |
测试结果:如上图一致
1 | --allocate()--- |
rewind()
,实现可重复读
1 | //使用rewind方法,实现可重复读 |
clear()
清除此缓冲区1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//clear()清除此缓冲区。但是其中的元素并没有消失,
// 只是处于在 被遗忘状态,
// 因为position、limit 值全部归零,和刚刚分配时一致。
bf.clear();
System.out.println("--clear()---");
System.out.println("capacity---"+bf.capacity());
System.out.println("limit---"+bf.limit());
System.out.println("position---"+bf.position());
System.out.println("依然可以取到值,测试:"+(char) bf.get());
/*
--clear()---
capacity---1024
limit---1024
position---0
依然可以取到值,测试:h
*/
- 注意点:clear()清除此缓冲区。但是其中的
元素并没有消失
,只是处于在被遗忘状态
,因为position、limit、capacity
值全部归零,和刚刚分配时一致。如果直接bf.get()
取值,当然也是可以取到。
mark()
和 reset()
1 | String str = "abcdefg"; |
结果:
1 | ab |
hasRemainin
和 remaining
1 | //hasRemaining 当前位置和极限位置之间是否还有元素 |
3.5 继承Buffer类的各具体类型缓冲区
中的常用方法(以ByteBuffer类为例)
3.5.1 allocate 分配缓冲区
1 | public static ByteBuffer allocate(int capacity) |
分配一个新的字节缓冲区
新缓冲区的位置将为零,其限制将为其容量,其标记将不定义,并且其每个元素将被初始化为零。 它将有一个backing array
,其array offset
将为零。
参数
capacity
- 新的缓冲区的容量,以字节为单位结果
新的字节缓冲区
3.5.2 put 存入 ,写入缓冲区
1
public abstract ByteBuffer put(byte b)
相对放置法(可选操作) 。
将给定字节写入当前位置的缓冲区,然后增加位置。
参数
b
- 要写入的字节结果
这个缓冲区
1
2public abstract ByteBuffer put(int index,
byte b)绝对put方法(可选操作) 。
将给定字节写入给定索引的缓冲区。
参数
index
- 要写入字节的索引b
- 要写入的字节值结果
这个缓冲区
1
2
3public ByteBuffer put(byte[] src,
int offset,
int length)相对大容量put方法(可选操作) 。
此方法将字节从给定的源数组传输到此缓冲区。 如果要从数组中复制的字节多于保留在此缓冲区中的字节数,也就是说,如果
length
>
remaining()
,则不会传输任何字节,并抛出BufferOverflowException
。否则,该方法将给定数组中的
length
个字节复制到此缓冲区中,从阵列中的给定偏移量和该缓冲区的当前位置开始。 此缓冲区的位置然后增加length
。换言之,所述表格
dst.put(src, off, len)
的这种方法的调用具有完全一样的环相同的效果1
for (int i = off; i < off + len; i++) dst.put(a[i]);
除了它首先检查这个缓冲区中是否有足够的空间,并且它可能更有效率。
参数
src
- 要读取字节的数组offset
- 要读取的第一个字节的数组内的偏移量; 必须是非负数,不得大于array.length
length
- 要从给定数组读取的字节数; 必须是非负数,不得大于array.length - offset
结果
这个缓冲区
1
public ByteBuffer put(ByteBuffer src)
相对大容量put方法(可选操作) 。
此方法将给定源缓冲区中剩余的字节传输到此缓冲区。 如果源缓冲区中剩余的字节多于此缓冲区,即
src.remaining()
>
remaining()
,则不会传输任何字节,并抛出BufferOverflowException
。否则,该方法将n =
src.remaining()
个字节从给定缓冲区复制到此缓冲区中,从每个缓冲区的当前位置开始。 然后将两个缓冲器的位置递增n 。换句话说,调用此方法的形式
dst.put(src)
具有与循环完全相同的效果1
2while (src.hasRemaining())
dst.put(src.get());除了它首先检查这个缓冲区中是否有足够的空间,并且它可能更有效率。
参数
src
- 读取字节的源缓冲区; 不能是这个缓冲区结果
这个缓冲区
3.5.3 get 获取 ,读取缓冲区
1
public abstract byte get()
相对获取方法。 读取该缓冲区当前位置的字节,然后增加位置。
结果
缓冲区当前位置的字节
1
public abstract byte get(int index)
绝对获取方法。 读取给定索引处的字节。
参数
index
- 读取字节的索引结果
给定索引的字节
1
public ByteBuffer get(byte[] dst)
相对批量获取方法。
此方法将字节从此缓冲区传输到给定的目标数组。 调用此方法的形式为
src.get(a)的
行为方式与调用完全相同1
src.get(a, 0, a.length)
参数
dst
- 目的地阵列结果
这个缓冲区
1
2
3public ByteBuffer get(byte[] dst,
int offset,
int length)相对批量获取方法。
此方法将字节从此缓冲区传输到给定的目标数组。 如果缓冲区中剩余的字节比满足请求所需的字节少,也就是说,如果
length
>
remaining()
,则不
传输
任何字节并抛出BufferUnderflowException
。否则,该方法将
length
字节从该缓冲区复制到给定的数组中,从该缓冲区的当前位置开始,并在数组中给定的偏移量。 然后将该缓冲区的位置增加length
。换句话说,调用此方法的形式
src.get(dst, off, len)
具有与循环完全相同的效果1
for (int i = off; i < off + len; i++) dst[i] = src.get():
除了它首先检查这个缓冲区中是否有足够的字节,并且它可能更有效率。
参数
dst
- 要写入字节的数组offset
- 要写入的第一个字节的数组中的偏移量; 必须是非负数,不得大于dst.length
length
- 要写入给定数组的最大字节数; 必须是非负数,不得大于dst.length - offset
结果
这个缓冲区
4. 非直接缓冲区和直接缓冲区
4.1 概念区分
- 非直接缓冲区:通过
allocate()
方法分配缓冲区,将缓冲区建立在JVM的内存
中. - 直接缓冲区:通过
allocateDirect()
方法分配直接缓冲区,将缓冲区建立在物理内存
中。操作系统的内存中,可以提升效率。
4.2 直接缓冲区的相对优缺点
- 直接缓冲区利用了操作系统中
直接映射方式
,在物理内存中初始化一个物理内存映射文件
,省去了非直接缓冲区的中间多余的内容复制
步骤,让应用程序和物理磁盘直接面对。- 优点:简化步骤,直接映射,提升效率。
- 缺点:
- 初始化物理内存映射文件时,
耗费较大物理内存
,并且不会直接用完释放,必须通过垃圾回收
机制进行释放。 - 直接操作物理内存和磁盘,
不安全,不易控制
。
- 初始化物理内存映射文件时,
- 适合:数据长时间在
内存
中操作,大量数据
直接在物理内存中存放
,不存放在jvm中。
- 直接缓冲区,Java虚拟机会尽量
避免
将缓冲区的内容复制到中间缓冲区
中(或从中间缓冲区中复制内容),即省去了中间内容复制
操作。而是直接
在此缓冲区
上执行本机IO操作。 - 直接缓冲区,通过Buffer类中的
allocateDirect()
工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区
。
5. 通道(Channel)
5.1 通道概念
用于源节点和目标节点的连接。NIO中负责缓冲区中数据的传输。Channel类似于传统的“流”。但是Channel本身不存储数据,因此需要配合缓冲区进行传输。
5.2 Channel的主要实现类
1 | java.nio.channels.Channel 接口 |
5.3 Channel的获取
- Java对于支持通道的类都提供了
getChannel()
方法。- 本地IO:
FileInputStream
FileOutputStream
RandomAccessFile
- 网路IO:
Socket
ServerSocket
DatagramSocket
- 本地IO:
- Java7中的NIO 2 对于各个通道提供了静态方法
open()
- Java7中的NIO 2 的
Files
工具类提供的方法newByteChannel()