NIO编程:
NIO的三个核心部位:Channel(通道),Buffer(缓冲区),Selector(选择器)
NIO和BIO的比较
1:BIO以流的方式处理数据,而NIO以块的方式处理数据,快I/O的效率比流I/O的效率高很多
2:BIO是阻塞,NIO是非阻塞
3BIO基于字节流和字符串流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓存区,或者从缓存区写入通道中,Selector(选择器)用于监听多个通道(比如:连接请求,数据到达等),因此使用单个线程就可以监视多个客户端通道
NIO BIO
面向缓冲区(Buffer) 面向流(Stream)
非阻塞(Non Blocking IO) 阻塞IO(Blocking IO)
选择器(Selectors)
NIO三个组件:
Buffer缓存区:临时储存数据的内存空间,可以立即成一个数组
buffer的基本类型
ByteBuffer,CherBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer
buffer的基本使用
创建一个内存空间
ByteBuffer buf = ByteBuffer.allocate(1024);
可以像Buffer放入数据
buf.put("你好同学".getBytes(StandardCharsets.UTF_8));
在读取buffer之前进行反转
buf.flip();//反转
反转之后才可以读取数据
byte[] result = new byte[buf.limit()];//使用limit创建读取数据的数组,不能超过limit
buf.get(result);
设置可以重复读取
buf.rewind();
需要重新读取数据的时候最好清理一下原有的数据
buf.clear();
Channel通道 :
channel的主要作用就是从buffer中读取或者写入数据
channel的使用和流有点类似
通道即可读,也可写,流可以分成写入和输出流
通道是可以设置成非阻塞的,流是阻塞的
通道主要是从buffer中读取数据
各种通道
FileChannel:文件通道
DatagramChannel:UDP通道
SocketChannel:一般的网络通信通道
ServerSocketChannel:专门监听连接Socket通道
文件通道的基本使用
实现文件写入数据
File file = new File("a.txt");
String data ="测试数据";
//创建一个buffer,将数据放入到buffer中
ByteBuffer buffer = ByteBuffer.allocate(1024);
//将数据先放入到buffer中
buffer.put(data.getBytes(StandardCharsets.UTF_8));
//马上翻转
buffer.flip();
//通过文件打开输出流
FileOutputStream fos = new FileOutputStream(file);
//通过流获取一个通道
FileChannel channel = fos.getChannel();
//将buffer中的数据写入到文件中
channel.write(buffer);
//关闭通道
channel.close();
从文件中读取数据
File file = new File("a.txt");
//通过流打开channel
FileInputStream fis = new FileInputStream(file);
FileChannel fileChannel = fis.getChannel();
//创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//将文件中的数据,通过channel读取到buffer中
fileChannel.read(buffer);
buffer.flip();
//将buffer中的数据输出
System.out.println(new String(buffer.array(),0,buffer.remaining()));
文件的复制功能
//将a复制到b
File afile = new File("a.txt");
File bfile = new File("b.txt");
//打开流
FileInputStream fis = new FileInputStream(afile);
FileOutputStream fos = new FileOutputStream(bfile);
//打开通道
FileChannel isChannel = fis.getChannel();
FileChannel osChannel = fos.getChannel();
//创建一个buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
int flag =0;
while(flag !=-1){
buffer.clear();//将缓存重置成初始状态
flag = isChannel.read(buffer);
buffer.flip();//反转
osChannel.write(buffer);
}
isChannel.close();
osChannel.close();
fis.close();
fos.close();
System.out.println("复制成功");
Selector选择器
是整个NIO的核心,尤其实现了多路复用的功能
将所有的Channel注册到selector上,由selector遍历所有出现了数据变化的channel来执行操作
实现socket连接的操作
服务端:
public static void main(String[] args) throws IOException {
//创建一个Selector
Selector selector = Selector.open();
//将需要监听的channel注册到selector之上
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//让ServerSocketChannel绑定端口
serverSocketChannel.bind(new InetSocketAddress(8989));
//设置channel是非阻塞
serverSocketChannel.configureBlocking(false);
//主要用户接收连接的用户的
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//接收进入到用户
while(selector.select()>0){//会阻塞代码
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("有人进来了");
}
}
客户端直接连接服务端
public static void main(String[] args) throws IOException {
//打开连接服务器的通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8989));
}
单项的消息发送
服务器端通过selector判断channel变化情况
public static void main(String[] args) throws IOException {
//创建一个Selector
Selector selector = Selector.open();
//将需要监听的channel注册到selector之上
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//让ServerSocketChannel绑定端口
serverSocketChannel.bind(new InetSocketAddress(8989));
//设置channel是非阻塞
serverSocketChannel.configureBlocking(false);
//主要用户接收连接的用户的
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动了");
//接收进入到用户
while(selector.select()>0){//会阻塞代码
//每个有监听的channel发生变化的时候,会将所有发送的状态放入到一个集合中
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey selectionKey = it.next();//获取当前channel变化的key
if(selectionKey.isAcceptable()){//判断是有新的连接了
System.out.println("有人进来了");
//获取连接的channel
SocketChannel socketChannel = serverSocketChannel.accept();//非阻塞
socketChannel.configureBlocking(false);
//将channel注册到selector
socketChannel.register(selector,SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){//判断是读取状态
//获取当前key对应的channel
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);//非阻塞
buffer.flip();
System.out.println(new String(buffer.array(),0,buffer.remaining()));
}
}
//移除当前key的迭代器
it.remove();
}
}
客户端发送请求
public static void main(String[] args) throws IOException {
//打开连接服务器的通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8989));
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while (true){
String msg = scanner.next();
//将msg放入到buffer中
buffer.put(msg.getBytes(StandardCharsets.UTF_8));
buffer.flip();
//通过channel将数据发送
socketChannel.write(buffer);
buffer.clear();//重置游标
}
}
小结:
BIO:
阻塞编程,代码必须一行一行执行,简单好操作
在处理并发问题的情况下,需要被每个连接都安排一个线程,极大的影响服务器的性能
适用于线程固定的,对服务器资源要求较高的场景
NIO
非阻塞编程,主要使用的是多路复用的概念,让每个线程通信消息放入到buffer缓存中,然后连接buffer缓存的channel注册到selector上
selector进行要轮询状态,会监听所有的注册的channel变化情况,一但有发生变化就对其进行处理,
selector在监听的过程中还是会阻塞
适合线程伸缩大,并发高,对资源要求不多的情况下,处理业务时间要短
AIO
异步非编程,在NIO的基础上,并不是通过轮询/监听的方式,而是让buffer发送变化之后,主动通知系统进行处理
将缓存的数据储存在操作系统 的内核中,处理完毕之后在通知用户线程
适合对资源要求高,处理业务时间长的操作,比如文件上传