一、NIO
1.定义
-
NIO是面向缓冲区的流, 我们将数据和缓冲区通过一根管道连接起来,然后我们对缓冲区中的数据进行操作了
-
NIO是双向的流, 也就是说,这个缓冲区既可以存储又可以输出
-
NIO是非阻塞的, 通道建立之后,就会自动的读或取了,这就意味着一个线程可以管理多个流通道
-
NIO在解析数据的时候非常麻烦, 但适用于高并发小流量的场景,如聊天服务器
二、Buffer(缓冲区)
1.定义
因为NIO主要就是对缓冲区进行操作,所以,这个至关重要
2.分类
-
除了boolean外的基本数据类型,都提供了对应的缓冲区
-
ByteBuffer , CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer
-
常用的就是ByteBuffer , CharBuffe
3.重要属性
-
capacity : 缓冲区容量, 表示缓冲区中最大存储数据的容量, 一旦声明,不能改变
-
limit : 界限, 表示缓冲区中可以操作数据的大小(limit和limit后的数据不能进行读写)
-
position : 位置, 表示缓冲区中正在操作数据的位置
-
mark : 标记, 可以标记position的位置 ,可以使用reset()方法,将position回到标记位置
4.常用方法
-
allocate(int capacity) : 指定缓冲区的大小
-
put() : 存储数据
-
get() : 获取数据
-
flip() : 切换成输出模式
-
rewind() : 切换回输出模式的初始化位置,重复读
-
clear() : 清空缓冲区, 所有标记回到最初状态, 其中的数据并没有被清空,只是处于"被遗忘"状态
-
mark() : 标记position的位置
-
reset() : 将position回到标记的位置
5.演示
public static void main(String[] args) throws Exception {
//创建指定容量的字节缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
System.out.println("..........put.........");
//添加
buffer.put("abc".getBytes());
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//切换成输出模式 position位置归0 limit移动到position原来的位置
System.out.println("..........flip.........");
buffer.flip();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//获取当前position位置的值 position位置+1
System.out.println("..........get.........");
byte b = buffer.get();
System.out.println(b);
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//切换回输出模式的初始化位置,重复读
System.out.println("..........get.........");
buffer.rewind();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//清空缓冲区,一切还原,为再次写入做准备
System.out.println("..........get.........");
buffer.clear();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//表示postion的位置 使用reset()将position的位置回归到mark标记位置
System.out.println("..........mark()和reset().........");
buffer.put("abc".getBytes());//添加三个字节
buffer.mark();
buffer.put("df".getBytes());
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
buffer.reset();
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
}
三、通道(Channel)
1.定义
用于读取、写入、映射和操作文件的通道,可以将程序和数据实体建立连接
java的流都提供了获取通道的方法
Channel是双向的,既可以读又可以写,而流是单向的
Channel可以进行异步的读写
对Channel的读写必须通过Buffer对象
2.常用方法
read(Buffer b):将数据写入到缓冲区
write(Buffer b):从缓冲区输出数据
四、FileChannel(不推荐使用)
1.定义
-
用于读取、写入、映射和操作文件的通道
-
将数据读取存储到缓冲区, 也可以将缓冲区的数据写入到本地
-
这个类无法直接关联到文件,必须通过IO的流进行获取, 预留的方法,但是还没有启用
2.演示
public static void main(String[] args) throws Exception {
ByteBuffer bf = ByteBuffer.allocate(1024);
//从字节数据流中获取文本FileChannel
FileInputStream fis = new FileInputStream("d:\\骑在银龙的背上.mp3");
FileChannel fcr = fis.getChannel();
//从字节输出流中获取文本FileChannel
FileOutputStream fos = new FileOutputStream("d:\\音乐.mp3");
FileChannel fcw = fos.getChannel();
while(fcr.read(bf)!=-1){
bf.flip();
fcw.write(bf);
bf.clear();
}
fcr.close();
fcw.close();
fis.close();
fos.close();
//快速复制
//fcr.transferTo(0, fcr.size(), fcw);
}
五、DatagramChannel
1定义
-
针对面向数据报套接字的可选择通道
-
操作UDP的NIO流
2.演示
接收端
public static void main(String[] args) throws Exception {
//获取DatagramChannel
DatagramChannel channel = DatagramChannel.open();
//创建socket
channel.socket().bind(new InetSocketAddress(9999));
//创建缓冲区
ByteBuffer buf = ByteBuffer.allocate(100);
Scanner scanner = new Scanner(System.in);
while(true){
//清空缓冲区,准备接收数据
buf.clear();
//接收网络数据
channel.receive(buf);
System.out.println(new String(buf.array(),0,buf.position()));
String str = scanner.nextLine();
//情况缓冲区,准备存入数据
buf.clear();
buf.put(str.getBytes());
//将缓冲区切换成输出模式
buf.flip();
//发送数据
channel.send(buf, new InetSocketAddress("127.0.0.1", 6666));
}
}
发送端
public static void main(String[] args) throws Exception {
//创建获取DatagramChannel
DatagramChannel channel = DatagramChannel.open();
//创建socket
channel.socket().bind(new InetSocketAddress(6666));;
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("黑色星期五".getBytes());
SocketAddress socket = new InetSocketAddress("127.0.0.1",9999);
Scanner scanner = new Scanner(System.in);
while(true){
buffer.clear();
String str = scanner.nextLine();
//将数据装入缓冲区
buffer.put(str.getBytes());
//将缓冲区切换为输出模式
buffer.flip();
channel.send(buffer, socket);
//清空缓冲区,为接受数据做准备
buffer.clear();
//接收数据
channel.receive(buffer);
System.out.println(new String(buffer.array(),0,buffer.position()));
}
}
六、SocketChannel和ServerSocketChannel
1、定义
-
对应着TCP协议
-
打开一个SocketChannel并连接到互联网上的某台服务器
-
一个新连接到达ServerSocketChannel时,会创建一个SocketChannel
-
用法和Socket,ServerSocket完全
2.演示
客户端
public static void main(String[] args) throws Exception {
//打开通道
SocketChannel channel = SocketChannel.open();
//建立连接
channel.connect(new InetSocketAddress("127.0.0.1", 9999));
//设置缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("I LOVE YOU".getBytes());
//将缓冲区设置为输出模式
buffer.flip();
channel.write(buffer);
}
服务端
public static void main(String[] args) throws Exception {
//打开通道
ServerSocketChannel channel = ServerSocketChannel.open();
//建立服务端
channel.socket().bind(new InetSocketAddress(9999));
//获取socket
SocketChannel socket = channel.accept();
ByteBuffer bf = ByteBuffer.allocate(1024);
//接收数据
socket.read(bf);
System.out.println(new String(bf.array(),0,bf.position()));
}
七、Selector
1.定义
-
Selector是一个通道管理器
-
我们知道,NIO具有非阻塞的能力, 可以在一个线程内同时执行多个操作, 节省了线程间切换的开销
-
但是, 当启动非阻塞的时候,输入和输出方法就完全独立运行了, 这可能导致读的时候对面还没有把信息发送过来, 写的时候,对方还没有完全准备好
-
所有, 我们使用Selector类对通道进行管理,当某个操作准备好了之后, Selector会提醒我们,这时,我们就可以进行操作了
2.Selector监视的状态分类
-
SelectionKey.OP_CONNECT 连接准备就绪
-
SelectionKey.OP_ACCEPT 客户端已经连接
-
SelectionKey.OP_READ 要读的数据已经准备好
-
SelectionKey.OP_WRITE 可以进行写入了
3.常用方法
-
select() 获取所有已经准备好的通道,仅仅是这次的,上一次调用这个方法获取的通道不算
-
selectedKeys() 获取上一次select()方法获取到的通道
4.编码步骤
public static void main(String[] args) throws Exception {
//打开通道
SocketChannel channel = SocketChannel.open();
//建立连接
channel.connect(new InetSocketAddress("127.0.0.1", 9999));
//将当前通道设置为非阻塞
channel.configureBlocking(false);
//获取通道选择器
Selector selector = Selector.open();
//将通道注册进通道选择器中,这里设置通道选择器需要监视的状态是"可读取"
channel.register(selector, SelectionKey.OP_READ);
//往服务端发送一条数据
ByteBuffer bs = ByteBuffer.allocate(1024);
bs.put("黑色星期五".getBytes());
bs.flip();
channel.write(bs);
//控制循环,时刻检测通道选择器
while(true){
//查看通道选择监视的状态时候有通道符合要求了
//select 方法获取所有符合状态的通道
if(selector.select()>0){
//遍历符合状态的通道
for (SelectionKey key : selector.selectedKeys()) {
//判断当前通道是否可读
if (key.isReadable()) {
//读取内容
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socket = (SocketChannel)key.channel();
int len = socket.read(buffer);
System.out.println(len);
System.out.println(new String(buffer.array(),0,buffer.position()));
//改变通道的需要监视的状态
//key.interestOps(SelectionKey.OP_READ);
}
//将键从已经选择的集合中去除
//这个里获取到的通道都是上一次select()方法已经执行到的,如果不去除的话,下一次调用select()方法就无法获取到了
selector.selectedKeys().remove(key);
}
}
}
}