2021SC@SDUSC
Java NIO 基本介绍
-
Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的
-
NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。【基本案例】
-
NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
-
NIO 是 面向缓冲区 ,或者面向 块 编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络
-
Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入, 这个线程同时可以去做别的事情。NIO是事件驱动的。【后面有案例说明】
-
通俗理解:NIO 是可以做到用一个线程来处理多个操作的。假设有 10000 个请求过来,根据实际情况,可以分配
50 或者 100 个线程来处理。不像之前的阻塞 IO 那样,非得分配 10000 个。 -
HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP1.1 大了好几个数量级
-
案例说明 NIO 的 Buffer
public class BasicBuffer {
public static void main(String[] args) {
//举例说明 Buffer 的使用 (简单说明)
//创建一个 Buffer, 大小为 5, 即可以存放 5 个 int
IntBuffer intBuffer = IntBuffer.allocate(5);
//向 buffer 存放数据
// intBuffer.put(10);
// intBuffer.put(11);
// intBuffer.put(12);
// intBuffer.put(13);
// intBuffer.put(14);
for(int i = 0; i < intBuffer.capacity(); i++) { intBuffer.put( i * 2);
}
//put存放数据
//如何从 buffer 读取数据
//将 buffer 转换,读写切换(!!!)
/*public final Buffer flip(){
limit=position;读写数据不能超过5
position=0;
mark=-1;
return this;
}*/
intBuffer.flip();
intBuffer.position(1);//从position为1处开始读
intBuffer.limit(3);//读两个,3不可超过
while (intBuffer.hasRemaining()) { System.out.println(intBuffer.get());//get维护一个索引
}
}
NIO 和 BIO 的 比 较
-
BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多
-
BIO 是阻塞的,NIO 则是非阻塞的
-
BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求, 数据到达等),因此使用单个线程就可以监听多个客户端通道
NIO 三大核心原理示意图
一张图描述 NIO 的 Selector 、 Channel 和 Buffer 的关系
Selector 、 Channel 和 Buffer 的关系图(简单版)
关系图的说明:
- 每个 channel 都会对应一个 Buffer
- Selector 对应一个线程, 一个线程对应多个 channel(连接)
- 该图反应了有三个 channel 注册到 该 selector //程序
- 程序切换到哪个 channel 是由事件决定的, Event 就是一个重要的概念
- Selector 会根据不同的事件,在各个通道上切换
- Buffer(双向) 就是一个内存块 , 底层是有一个数组
- 数据的读取写入是通过 Buffer, 这个和 BIO , BIO 中要么是输入流,或者是输出流, 不能双向,但是 NIO 的 Buffer 是可以读也可以写, 需要 flip 方法切换
- channel 是双向的, 可以返回底层操作系统的情况, 比如 Linux , 底层的操作系统通道就是双向的
缓冲区(Buffer)
1.基本介绍
缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组)(final 数组类型[ ] hb//Non-null only for heap buffer),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer,如图: 【后面举例说明】
2.Buffer 类及其子类
-
在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类, 类的层级关系图:
-
Buffer 类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息:
debug时position会依次往前走,5退出 -
Buffer 类相关方法一览
ByteBuffer
从前面可以看出对于 Java 中的基本数据类型(boolean 除外),都有一个 Buffer 类型与之相对应,最常用的自然是 ByteBuffer 类(二进制数据),该类的主要方法如下:
通道(Channel)
基本介绍
- NIO 的通道类似于流,但有些区别如下:
通道可以同时进行读写,而流只能读或者只能写
通道可以实现异步读写数据
通道可以从缓冲读数据,也可以写数据到缓冲:
-
BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel) 是双向的,可以读操作,也可以写操作。
-
Channel 在 NIO 中是一个接口
public interface Channel extends Closeable{} -
常 用 的 Channel 类 有 : FileChannel 、 DatagramChannel 、 ServerSocketChannel 和 SocketChannel 。
【ServerSocketChannel(实际是ServerSocketChannelImpl实现)类似 ServerSocket , 抽象类SocketChannel 类似 Socket】 -
抽象类FileChannel (实际是FileChannelImpl实现)用于文件的数据读写, DatagramChannel 用于 UDP 的数据读写, ServerSocketChannel 和SocketChannel 用于 TCP 的数据读写。
-
图示
FileChannel 类
FileChannel 主要用来对本地文件进行 IO 操作,常见的方法有
public int read(ByteBuffer dst) ,从通道读取数据并放到缓冲区中
public int write(ByteBuffer src) ,把缓冲区的数据写到通道中
public long transferFrom(ReadableByteChannel src, long position, long count),从目标通道src中复制数据到当前通道
public long transferTo(long position, long count, WritableByteChannel target),把数据从当前通道复制给目标通道,底层速度很快实现零拷贝
应用实例 1-本地文件写数据
实例要求:
1)使用前面学习后的 ByteBuffer(缓冲) 和 FileChannel(通道), 将 “hello,山东大学” 写入到 file01.txt 中
2)文件不存在就创建
3)代码演示
package com.shandonguniversity.nio;
import java.io.FileOutputStream; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannel01 {
public static void main(String[] args) throws Exception{
String str = "hello,山东大学";//hello,六个字节,utf-8一个汉字三个字节
//6+12=18字节,position 0-18,positon18没有放数据
//创建一个输出流->channel
FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");
//通过输出流fileOutputStream 获取 对应的 FileChannel,java输出流对象对NIO FileChannel进行了包装
//原生fileOutputStream包裹了channel(FileChannelImp)
//这个 fileChannel 真实类型是FileChannelImpl
FileChannel fileChannel = fileOutputStream.getChannel();
//创建一个缓冲区 ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 将 str 放 入 byteBuffer byteBuffer.put(str.getBytes());
byteBuffer.put(str.getBytes());
//对 byteBuffer 进行 flip ,debug时关注此处,position变为0,limit=18
byteBuffer.flip();
//将 byteBuffer 数据写入到 fileChannel
fileChannel.write(byteBuffer);
fileOutputStream.close();//关闭流
}
}
应用实例 2-本地文件读数据
实例要求:
1)使用前面学习后的 ByteBuffer(缓冲) 和 FileChannel(通道), 将 file01.txt 中的数据读入到程序,并显示在控制台屏幕
2)假定文件已经存在
3)代码演示
import java.nio.channels.FileChannel;
public class NIOFileChannel02 {
public static void main(String[] args) throws Exception {
//创建文件的输入流
File file = new File("d:\\file01.txt");
FileInputStream fileInputStream = new FileInputStream(file);
//通过 fileInputStream 获取对应的 FileChannel -> 实际类型 FileChannelImpl
FileChannel fileChannel = fileInputStream.getChannel();
//创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//将通道的数据读入到 Buffer
fileChannel.read(byteBuffer);
// 将 byteBuffer 的 字 节 数 据 转 成 String ,byteBuffer中的hb数组转成String
System.out.println(new String(byteBuffer.array()));
fileInputStream.close();
}
}
应用实例 3-使用一个Buffer 完成文件读取、写入
阻塞型不需要中间黑色的buffer,但现在实现的是绿色的文件通道加buffer的形式
实例要求:
1)使用 FileChannel(通道) 和 方法 read , write,完成文件的拷贝
2)拷贝一个文本文件 1.txt, 放在项目下即可
3)代码演示
package com.shandonguniversity.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannel03 {
public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream("1.txt");
FileChannel fileChannel01 = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel fileChannel02 = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true) { //循环读取
//这里有一个重要的操作clear,一定不要忘了
/*
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
*/
byteBuffer.clear(); //清空buffer
int read = fileChannel01.read(byteBuffer);
System.out.println("read =" + read);
if(read == -1) { //表示读完
break;
}
//将buffer 中的数据写入到 fileChannel02 -- 2.txt
byteBuffer.flip();
fileChannel02.write(byteBuffer);
}
//关闭相关的流
fileInputStream.close();
fileOutputStream.close();
}
}
应用实例 4-拷贝文件transferFrom 方法
- 实例要求:
- 使用 FileChannel(通道) 和 方法 transferFrom ,完成文件的拷贝
- 拷贝一张图片
- 代码演示
package com.shandonguniversity.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
public class NIOFileChannel04 {
public static void main(String[] args) throws Exception {
//创建相关流
FileInputStream fileInputStream = new FileInputStream("d:\\a.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("d:\\a2.jpg");
//获取各个流对应的filechannel
FileChannel sourceCh = fileInputStream.getChannel();
FileChannel destCh = fileOutputStream.getChannel();
//使用transferForm完成拷贝
destCh.transferFrom(sourceCh,0,sourceCh.size());
//关闭相关通道和流
sourceCh.close();
destCh.close();
fileInputStream.close();
fileOutputStream.close();
}
}