NIO | BIO |
---|---|
面向缓冲区Buffer | 面向流Stream |
非阻塞Non Blocking IO | 阻塞Blocking IO |
选择器Selector |
Buffer:一块可读写数的内存,被包装成NIO Buffer对象,并提供一组方法来访问。相对数组更易操作管理。
Channel:可读可写的管道,流(inPut,Output)是单向。通道可支持读取写入缓冲区,也支持异步读写。
Selector:可检查多个NIO管道是否就绪。这样一个单独线程可管理多个Channel。
一个用于基本数据类型的容器。nio包定义。所有缓冲区都是Buffer抽象类的子类。主要用于与NIO通道进行交互。数据都是从通道读入缓冲区,在写入管道。
Buffer类似数组,保存多个相同类型的数据。根据类型不同,有以下子类:Byte,Char,Short,Int,Long,Float,Double。上述Buffer采取相同的方法管理数,只是数据类型不同。
static XxxBufer allocate(int capacity) : 创建一个容量为capacity的 XxxBuffer对象
Buffer clean():清空缓冲区并返回缓冲区的引用
Buffer flip():为将缓冲区的界限设置为当前位置,并将当前位置重置为0
int capacity():返回Buffer的容量
boolean hasRemaining():判断缓冲区还有元素
int limit():返回Buffer界限位置
Buffer limit():设置缓冲区界限为n,并返回一个新的limit的缓冲区对象
Buffer mark():对缓冲区设置标记
int position():返回缓冲区当前位置
Buffer position(int n):设置缓冲区当前位置为n,并返回修改后的Buffer对象
int remaining():返回position和limit之间的元素个数
Buffer reset():将位置position转到以前的设置的mark所在的位置
Buffer rewind():位置设置为0,取消设置的mark
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(buffer.position()); //0
System.out.println(buffer.limit()); //10
System.out.println(buffer.capacity()); //10
String name = "xuy";
buffer.put(name.getBytes());
System.out.println(buffer.position()); //3
System.out.println(buffer.limit()); //10
System.out.println(buffer.capacity()); //10
buffer.flip();
System.out.println(buffer.position()); //0
System.out.println(buffer.limit()); //3
System.out.println(buffer.capacity()); //10
char c = (char) buffer.get();
System.out.println(c); //x
System.out.println(buffer.position()); //1
System.out.println(buffer.limit()); //3
System.out.println(buffer.capacity()); //10
buffer.clear();
System.out.println(buffer.position()); //0
System.out.println(buffer.limit()); //10
System.out.println(buffer.capacity()); //10
System.out.println(c); //x 只有在重复值时生效
ByteBuffer buf = ByteBuffer.allocate(10);
String n = "abcdefg";
buf.put(n.getBytes());
buf.flip();
//读取数据
byte[] b = new byte[2];
buf.get(b);
String rs = new String(b);
System.out.println(rs); //ab
System.out.println(buf.position()); //2
System.out.println(buf.limit()); //7
System.out.println(buf.capacity()); //10
buf.mark(); //标记此刻位置:2
byte[] b2 = new byte[3];
buf.get(b2);
System.out.println(new String(b2)); //cde
System.out.println(buf.position()); //5
System.out.println(buf.limit()); //7
System.out.println(buf.capacity()); //10
buf.reset(); //回到标记位置
if(buf.hasRemaining()) {
System.out.println(buf.remaining()); //5
}
Buffer提供两种用于数据操作方法:get,put获取Buffer中数据。
get():获取单个字节
get(byte[] dst):批量读取多个字节到dst中
get(int index):读取指定索引位置的字节(不移动position)
put(byte b):将给定单个字节写入缓冲区的当前位置
put(byte[] src):将src中的字节写入缓冲区的当前位置
put(int index, byte b):将指定字节写入缓冲区的索引位置(不移动position)
buteBuffer分为基于直接内存(非堆内存),直接作用于本地IO操作,高效;另一种是非直接内存(堆内存),IO操作时要先从本进程内存复制到直接内存,再利用本地IO处理。
可使用isDriect()方法判断
非直接内存作用链:本地IO - 直接内存 - 非直接内存 - 直接内存 - 本地IO
直接内存调用链:本地IO - 直接内存 - 本地IO
结论:发送大量数据,生命周期长,直接内存效率高。直接使用allocateDirect创建,耗费性能。不过数据在JVM外存储不占用应用内存。
java.nio.channels包定义,表示IO源与目标打开的连接。Channel类似流,但不能直接访问数据,Channel只能与Buffer进行交互。
FIleChannel:用于读写,映射和操作文件
DatagramChannel:通过UDP读写网络中的数据
SocketChannel:通过TCP读写网络中的数据
ServerSocketChannel:可监听新进来的TCP连接,对每一个连接创建SocketChannel。
获取通道一种方式是对支持通道对象调用getChannel()方法,支持通道类型:FileInputStream,FileOutputStream,RendomAccessFile,DatagramSocket,Socket,ServerSocket。
获取管道的其他方式是使用File类的静态方法newByteChannel()获取字节通道。open()打开并返回指定通道。
//写测试
try {
//1.从字节输出流写目标文件
FileOutputStream fos = new FileOutputStream("data.txt");
//2.得到字节输出流对应的Channel
FileChannel channel = fos.getChannel();
//3.分配缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("xuyu".getBytes());
//4.缓冲区改为写模式
buffer.flip();
channel.write(buffer);
channel.close();
System.out.println("write ok!");
}catch (Exception e){
e.printStackTrace();
}
//读测试
try {
//1.定义一个文件字节输入流说源文件联通
FileInputStream is = new FileInputStream("data.txt");
//2.需要得到文件字节输入流管道
FileChannel channel = is.getChannel();
//3.定义一个缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(1024);
//4.读取数据到缓冲区
channel.read(buffer1);
buffer1.flip();
//5.读取缓冲区中的数据并输出
String rs = new String(buffer1.array(), 0,buffer1.remaining()); //回到首位读取
System.out.println(rs);
} catch (Exception e){
e.printStackTrace();
}
//文件复制
try{
//源文件
File srcFile = new File("C:\\Desktop\\Test.jpg");
File desFile = new File("C:\\Desktop\\Test.jpg");
//得到一个字节输出流和字节输入流
FileInputStream fis = new FileInputStream(srcFile);
//得到一个字节输出流
FileOutputStream fos = new FileOutputStream(desFile);
//得到文件通道
FileChannel isChannel = fis.getChannel();
FileChannel osChannel = fos.getChannel();
//分散与聚集
try{
//1.字节输入管道
FileInputStream is = new FileInputStream("data.txt");
FileChannel isChannel = is.getChannel();
//2.字节输出流管道
FileOutputStream fos = new FileOutputStream("data2.txt");
FileChannel fosChannel = fos.getChannel();
//3.定义多个缓冲区做数据分散
ByteBuffer buffer1 = ByteBuffer.allocate(4);
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {buffer1, buffer2};
//4.从通道中读取数据分散到各个缓冲区
isChannel.read(buffers);
//5.从每个缓冲区中查询是否有数据读取到了
for (ByteBuffer buffer : buffers) {
buffer.flip(); //切换到读模式
System.out.println(new String(buffer.array(), 0, buffer.remaining()));
}
//6.聚集写入
fosChannel.write(buffers);
isChannel.close();
fosChannel.close();
System.out.println("文件复制完成");
}catch (Exception e){
e.printStackTrace();
}
//从目标通道复制原通道数据
try{
//1.字节输入管道
FileInputStream fis = new FileInputStream("data.txt");
FileChannel fisChannel = fis.getChannel();
//2.字节输出流管道
FileOutputStream fos = new FileOutputStream("data1.txt");
FileChannel fosChannel = fos.getChannel();
//3.复制
fosChannel.transferFrom(fisChannel,fosChannel.position(),fisChannel.size());
fisChannel.close();
fosChannel.close();
}catch (Exception e){
e.printStackTrace();
}
//从原通道数据复制到目标通道
try{
//1.字节输入管道
FileInputStream is = new FileInputStream("data.txt");
FileChannel isChannel = is.getChannel();
//2.字节输出管道流
FileOutputStream fos = new FileOutputStream("data1.txt");
FileChannel osChannel = fos.getChannel();
//3.复制
isChannel.transferTo(isChannel.position(), isChannel.size(),osChannel);
isChannel.close();
osChannel.close();
} catch (Exception e) {
e.printStackTrace();
}
Selector是SelectableChannel对象的多路复用器,Selector可同时监听多个SelectableChannel的IO状况。利用Selector可使一个单独的线程管理多个Channel。Selector是非阻塞IO的核心。
创建Selector:调用Selector.open()
向选择器注册通道:SelectableChannel.register(Selector sel, int ops);
//获取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); //切换非阻塞模式 ssChannel.configureBlocking(false); //绑定连接 ssChannel.bind(new InetSocketAddress(9898)); //获取选择器 Selector selector = Selector.open(); //将通道注册到选择器上,并且指定“监听接收事件” ssChannel.register(selector, SelectionKey.OP_ACCEPT);
当调用register()将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数ops指定。可监听二点事件类型:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE
selector可以实现:一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能,弹性伸缩能力和可靠性都得到了极大的提升。
ServerSocketChannel = ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.bind(new InetSocketAddress(9999));
ssChannel.bind(new InetSocketAddress(9999));
ssChannel.register(selector, selectionKey.OP_ACCEPT);
while(selector.select() > 0) {
sout("第一轮");
//获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()) {
//获取准备“就绪”的事件
SelectionKey sk = it.next();
//判断具体是什么事件准备就绪
if(sk.isAcceptable()) {
//若就绪,获取客户端连接
SocketChannel sChannel = ssChannel.accpet();
//切换非阻塞模式
sChannel.configureBlocking(false);
//将该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
//获取当前选择器上“读就绪”状态的通道
SocketCahnnel sChannel = (SocketChannel) sk.channel();
//读取数据
ByteBuffer buf = ByteBuffer.alocat(1024);
int len = 0;
while((len = sChannel.read(buf)) > 0) {
buf.flip();
sout(new String(buf.array(), 0 ,len));
buf.clear();
}
}
//取消选择键 SelectionKey
it.remove();
}
}
SocketChannel sChannel = socketChannel.opan(new InetSocketAddress("127.0.0.1", 9999));
sChannel.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner scan = new Scanner(System.in);
while(scan.hasNext()) {
String str = scan.nextLine();
buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis()) + "\n" + str).getBytes());
buf.filp();
sChannel().wriet(buf);
buf.clear();
}
需求:服务端接收客户端的连接请求,并接收多个客户端发送过来的事件。
地址:https://gitee.com/xuyu294636185/JAVA_NIO_DEMO.git
需求:NIO非阻塞网路编程实现多人群聊
BIO | NIO | NIO |
---|---|---|
Socket | SocketChannel | AsynChronousScoketChannel |
ServerSocket | ServerSocketChannel | AsynchronousServerSocketChannel |
与NIO不同,当进行读写操作时,只须直接调用API的read或write异步方法,当有流可读时,操作系统会将可读流传入read缓冲区,当write方法传递的流写完,操作系统主动通知应用程序。 |