一.NIO
1.作用:传输数据
2.概念
- 同步:一个对象或是一段逻辑在一段时间段内只能被一个线程占用。eg:InputStream、OutputStream、Reader、Writer
- 异步:一个对象或一段逻辑在一段时间段内允许被多个线程抢占
- 阻塞:线程在获取到结果之前会持续等待,既不往下执行也不报错
- 非阻塞:线程无论是否获取结果,都会继续往下执行或是报错
3.JDK中IO的分类 - BIO-BlockingIO-同步阻塞式IO
- NIO-NewIo-在jdk1.4中出现-NonBlockingIo-同步非阻塞式IO----tomcat8之后使用的是NIO进行通信(之前是BIO)
- AIO-AsynchronousIo-异步非阻塞式IO,在JDK1.7中出现-AIO本身是在NIO的基础上进行改进,因此称之为NIO.2
4.在现在开发中,如果需要进行大量的短任务,那么使用的是NIO;如果需要进行长任务,会使用BIO
5.BIO的缺点: - 阻塞:阻塞必然会导致效率降低
- 一对一连接:每次客户端发起了连接,服务器端都需要产生一个请求去处理这个连接。如果有大量的客户端发起连接,那么就意味着服务器端需要产生大量线程去处理请求。服务器端的线程如果过多,导致服务器的卡顿甚至崩溃
- 无效连接:在BIO中,无法处理无效的空连接。如果有大量的无效连接产生,就会大量占用服务器端的线程。此时依然可能会导致服务器的卡顿甚至崩溃
6.NIO的组件:Buffer、Channel、Selector
二.Buffer-缓冲区
1.作用:临时缓存区
2.Buffer底层是基于数组存储数据,只能存储基本类型的数据,Buffer针对八种基本类型,提供7个实现类(byte、short、int、long、char、float、double),唯独没有boolean
3.重要位置:capacity>=limit>=position
- capacity:容量位,用于标记缓冲区的容量,只想缓冲区的最后一位。容量位不可改变
- position:操作位。类似于数组中的下标,position指向哪一位就会读写哪一位。每次读写完成之后,position就会自动向后挪一位。
- limit:限制位。用于限定position所能达到的最大下标的。当limit和position重合时候,表示所有的数据都已经被读写完成。在缓冲区刚创建时,limit和capacity重合
4.创建Buffer对象
package nio.buffer;
import java.nio.ByteBuffer;
public class ByteBufferDemo2 {
public static void main(String[] args) {
//byte [] arr=new byte[n];-数据未知
//ByteBuffer buffer= ByteBuffer.allocate(10);
//数据已知
//byte[] arr={n1,n2...}
ByteBuffer buffer=ByteBuffer.wrap("hello".getBytes());
//buffer.put("a".getBytes());
//获取底层对应的字节数组
//如果改变数组中的数据,缓冲区的数据也会跟着改变
byte[] array = buffer.array();
array[0]=97;
System.out.println(new String(array));
}
}
5.操作
package nio.buffer;
import java.nio.ByteBuffer;
public class ByteBufferDemo {
public static void main(String[] args) {
//构建ByteBuffer对象
//参数实际上是指定底层的字节数组的容量
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//获取容量位
System.out.println(byteBuffer.capacity());//10
//获取操作位
System.out.println(byteBuffer.position());//0
//获取限制位
System.out.println(byteBuffer.limit());//10
//添加数据
byteBuffer.put("abc".getBytes());
byteBuffer.put("def".getBytes());
//需要先将limit挪动到position上
byteBuffer.limit(byteBuffer.position());
//再将position归零
byteBuffer.position(0);
//上述两步操作,称之为翻转缓冲区,等价于下面一步
//byteBuffer.flip();
//while(byteBuffer.position()<byteBuffer.limit())
//等价于下面一步
while (byteBuffer.hasRemaining()){
byte b = byteBuffer.get();
System.out.print((char)b+" ");
}
System.out.println();
//获取操作位
System.out.println(byteBuffer.position());
//挪动操作位
byteBuffer.position(0);
//获取数据
byte b = byteBuffer.get();
System.out.println(b);
}
}
三、Channel-通道
1.作用:传输数据
2.Channel可以实现双向传输
3.Channel默认实阻塞的,但是可以手动设置为非阻塞
4.常见的channel:
- 文件:FileChannel
1)通过FileInputStream获取Channel
package nio.channel;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelDemo1 {
public static void main(String[] args) throws IOException {
//读取文件
//获取通道-通过FileInputStream获取Channel,所以这个channel只能读不能写
//获取channel对象
FileChannel channel=new FileInputStream("a.txt").getChannel();
//构建ByteBuffer用于存储对象
ByteBuffer buffer= ByteBuffer.allocate(1024);
//读取数据
channel.read(buffer);
//解析数据
System.out.println(new String(buffer.array(),0,buffer.position()));
//关流
channel.close();
}
}
2)通过FileOutputStream获取通道
package nio.channel;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelDemo2 {
public static void main(String[] args) throws IOException {
//通过FileOutputStream获取通道,只写不读
FileChannel fc=new FileOutputStream("text.txt").getChannel();
//写出数据
fc.write(ByteBuffer.wrap("hello".getBytes()));
//关流
fc.close();
}
}
3)可读可写
package nio.channel;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelDemo3 {
public static void main(String[] args) throws IOException {
//r--只读方式
//rw--读写方式
//rwd--读写方式,不同于rw的地方在于,每次写入的数据必须直接更新到磁盘中而不是内存中
//RandomAccessFile将整个文件看作是一个大型的字节数组
//在RandomAccessFile中,每次读写完成之后,下标会自动后挪
RandomAccessFile raf=new RandomAccessFile("a.txt","rw");
//获取channel
FileChannel channel = raf.getChannel();
//读取数据
ByteBuffer buffer=ByteBuffer.allocate(3);
channel.read(buffer);
System.out.println(new String(buffer.array(),0,3));
//指定在文件中的写入位置
raf.seek(40);
//写出数据
channel.write(ByteBuffer.wrap("abc20".getBytes()));
//关流
channel.close();
}
}
- UDP:DatagramChannel
- TCP:SocketChannel、ServerSocketChannel
客户端
package nio.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) throws IOException {
//开启客户端通道
SocketChannel sc=SocketChannel.open();
//手动设置为非阻塞
sc.configureBlocking(false);
//发起连接-阻塞
//非阻塞:无论是否建立连接,都会继续往下执行
sc.connect(new InetSocketAddress("localhost",8090));
//判断连接是否建立
//如果多次连接没有成功,说明这个连接无法建立
while (!sc.isConnected()){
//试图重新建立连接
//这个方法会自动计数,多次计数之后如果仍然无法建立连接,会抛出异常
sc.finishConnect();
}
//连接建立
sc.write(ByteBuffer.wrap("hello server".getBytes()));
//关闭连接
sc.close();
}
}
服务端
package nio.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class Server {
public static void main(String[] args) throws IOException {
//开启服务器通道
ServerSocketChannel ssc=ServerSocketChannel.open();
//绑定端口
ssc.bind(new InetSocketAddress(8090));
//设置为非阻塞
ssc.configureBlocking(false);
//接受连接
SocketChannel sc= ssc.accept();
//判断是否接受了连接
while (sc==null){
sc=ssc.accept();
}
//连接建立
//读取数据
ByteBuffer buffer=ByteBuffer.allocate(1024);
sc.read(buffer);
//解析数据
System.out.println(new String(buffer.array(),0,buffer.position()));
//关流
sc.close();
}
}
5.利用FileChannel实现“zero-copy"(零拷贝)机制-指的并不是没有数据传输而是没有状态的转化
6.Channel都是面向Buffer进行操作的
四、Selector-多路复用选择器
1.作用:针对事件(客户端和服务端之间能够产生的操作)来进行选择
2.实际生产过程中,会考虑将选择器放在服务器端来设置
3.Selector是面向Channel进行操作的,要求Channel必须是非阻塞的
4.服务端
package nio.selector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class Server {
public static void main(String[] args) throws IOException {
//开启服务端的通道
ServerSocketChannel ssc=ServerSocketChannel.open();
//绑定端口
ssc.bind(new InetSocketAddress(8090));
//设置为非阻塞
ssc.configureBlocking(false);
//获取选择器
//实际生产过程中,将选择器设置为单例的
Selector sel = Selector.open();
//将通道注册到选择器中,指定要注册的事件
ssc.register(sel, SelectionKey.OP_ACCEPT);
//模拟服务器一直处于开启状态
while(true){
//随着服务器一直开启,服务器将会接受到越来越多的请求
//但是不代表这些请求都是有用请求-需要进行选择,选出有用请求
sel.select();
//确定这一些请求,可能会发出的操作:accept/read/writer
Set<SelectionKey> set = sel.selectedKeys();
//遍历集合,根据请求类型做出不同处理
Iterator<SelectionKey> iterator = set.iterator();
while (iterator.hasNext()){
//获取请求类型
SelectionKey key = iterator.next();
if(key.isAcceptable()){
//先从选择器中获取到同奥
ServerSocketChannel sscx =(ServerSocketChannel) key.channel();
//接受请求
SocketChannel sc = sscx.accept();
//设置非阻塞
sc.configureBlocking(false);
//注册可读事件
//sc.register(sel,SelectionKey.OP_READ);
//注册可写事件
//sc.register(sel,SelectionKey.OP_WRITE);
//再注册事件的时候,后注册的时间会覆盖之前的注册的事件
sc.register(sel,SelectionKey.OP_WRITE+SelectionKey.OP_READ);
//sc.register(sel,SelectionKey.OP_WRITE|SelectionKey.OP_READ);
//sc.register(sel,SelectionKey.OP_WRITE^SelectionKey.OP_READ);
}
if(key.isReadable()){
//获取通道
SocketChannel sc=(SocketChannel)key.channel();
//读取数据
ByteBuffer buffer=ByteBuffer.allocate(1024);
sc.read(buffer);
//解析数据
System.out.println(new String(buffer.array(),0,buffer.position()));
//注销可读事件
//key.interestOps()获取当前通道上的所有事件--再从这些事件上扣去可读事件
sc.register(sel,key.interestOps()-SelectionKey.OP_READ);
//sc.register(sel,key.interestOps()^SelectionKey.OP_READ);
}
if(key.isWritable()){
//获取通道
SocketChannel sc=(SocketChannel)key.channel();
//读取数据
ByteBuffer buffer=ByteBuffer.allocate(1024);
sc.read(buffer);
//解析数据
System.out.println(new String(buffer.array(),0,buffer.position()));
//注销可读事件
//key.interestOps()获取当前通道上的所有事件--再从这些事件上扣去可读事件
sc.register(sel,key.interestOps()-SelectionKey.OP_WRITE);
//sc.register(sel,key.interestOps()^SelectionKey.OP_WRITE);
}
//移除相同请求
iterator.remove();
}
}
}
}
客户端
package nio.selector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) throws IOException {
//开启客户端通道
SocketChannel sc=SocketChannel.open();
//发起连接
sc.connect(new InetSocketAddress("localhost",8090));
//写出数据
sc.write(ByteBuffer.wrap("hi for client".getBytes()));
//读取数据
ByteBuffer buffer=ByteBuffer.allocate(1024);
sc.read(buffer);
System.out.println(new String(buffer.array(),0,buffer.position()));
//关流
sc.close();
}
}