NIO是什么?
云里雾里的简介
1 | nio就是new io的缩写,在Java1.4的时候就有了,可以替代标准的IO API,它的使用方式与原来的IO完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文 件的读写操作。 |
NIO与IO(BIO)的区别
用简洁明了的表格表示吧
IO | NIO |
---|---|
面向流的操作 | 面向缓冲区的操作 |
阻塞的IO | 非阻塞的IO |
无选择器概念 | 有选择器概念 |
NIO相关概念
缓冲区(buffer)
网上介绍
一个用于特定基本数据类型的容器。由java.nio包定义的,所有缓冲区都是Buffer抽象类的子类,主要用于与NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。
个人理解
缓冲区其实可以理解为火车,火车(buffer)可以再铁路线(channel)上双向跑动,并且载有具体的乘客(data)
实操感悟
我们先看一下下图内容
由图中实现树可以知道Buffer类有很多实现类,并且有mark、position、limit、capacity四个属性,这是很重要的四个属性,读者应该弄懂这四个属性的含义。
接下来,我们代码操作,先来解读后面三个属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49package com.xk.nio;
import org.junit.Test;
import java.nio.Buffer;
import java.nio.ByteBuffer;
/**
* Created by xk on 2018-05-17
*/
public class BufferDemo {
@Test
public void lastThreeAttr(){
// 通过allocate()获得一个指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
print(byteBuffer);
// 向缓冲区写入数据
String name = "abcdef";
byteBuffer.put(name.getBytes());
print(byteBuffer);
// 切换到读模式
byteBuffer.flip();
print(byteBuffer);
// 从缓冲区读取数据
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println("bytes:"+new String(bytes, 0 ,bytes.length));
print(byteBuffer);
// 可重复读
Buffer rewind = byteBuffer.rewind();
print(rewind);
// 清空缓冲区(并没有把缓冲区的数据清除掉,只不过后续的写入会覆盖掉原来的数据,也就相当于清空了数据了。)
byteBuffer.clear();
print(byteBuffer);
}
public void print(Buffer buffer){
System.out.println(buffer.capacity());
System.out.println(buffer.limit());
System.out.println(buffer.position());
System.out.println("----------------------");
}
}对应的运行结果为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25capacity:10
limit:10
position:0
----------------------
capacity:10
limit:10
position:5
----------------------
capacity:10
limit:5
position:0
----------------------
bytes:abcde
capacity:10
limit:5
position:5
----------------------
capacity:10
limit:5
position:0
----------------------
capacity:10
limit:10
position:0
----------------------读者可以直接运行我的代码,并自己阅读代码后,可以对照下图和文字进行理解
其实缓冲区的初始情况下的position为0,capacity和limit为我们调用allocate()方法里指定的大小(int值),当我们调用put()方法写入数据,此时position会进行移动,而capacity和limit并没有影响,所以position输出为5,而其他属性不变,当我们flip切换到读取数据模式时,那么我们的position和limit都会发生变化,所以输出为limit:5,position:0
接下来,我们继续代码操作看看mark属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@Test
public void markAttr(){
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
byteBuffer.put("abcde".getBytes());
System.out.println("position:"+byteBuffer.position());
byteBuffer.flip();
//给position标记一下
byteBuffer.mark();
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes, 0, 4);
System.out.println(new String(bytes,0,4));
System.out.println("position:"+byteBuffer.position());
byteBuffer.reset();
System.out.println("position:"+byteBuffer.position());
}对应的运行结果:
1
2
3
4position:5
abcd
position:4
position:0这个应该很容易体会到吧???
总结一下:
容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据 不可读写。缓冲区的限制不能为负,并且不能大于其容量。
位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制。
标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法 指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这 个 position。
标记、位置、限制、容量遵守以下不变式:0<=mark<=position<=limit<=capacity。
接下来补充一个知识:
非直接缓冲区:我们可以通过allocate()在JVM内存中分配一个缓冲区。
直接缓冲区:我们可以通过allocateDirect()在物理内存中分配一个缓冲区,可以提高效率,但该模式比较消耗内存资源。
总结:
- 通过看源码发现,allocate(),allocateDirect()方法并不是Buffer的,只是其部分实现类才有的(比如:ByteBuffer,IntBuffer)
- 我们可以通过buffer的isDirect()判断该缓冲区是否为直接缓冲区。
通道(channel)
网上介绍
由 java.nio.channels 包定义的。Channel表示IO源与目标打开的连接。Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。
个人理解
通道其实类似于一个铁路线,比如上海-合肥的铁路线,这条铁路线是供火车运行的,有了铁路线,火车才能跑呀。
实操感悟
先看看如下2张图片
从上图我们可以发现,通道模式下,并不会向CPU申请控制IO权限,而是自己独立的直接进行IO操作控制,所以当IO操作非常多时,用通道来操作的话,可以让CPU空闲起来做其他事,这样效率比DMA方式效率高。
通过查看Channel的实现类,我们可以看到有以下主要实现类:
1
2
3
4
5•FileChannel:用于读取、写入、映射和操作文件的通道。
•DatagramChannel:通过 UDP 读写网络中的数据通道。
•SocketChannel:通过 TCP 读写网络中的数据。
•ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来 的连接都会创建一个SocketChannel。
可以看出这里的第一个为:本地IO,后三个为:网络IO接下来,我们代码操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/**
* Created by xk on 2018-05-17
*/
public class ChannelDemo {
@Test
public void testChannelDemo() throws IOException {
FileInputStream fileInputStream = new FileInputStream("xk.jpeg");
FileOutputStream fileOutputStream = new FileOutputStream("xkRC.jpeg");
FileChannel inputStreamChannel = fileInputStream.getChannel();
FileChannel outputStreamChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (inputStreamChannel.read(byteBuffer) != -1){
byteBuffer.flip();//切换到读模式,该语句不能放在while外面
outputStreamChannel.write(byteBuffer);
byteBuffer.clear();
}
outputStreamChannel.close();
inputStreamChannel.close();
fileOutputStream.close();
fileInputStream.close();
}
}可以看到我们目录下多了一个xkRC.jpeg文件
接下来,再学习2个概念:
分散(scatter):如图
聚集(gather):如图
接下来,我们代码操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34@Test
public void testScatterAndGather(){
try {
RandomAccessFile randomAccessFile = new RandomAccessFile("NIO.iml","rw");//我这里直接使用ide里自带的.iml文件做测试
FileChannel channel = randomAccessFile.getChannel();
ByteBuffer byteBuffer1 = ByteBuffer.allocate(5);
ByteBuffer byteBuffer2 = ByteBuffer.allocate(200);
ByteBuffer byteBuffer3 = ByteBuffer.allocate(1024);
ByteBuffer[] byteBuffers = {byteBuffer1, byteBuffer2, byteBuffer3};
channel.read(byteBuffers);
for (ByteBuffer byteBuffer : byteBuffers){
byteBuffer.flip();
}
System.out.println(new String(byteBuffers[0].array(), 0 , byteBuffers[0].limit()));
System.out.println("**************************");
System.out.println(new String(byteBuffers[1].array(), 0 , byteBuffers[1].limit()));
System.out.println("**************************");
System.out.println(new String(byteBuffers[2].array(), 0 , byteBuffers[2].limit()));
// 测试聚集写入
RandomAccessFile randomAccessFile2 = new RandomAccessFile("NIO2.iml","rw");
FileChannel channel1 = randomAccessFile2.getChannel();
channel1.write(byteBuffers);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<?xml
**************************
version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODU
**************************
LE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library name="JUnit4">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.12/junit-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>同时项目里生成NIO2.iml文件。