NIO 输入输出
NIO 是java14 API 提供的一种新输入输出流,一套用于标准IO的文件读写,一套用于网络编程。
1. NIO 与IO 的区别
- IO流以字节流输入输出,一次以一个字节进行数据操作,效率慢; NIO以块的方式处理数据,面向缓冲区,一次产生或消费一个数据块,速度快
- IO流是阻塞IO,当客户端连接会处于阻塞状态;NIO是非阻塞IO,可以设置属性进行多路复用处理
- NIO可以通过选择器Selctors进行多路复用
2. 核心组件
- ByteBuffer 数据缓冲区
- 对数据进行处理,本质是一个数组,用来保存类型不同的数据
- Buffer是一个抽象类,有很多子类,最常用的是ByteBuffer
- 创建(静态方法调用)
- (最常用)在堆中创建缓冲区 allocate(int capacity)
- 在系统内存创建 allocateDirect(int capacity)
- 普通数组创建 wrap(byte[] arr)
- 常用方法
- 添加元素,写处理 put(byte b)
- 获取元素,读处理 get(byte b)
- 将缓冲区转换成数组 对象名.array()
- 切换读模式,将position=0;limit=position flip()
- 清空数据 clear(),数据只是被遗忘,但仍存在在缓冲区,不能在通过下标获取元素,position=0;limit=capacity
- 重要属性
- capacity 容量,缓冲区能容纳的最大数据元素数量,一经设定不可改变
- limit 界限,缓冲区可以操作的数据大小,limit后面的元素都不可操作,初始化为capacity的值
- position 位置,下一个要被操作的数据位置,初始化为0, put 和get 操作都会影响limit和position的值
- mark 标记,初始化为-1,记录上一次position的位置
package task04; import java.io.FileInputStream; import java.nio.ByteBuffer; import java.util.Arrays; public class ByteBufferTest { public static void main(String[] args) { //创建 ByteBuffer bf1=ByteBuffer.allocate(10); ByteBuffer bf2=ByteBuffer.allocateDirect(20); byte[] bytes=new byte[1024]; ByteBuffer bf3=ByteBuffer.wrap(bytes); //属性值 System.out.println(bf1.capacity()); //10 System.out.println(bf1.limit());//10 System.out.println(bf1.position());//0 System.out.println(bf1.mark());//java.nio.HeapByteBuffer[pos=0 lim=10 cap=10] System.out.println("-----------"); //添加获取元素 byte[] arr=new byte[]{1,2,3,4,5}; bf1.put(arr); System.out.println(bf1.get());//0 因为此时position在5的位置 bf1.flip(); System.out.println(bf1.get());//1,在ByteBuffer 下标为0 System.out.println(bf1.get(2));//3,在ByteBuffer 下标为2,这里并不会设置position的值 System.out.println(bf1.get());//2 position=2 System.out.println(bf1.get());//3 position=2 System.out.println("-----------"); //属性值 System.out.println(bf1.capacity());//10 System.out.println(bf1.limit());//6 System.out.println(bf1.position());//3 } }
- Channel通道
- 用于传输数据,与数据处理无关,双向传输,在多路复用时要进行注册操作
- Channel接口通过以下实现类实现
- FileChannel:用于读取、写入、映射和操作文件的通道。
package task04; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.FileChannel; public class FileTest { public static void main(String[] args) throws IOException { //建立输入输出流 FileInputStream fis=new FileInputStream("D:\\a.txt"); FileOutputStream fos=new FileOutputStream("D:\\b.txt"); //获取连接 FileChannel fc1=fis.getChannel(); FileChannel fc2=fos.getChannel(); //缓冲区 ByteBuffer bf=ByteBuffer.allocate(1024); //读取 while (fc1.read(bf)!=-1){ bf.flip(); fc2.write(bf); bf.clear(); } //关流 fis.close(); fos.close(); } }
- DatagramChannel:通过 UDP 读写网络中的数据通道。
- SocketChannel:通过 TCP 读写网络中的数据。
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来 的连接都会创建一个SocketChannel
- 网络通信常用open方法生成实例对象
- SocketChannel:通过connect方法建立连接
- ServerSocketChannel:通过bind方法绑定端口号,accept方法建立连接
- 设置非阻塞方式:configureBlocking(false)
package task04; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; //客户端 public class ClientTest { public static void main(String[] args) throws Exception { SocketChannel sc = SocketChannel.open(); System.out.println("建立连接"); sc.connect(new InetSocketAddress("127.0.0.1",9999)); System.out.println("成功连接服务端"); //创建缓冲区 ByteBuffer bf=ByteBuffer.allocate(1024); bf.put("哈哈哈".getBytes()); bf.flip(); sc.write(bf); //关闭 sc.close(); } }
package task04; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; //服务器端 public class ServerTest { public static void main(String[] args) throws Exception { ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.bind(new InetSocketAddress(9999)); System.out.println("等待连接"); //设置非阻塞 ssc.configureBlocking(false); while (true){ SocketChannel accept = ssc.accept(); //阻塞IO if(accept!=null){ System.out.println("连接成功"); //读取数据 ByteBuffer bf=ByteBuffer.allocate(1024); int read = accept.read(bf); System.out.println(new String(bf.array(),0,read)); accept.close(); ssc.close(); break; }else { System.out.println("嘻嘻嘻"); Thread.sleep(2000); } } } }
- Selector 选择器,多路复用器
- 一个选择器可以同时监听多个服务器端口,帮多个服务器端口同时等待客户端访问
- Selector是channel通道的多路复用器,同时监控多个通道的IO状况,Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。Selector 允许单线程处理多个Channel。仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道,这样会大量的减少线程之间上下文切换的开销。
- 可选择通道SelectableChannel ,继承了抽象类SelectableChanne的Channel可以被复用,否则不能
- 通道和选择器之间的关系,使用注册的方式完成。SelectableChannel可以被注册到Selector对象上,在注册的时候,需要指定通道的哪些操作,是Selector感兴趣的
- Channel.register(Selector sel,int ops)方法,将一个通道注册到一个选择器时。
- 第一个参数:指定通道要注册的选择器是谁
- 第二个参数:指定选择器需要查询的通道操作
- 可读 : SelectionKey.OP_READ
- 可写 : SelectionKey.OP_WRITE
- 连接 : SelectionKey.OP_CONNECT
- 接收 : SelectionKey.OP_ACCEPT
- select() :选择器等待客户端连接的方法
- selectedKeys() :选择器会把被连接的服务端对象放在Set集合中,这个方法就是返回一个Set集合
- Channel.register(Selector sel,int ops)方法,将一个通道注册到一个选择器时。
- 使用selector
- 创建Selector,Selector对象是通过调用静态工厂方法open()来实例化的
- 创建channel,与Selector一起使用时,Channel必须处于非阻塞模式下,否则将抛出异常iIlegalBlockingModeException
- 设置非阻塞
- 绑定连接
- 制定监听事件
- 通过Selector的 select() 方法,可以查询出已经就绪的通道操作,这些就绪的状态集合,包存在一个元素是SelectionKey对象的Set集合中。select()方法返回的int值,表示有多少通道已经就绪
package task04; 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 SelectServer { public static void main(String[] args) throws IOException { //创建服务端对象 ServerSocketChannel ssc1 = ServerSocketChannel.open(); ServerSocketChannel ssc2 = ServerSocketChannel.open(); ServerSocketChannel ssc3 = ServerSocketChannel.open(); //设置非阻塞 ssc1.configureBlocking(false); ssc2.configureBlocking(false); ssc3.configureBlocking(false); //创建服务端对象 ssc1.bind(new InetSocketAddress(9999)); ssc2.bind(new InetSocketAddress(8888)); ssc3.bind(new InetSocketAddress(7777)); //创建选择器对象 Selector selcetor = Selector.open(); //两个服务器都要交给选择器来管理 ssc1.register(selcetor, SelectionKey.OP_ACCEPT); ssc2.register(selcetor, SelectionKey.OP_ACCEPT); ssc3.register(selcetor, SelectionKey.OP_ACCEPT); while (selcetor.select() > 0) { //获取集合 //selectedKeys() :返回集合,集合作用存放的是被连接的服务对象的key Set<SelectionKey> selectionKeys = selcetor.selectedKeys(); // 6、采用轮询的方式,查询获取"准备就绪"的注册过的操作 // 7、获取当前选择器中所有注册的选择键(“已经准备就绪的操作”) Iterator<SelectionKey> iterator = selectionKeys.iterator(); // 8、获取"准备就绪"的事件 while (iterator.hasNext()) { SelectionKey next = iterator.next(); // 9、获取ServerSocketChannel ServerSocketChannel channel = (ServerSocketChannel) next.channel(); // 10、接受客户端发来的数据 SocketChannel accept = channel.accept(); ByteBuffer bf = ByteBuffer.allocate(1024); // 11、读取数据 int length = 0; while ((length = accept.read(bf)) != -1) { bf.flip(); System.out.println(new String(bf.array(), 0, length)); bf.clear(); } accept.close(); } // 12、移除选择键 iterator.remove(); } // 13、关闭连接 ssc1.close(); ssc2.close(); ssc3.close(); } }
package task04; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; public class SelectClient { public static void main(String[] args) throws IOException { //创建客户端 SocketChannel sc=SocketChannel.open(); //指定要连接的服务器ip和端口 sc.connect(new InetSocketAddress("127.0.0.1",9999)); //创建缓冲输出 ByteBuffer bf=ByteBuffer.allocate(1024); //给数组添加数据 bf.put("hahaha".getBytes()); //切换 bf.flip(); //输出数据服务端 sc.write(bf); //关闭资源 sc.close(); } }
浙公网安备 33010602011771号