JAVA NIO

简介

JavaNIO (New IO /Non Blocking IO),可以代替标准的Java IO

Java NIO 与 IO的主要区别

image-20210313001703545

通道和缓冲区

Java NIO系统的核心在于:通道(channel)和缓冲区(Buffer) 通道表示打开到IO设备(文件,套接字)的连接

若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区. 然后操作缓冲区,对数据进行处理

简而言之 channel负责传输 Buffer 负责存储

Buffer

除了Boolean 以外,都有对应的类型缓冲区

所有缓冲区的管理方式几乎一致,通过allocate() 获取对应的缓冲区

缓冲区存储数据的两个核心方法

put(): 存入数据到缓冲区中

get(): 获取缓冲区中的数据

缓冲区的四个核心属性

capacity: 容量,表示缓冲区中最大存储数据的容量,一旦声明 不能改变

position: 表示缓冲区中正在操作数据的位置

limit: 界限,表示缓冲区中可以操作数据的大小 limit后边的数据不能读写

mark: 标记,表示当前position的位置 可以通过reset()恢复到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
public void test() {
String s = "billy";
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//存入数据到缓冲区
buffer.put(s.getBytes());
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//切换到读取数据
buffer.flip();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//rewind 重复读
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
System.out.println(new String(bytes,0,bytes.length));

buffer.rewind();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
buffer.get(bytes);
System.out.println(new String(bytes,0,bytes.length));
// clear 清除
buffer.clear();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());

buffer.get(bytes);
System.out.println(new String(bytes,0,bytes.length));
}

直接缓冲区与非直接缓冲区

非直接缓冲区: 通过allocate()方法分配缓冲区

直接缓冲区: 通过allocateDirect() 方法分配缓冲区,就缓冲区建立在物理内存中可以提高效率,在某种情况下

image-20210313140134613

image-20210313140549531

通道(Channel)

通道(Channel): 由java.nio.channels包定义的. Channel表示IO源与目标打开的链接

Channel类似于传统中的流,只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互

image-20210313141537062

image-20210313142012923

通道是一个完全独立的处理器,拥有一套自己的传输方式

通道的主要实现类

java.nio.channels.channel接口:

FileChannel

SocketChannel

ServerSocketChannel

DataframChannel

获取通道:

  1. Java针对支持通道的类提供类getChannel()方法

    本地IO:

    FileInputStream/FileOutputStream

    RandomAccessFile

    网络IO:

    Socket

    ServerSocket

    DatagramSocket

  2. 在JDK1.7中的NIO2针对各个通道提供类静态方法open()

  3. 在JDK1.7中的NIO2的Files工具类的newByteChannel()

使用通道完成文件复制

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
49
50
51
52
53
54
55
56
57
58
59
@Test
public void test1() {
FileInputStream fis= null;
FileOutputStream fos = null;
FileChannel inc = null;
FileChannel ouc = null;
try {
fis = new FileInputStream("src/main/resources/1.jpg");
fos = new FileOutputStream("src/main/resources/2.jpg");

//获取通道
inc = fis.getChannel();
ouc = fos.getChannel();

//分配指定的缓冲区大小
ByteBuffer buf = ByteBuffer.allocate(1024);

//开始读取数据
while(inc.read(buf)!=-1){
buf.flip();//切换成读取数据的模式
ouc.write(buf);
buf.clear();//清空缓冲区

}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ouc!=null){

try {
ouc.close();
} catch (IOException e) {
e.printStackTrace();
}

}
if (inc!=null){
try {
inc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}

使用直接缓冲区完成文件的复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test2() throws IOException {
FileChannel inc = FileChannel.open(Paths.get("src/main/resources/1.jpg"), StandardOpenOption.READ);
FileChannel ouc = FileChannel.open(Paths.get("src/main/resources/3.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ);
//内存映射文件
MappedByteBuffer inMapBuf = inc.map(FileChannel.MapMode.READ_ONLY, 0, inc.size());
MappedByteBuffer ouMapBuf = ouc.map(FileChannel.MapMode.READ_WRITE, 0, inc.size());

//这种方式不用通过通道 直接在内存中操作 直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMapBuf.limit()];
inMapBuf.get(dst);
ouMapBuf.put(dst);
inc.close();
ouc.close();

}

通道之间的数据传输

transferFrom()

transferTo()

1
2
3
4
5
6
7
8
9
@Test
public void test3() throws IOException {
FileChannel inc = FileChannel.open(Paths.get("src/main/resources/1.jpg"), StandardOpenOption.READ);
FileChannel ouc = FileChannel.open(Paths.get("src/main/resources/4.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ);
inc.transferTo(0,inc.size(),ouc);

inc.close();
ouc.close();
}

分散与聚集

分散读取(Scatter Reads) 将通道中的数据分散到多个缓冲区中去

聚集写入(Gathering Writes) 将多个缓冲区中的数据

image-20210313154304838

image-20210313154339490

字符集: Charset

编码: 字符串 -> 字节数组

解码: 字节数组 -> 字符串

阻塞与非阻塞

通过NIO完成网络通信的三个核心

  1. 通道: 负责连接

    SelectableChannel

    - SocketChannel
    - ServerSocketChannel
    - DatagramChannel
    - Pipe.SinkChannel
    - Pipe.SourceChannel
    
  2. 缓冲区: 负责数据的存取

  3. 选择器: 是SelectableChannel的多路复用器.用于监控SelectableChannel的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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class TestBlockingNIO {
//客户端
@Test
public void client() throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileChannel inChannel = FileChannel.open(Paths.get("src/main/resources/1.jpg"), StandardOpenOption.READ);
//2.分配指定大小的Buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.读取本地文件发送到服务端
while(inChannel.read(buf)!=-1){
buf.flip();
sChannel.write(buf);
buf.clear();
}

//4.关闭通道
inChannel.close();
sChannel.close();
}

//服务端
@Test
public void server() throws IOException {
//1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//2.绑定连接
ssChannel.bind(new InetSocketAddress(9898));

//3.获取客户端连接的通道
SocketChannel sChannel = ssChannel.accept();

//4.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);

//5.读取客户端的数据并保存到本地
while(sChannel.read(buf)!=-1){
buf.flip();
outChannel.write(buf);
buf.clear();
}
//6.关闭通道
sChannel.close();
outChannel.close();
ssChannel.close();


}
}