今天学习了NIO,Selector选择器和AIO,是概念也多代码也多今天学习会比较懵,做好笔记!!
时间: 2022/5/1
NIO
之前的IO都是BIO也就是阻塞IO
NIO是非阻塞IO,非阻塞可以提高效率
Buffer缓冲数组
Buffer是NIO里面的缓冲数组,他可以代替之前的普通数组完成IO操作
ByteBuffer* 代替之前的byte[]
CharBuffer char[]
DoubleBuffer double[]
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
ByteBuffer的创建方式
- 在堆中创建缓冲区:allocate(int capacity)*
- 在系统内存创建缓冲区:allocateDirect(int capacity)
- 通过普通数组创建缓冲区:wrap(byte[] arr)
import java.nio.ByteBuffer;
public class Demo01 {
public static void main(String[] args) {
//ByteBuffer的三种创建方式
//- 在堆中创建缓冲数组:allocate(int capacity)*
ByteBuffer buffer1 = ByteBuffer.allocate(10);
//- 在系统内存创建缓冲数组:allocateDirect(int capacity)
ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);
//- 通过普通数组创建缓冲数组:wrap(byte[] arr)
byte[] brr = new byte[10];
ByteBuffer buffer3 = ByteBuffer.wrap(brr);
}
}
常用方法
-
put(byte b) : 给数组添加元素
-
get() :获取一个元素
-
capacity() :获取容量,容量是一个定值。
-
limit() : 限制。limit可以指定一个索引,从limit开始后面的位置不能操作。
-
position() :位置。位置代表将要存放的元素的索引,每次添加元素position会往后移动。位置不能小于0,也不能大于limit. position只会自动完后走,不会自动往前走
-
clear():还原缓冲区的各个指针。 只移动指针不会清空数据。
- 将position设置为初始状态
- 将限制limit设置为初始状态
-
flip():切换读写状态。在读写数据之间要调用这个方法。
- 将limit设置为当前position位置
- 将当前position设置为初始位置
Channel通道
Channel是什么,有哪些分类
Channel叫通道,通道相当于是双向IO流,之前的IO流有输入和输出两种
Channel既能读取也能写出
通道的读取要用缓冲数组
分类:
- FileChannel:从文件读取数据的
- DatagramChannel:读写UDP网络协议数据
- SocketChannel:读写TCP网络协议数据
- ServerSocketChannel:可以监听TCP连接
FileChannel基本使用
public static void main(String[] args) throws IOException {
//创建文件输入流
FileInputStream f1 = new FileInputStream("C:\\Users\\Administrator\\Desktop\\3.jpg");
//创建文件输出流
FileOutputStream f2 = new FileOutputStream("E:\\3.jpg");
//IO流可以获取通道对象
FileChannel c1 = f1.getChannel();
FileChannel c2 = f2.getChannel();
//创建缓冲数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(c1.read(buffer) != -1){
buffer.flip();
c2.write(buffer);
buffer.clear();
}
//关流
f1.close();
f2.close();
}
网络编程收发信息
//客户端
public static void main(String[] args) throws IOException {
//创建客户端对象
SocketChannel sc = SocketChannel.open();
//指定要连接的服务器的ip和端口
sc.connect(new InetSocketAddress("127.0.0.1",8888));
String s = "你好呀~";
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(s.getBytes());
buffer.flip();
//输出数据
sc.write(buffer);
//关闭资源
sc.close();
//服务端
public static void main(String[] args) throws IOException {
//创建服务器对象
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定端口
ssc.bind(new InetSocketAddress(8888));
//连接客户端
SocketChannel sc = ssc.accept();
//接受数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = sc.read(buffer);
//把数组转成字符串用于输出
byte[] bytes = buffer.array();
String s = new String(bytes,0,len);
System.out.println(s);
//关闭资源
sc.close();
ssc.close();
}
accept阻塞问题
//服务端
public static void main(String[] args) throws IOException, InterruptedException {
//创建服务器对象
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定端口
ssc.bind(new InetSocketAddress(8888));
//设置非阻塞连接
ssc.configureBlocking(false);
//循环
while(true) {
//连接客户端
SocketChannel sc = ssc.accept(); //非阻塞
//判断是否有客户端访问
if(sc != null) {
//接受数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = sc.read(buffer);
//把数组转成字符串用于输出
byte[] bytes = buffer.array();
String s = new String(bytes, 0, len);
System.out.println(s);
//关闭资源
sc.close();
ssc.close();
//
break;
}else{
//没有访问,就去执行其他代码
//以后可能有很多代码需要执行
//现在我们没有其他代码,睡觉2秒钟模拟可以执行别的diam
System.out.println("正在执行别的代码...");
Thread.sleep(2000);
}
}
}
Selector选择器
多路复用的概念
Selector(选择器)是Java NIO中能够检测一到多个Channel通道的工具
一个选择器可以同时监听多个服务器端口, 帮多个服务器端口同时等待客户端的访问。
Selector介绍
select() :选择器等待客户端连接的方法
阻塞效果:
1.在开始没有客户访问的时候是阻塞的
2.在有客户来访问的时候会变成非阻塞的
3.如果客户的访问被服务器接收之后,又会恢复成阻塞的
selectedKeys() :选择器会把被连接的服务端对象放在Set集合中,这个方法就是返回一个Set集合
keys() :返回一个set集合,集合里面是所有被管理的服务器
Selector方法的演示
public static void main(String[] args) throws IOException {
//创建三个服务器
ServerSocketChannel ssc1 = ServerSocketChannel.open();
ssc1.bind(new InetSocketAddress(7777));
ServerSocketChannel ssc2 = ServerSocketChannel.open();
ssc2.bind(new InetSocketAddress(8888));
ServerSocketChannel ssc3 = ServerSocketChannel.open();
ssc3.bind(new InetSocketAddress(9999));
//如果要使用Selector必须要让服务器设置非阻塞
ssc1.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
//创建选择器对象
Selector sel = Selector.open();
//让select去管理服务器对象
ssc1.register(sel, SelectionKey.OP_ACCEPT);
ssc2.register(sel, SelectionKey.OP_ACCEPT);
ssc3.register(sel, SelectionKey.OP_ACCEPT);
//获取集合
Set<SelectionKey> keys = sel.keys();
Set<SelectionKey> skeys = sel.selectedKeys();
System.out.println("被管理的个数:" + keys.size()); //3
System.out.println("被访问的个数:" + skeys.size()); //0
//让选择器等待客户端
sel.select();
System.out.println("被管理的个数:" + keys.size()); //3
System.out.println("被访问的个数:" + skeys.size()); //1
}
Selector管理多个ServerSocketChannel的问题
被连接的服务器对象会放在Set集合中,但是使用完之后并没有从集合中删除。
所以在使用之后,我们需要自己把对象从集合中删除, 因为使用集合对象删除有可以出现并发修改异常
所以我们使用迭代器对象删除元素
public static void main(String[] args) throws IOException, InterruptedException {
//创建三个服务器
ServerSocketChannel ssc1 = ServerSocketChannel.open();
ssc1.bind(new InetSocketAddress(7777));
ServerSocketChannel ssc2 = ServerSocketChannel.open();
ssc2.bind(new InetSocketAddress(8888));
ServerSocketChannel ssc3 = ServerSocketChannel.open();
ssc3.bind(new InetSocketAddress(9999));
//如果要使用Selector必须要让服务器设置非阻塞
ssc1.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
//创建选择器对象
Selector sel = Selector.open();
//让select去管理服务器对象
ssc1.register(sel, SelectionKey.OP_ACCEPT);
ssc2.register(sel, SelectionKey.OP_ACCEPT);
ssc3.register(sel, SelectionKey.OP_ACCEPT);
//获取集合
Set<SelectionKey> keys = sel.keys();
Set<SelectionKey> skeys = sel.selectedKeys();
while(true) {
System.out.println("被管理的个数:" + keys.size()); //3
System.out.println("被访问的个数:" + skeys.size()); //0
//让选择器等待客户端
sel.select();
System.out.println("被管理的个数:" + keys.size()); //3
System.out.println("被访问的个数:" + skeys.size()); //1
Thread.sleep(2000);
//有客户访问了某个服务器,那么被访问的服务器编号就放在集合中
//for (SelectionKey skey : skeys) {
Iterator<SelectionKey> it = skeys.iterator();
while(it.hasNext()){
SelectionKey skey = it.next();
//根据key能找到对应的服务器对象
SelectableChannel channel = skey.channel();
//向下转型
ServerSocketChannel ssc = (ServerSocketChannel) channel;
//接受客户端的数据
SocketChannel sc = ssc.accept();
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = sc.read(buffer);
String s = new String(buffer.array(),0,len);
System.out.println(s);
//使用集合对象删除元素并发修改异常!!
//需要使用迭代器对象删除!!!
it.remove();
}
}
}
AIO
AIO是异步非阻塞的IO,是NIO的升级版本,更加优化
AIO异步非阻塞-连接accept()
public static void main(String[] args) throws IOException {
// BIO ServerSocket
// NIO ServerSocketChannel
// AIO AsynchronousServerSocketChannel
//创建服务器对象
AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
assc.bind(new InetSocketAddress(8888));
//异步非阻塞连接客户端!!
//A attachment
//CompletionHandler com
assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
//这个方法代表成功,如果连接成功就会自动调用这个方法
public void completed(AsynchronousSocketChannel sc, Object attachment) {
System.out.println("连接成功");
}
@Override
//这个方法代表失败,如果连接失败就会自动调用这个方法
public void failed(Throwable exc, Object attachment) {
System.out.println("连接失败");
}
});
//我们下面其实会有无数的代码,异步非阻塞在前面不会有任何停留
//直接就会去执行下面的代码
//现在我们写while(true)循环代表下面有无数代表程序不结束
while (true){
}
}
AIO异步非阻塞-读read()
public static void main(String[] args) throws IOException {
// BIO ServerSocket
// NIO ServerSocketChannel
// AIO AsynchronousServerSocketChannel
//创建服务器对象
AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
assc.bind(new InetSocketAddress(8888));
//异步非阻塞连接客户端!!
//A attachment
//CompletionHandler com
System.out.println("a");
assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
//这个方法代表成功,如果连接成功就会自动调用这个方法
public void completed(AsynchronousSocketChannel sc, Object attachment) {
System.out.println("b");
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer, null, new CompletionHandler<Integer, Object>() {
@Override
//如果读取数据成功会自动执行这个方法
public void completed(Integer len, Object attachment) {
System.out.println("c");
String s = new String(buffer.array(),0,len);
System.out.println(s);
}
@Override
//如果读取失败会自动执行这个方法
public void failed(Throwable exc, Object attachment) {
}
});
System.out.println("d");
}
@Override
//这个方法代表失败,如果连接失败就会自动调用这个方法
public void failed(Throwable exc, Object attachment) {
System.out.println("连接失败");
}
});
System.out.println("e");
//我们下面其实会有无数的代码,异步非阻塞在前面不会有任何停留
//直接就会去执行下面的代码
//现在我们写while(true)循环代表下面有无数代表程序不结束
while (true){
}
}
AIO客户端演示【了解】
public static void main(String[] args) throws IOException {
//客户端
AsynchronousSocketChannel asc = AsynchronousSocketChannel.open();
//指定连接服务端的ip和端口
asc.connect(new InetSocketAddress("127.0.0.1", 8888), null, new CompletionHandler<Void, Object>() {
//连接成功之后自动会执行的方法
@Override
public void completed(Void result, Object attachment) {
System.out.println("连接服务器成功!!");
}
//连接失败之后自动执行的方法
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("连接服务器失败");
}
});
while(true){
}
}
总结
今天学的是IO流的高级应用,后面不太会写这些代码…除非你到了公司你直接就是组长…