NIO概述
NIO介绍
旧版IO 流(java.io) : 在读写操作完成之前变成线性阻塞。代码简单、安全,但性能较差。 NIO:支持非阻塞编程,并且有更多的功能。性能提高了,但是代码写起来比较复杂。
概念理解
同步(Synchronization) : 当一个线程正在运行时,其他线程只能等待。异步(Async) : 当一个线程正在运行时,其他线程不必等待。阻塞(Blocking) : 当前任务没有运行,导致后续任务无法运行。 Non-blocking(非阻塞) : 当前任务没有运行,不阻止后续任务运行。
IO流与NIO的区别
NIO是面向缓冲区的,IO是面向流的。 NIO 是非阻塞的,IO 是阻塞的。 NIO 可以使用选择器,但IO 不需要选择器。
NIO组成
Buffer(负责读写数据,类似火车)、Channel(负责传输,类似铁轨) Selector:(选择器,负责调度通道,类似指挥中心) :010 -1010 :010 -1010 : 本质上是等价的。这是常规IO 流中的数组,负责存储和检索数据。但是,它提供对数据的结构化访问,并允许您跟踪系统的读取和写入过程。常用类别:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer
Buffer
容量: 表示缓冲区的最大容量。 limit: 表示剩余(可存储/可读)数量。 position: 表示(保存/可读)位置。 mark: 表示当前位置。四个属性之间的关系:mark=Position=limit=Capacity
介绍
static ByteBuffer allocate(intCapacity)分配一个新的字节缓冲区。 static ByteBuffer allocateDirect(intCapacity) 分配一个新的直接字节缓冲区。 static ByteBuffer Wrap(byte[] array) 将字节数组包装到缓冲区中。
核心属性
获取属性值
Capacity(): 获取缓冲区的最大容量。 limit(): 获取剩余金额(保存/可读)position(): 获取(保存/读取)位置mark(): 标记当前位置。访问数据
put(Xxx[] xxx) 将数据保存到缓冲区。无法写入,因为位置=限制。 get() 获取缓冲区的位置数据并将位置向后移动。位置=限制无法读取。核心方法
flick() 翻转此缓冲区(限制=容量位置,位置=0)并清除读取模式标志。 clear() 清除此缓冲区(限制=容量,位置=0),清除标记,并在写入模式下使用。 rewind() 倒回此缓冲区(位置=0)并清除标记。 reset() 将此缓冲区的位置重置为先前标记的位置(position=mark)。
构造方法(以ByteBuffer为例)
public class Test01Buffer { public static void main(String[] args) { //创建一个新的缓冲区对象(默认为写入模式) ByteBuffer b=ByteBuffer.allocate(10); //写入数据println('====写入模式属性状态===='); System.out.println('====读取模式属性状态===='); out.println('====写入数据===='); b.put(new byte[] ); System.out.println('====写入数据读取===='); b.flip('position--- -----' + b.position() + ',get:' + b.get()); //一般形式Loop //while (b. position()b.limit()){ //System .out.println( 'position---------' + b.position() + ',get:' + b.get()); /} 记录设置前的位置: System.out.println('====.操作位置===='); //位置b.mark('====重置操作位置前获取新数据===='); System.out.println('position------ -' + b.position() + ',get:' + b.get( ));===重置操作位置后===='); System.out.println('====回滚缓冲区前===='); System.out.println('====倒回缓冲区后===='); b.rewind() } //显示参数public static void showProperty(ByteBuffer b) { //容量System.out.println( 'capacity:' + b.capacity()) //可存储的数字System.out.println('limit:' + b.limit ()); System.out.println('position:' + b.position() ); }}
常用方法
演示代码
Channel 被理解为一个流对象,包括写入和读取操作我的理解是。在IO中。通道负责读写,缓冲区负责访问。常见分类:FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel
Channel入门
Channel 可以双向读写,而IO 可以单向读写,并且可以异步,但IO 不支持异步。 Channel读写必须经过buffer对象,IO可以直接通过流读写。
介绍
在IO流FileXXX字节流上提供getChannel()方法来获取FileChannel对象。
FileChannel getChannel() 通过FileXXX 字节流方法获取对象。
Channel与IO流区别
int read(ByteBuffer dst): 将数据读入缓冲区。 int write(ByteBuffer src): 将数据从缓冲区写入指定位置。010 -1010 public class Test02FileChannel { public static void main(String[] args) throws IOException { FileChannel in=new FileInputStream('D:\\image.jpg').getChannel() out=new FileOutputStream('D:\\imageCopy . jpg') ).getChannel(); ByteBuffer b=ByteBuffer.allocate(10); int len=-1; while ((len=in.read(b)) !=-1) { b.flip() ; write (b); b.clear(); } in.close(); }}
构造方法(以FileChannel为例 )
用SocketChannel 和ServerSocketChannel 替换TCP 协议。用于读写TCP网络协议数据来检索对象。 public static SocketChannelopen() 连接到服务器。 boolean connect(SocketAddress Remote) SocketAddress 是一个抽象类,是使用其子类InetSocketAddress 创建的对象。
InetSocketAddress(String ip, int port) 侦听客户端连接。 SocketChannelaccept()
常用方法
ServerSocketChannel 服务器通道。由服务器用来监视TCP 连接并检索对象。 public static ServerSocketChannel open() 绑定端口号ServerSocketChannel binding(SocketAddress local ) :010 -1010 public class Test03ServerByChanner { public static void main(String[] args) throws IOException { //获取服务器通道对象ServerSocketChannel serverSocket=ServerSocketChannel.open() //绑定端口ServerSocketChannel socket=serverSocket.bind(new InetSocketAddress (8888 )); //接收数据System.out.println('服务器开始接收数据.'); -1; while ((len=server.read(buffer)) !=-1) { //翻转缓冲区并读取databuffer.flip(); System.out.println( 'server:' + new String() Buffer.array())); System.out.println('服务器开始反馈数据.'); //从缓冲区中取出数据buffer 并写入客户端server.write(buffer); }}
演示代码
public class Test03ClientByChannel; throws Exception { //获取连接对象。 SocketChannel client=SocketChannel.open() //连接服务器client.connect(new InetSocketAddress('localhost', 8888));发送数据System.out.println('客户端开始发送数据.'); Buffer=ByteBuffer.allocate(1024); //反转缓冲区。读取数据。 buffer.flip(); //从缓冲区中获取数据并将其写入通道。 client.shutdownOutput(); //等待反馈。=- 1; while ((len=client.read(buffer)) !=-1) {buffer.flip(); System.out.println('client: ' + new String(buffer.array()) ); buffer.clear(); } //Client client.close();
ChannelTCP协议编程
介绍
非复用: 服务器必须为每个端口上的每个请求打开一个线程。这样做会降低系统性能。 复用:服务器采用单线程处理多个端口的访问请求,节省CPU资源,提高程序执行效率。在高并发情况下也有明显的优势。
客户端通道操作
1.通过Selector的open方法获取selector对象
public static Selector open(): 获取Selector对象。 2. 使用Channel 方法将通道注册到选择器。
创建通道对象并设置通道屏蔽模式
void configureBlocking(boolean block)将通道注册到选择器并为通道设置显着事件
SelectionKey register(Selector sel,int ops)Selector sel 要注册的选择器ops 表示正在注册的事件类型,由SelectorKey 类提供的四种类型实现。 SelectionKey.OP_ACCEPT : 服务器监视客户端连接并接收指示服务器已准备好接受此连接的连接就绪事件。 SelectionKey.OP_CONNECT: 表示客户端和服务器之间的连接已成功建立。OP_READ: 读就绪事件,指示通道已建立连接并且可以执行读操作。 SelectionKey.OP_WRITE: 写入就绪事件,指示数据可以写入通道。注册通道必须支持异步模式。否则,异步NIO 将无法工作,例如无法向选择器注册FileChannel(无异步模式)。注册时,ServerSocketChannel只能在OP_ACCEPT状态下注册。否则,将会抛出异常。 SocketChannel 不支持注册期间以OP_ACCEPT 状态注册。 3.通过选择器方法获取事件
int select(): 将事件保存到事件集合并返回就绪事件的数量。如果没有新的就绪事件,则此方法继续阻塞。 Selector的SetSelectionKey selectedKeys():返回选择器的就绪事件集合。 SetSelectionKey Keys():返回选择器的目标事件集(注册事件的数量)。 SelectionKey 概述SelectionKey 表示注册到Selector 的通道的事件关系键。当选择器发出传入事件信号时,该事件将通过相应的SelectionKey 传递。如果要取消订阅的频道事件,需要使用SelectionKey的cancel方法。 SelectionKey 属性:Interest 代表已注册事件的集合set: 下次调用该方法时,将测试该事件是否已添加。您可以通过SelectionKey 的int InterestOps() 方法检索当前SelectionKey 的感兴趣事件。就绪设置: 就绪设置。表示事件收集已准备就绪。您可以通过SelectionKey 的intreadyOps() 方法检索当前SelectionKey 的就绪事件。 Channel: 事件对应的通道。您可以通过SelectionKey 的SelectableChannel channel() 方法检索当前SelectionKey 所代表的通道。 Selector: 绑定到事件的选择器。您可以通过SelectionKey 的Selector selector() 方法检索绑定到当前SelectionKey 的选择器。附件:有关事件对象的附加信息。使用SelectionKey 的Objectattach(Object ob) 方法将指定对象附加到此键。通过SelectionKey的Objectattachment()方法获取当前附件。可以通过Channel 的SelectionKey 注册(Selector sel、int ops、Object ob)方法附加和检索附加信息。 SelectionKey Iterator 4. 通过SelectionKey 的方法确定事件。
isAcceptable() 准备接收新连接isConnectable() 已完成连接isReadable() Readable isWritable() Writable isValid()
服务端通道操作
1. 如果是有效键,则获取选择器对象。 2. 创建一个通道对象,将其设置为异步,并将其注册到选择器。 3. 定义一个无限循环,反复检查是否有新的事件被触发(selector的int select()方法) 3.1 如果有新的时间被触发,获取所有触发事件的集合(selector的SetSelectionKey selectedKeys()方法) 3.2.触发事件集合的迭代器3.3. 遍历迭代器并获取所有触发事件类型。指向对应的操作示例if (selectionKey.isAcceptable( )) {}3.3.2 移除已完成操作的触发事件(Iterator 的remove()方法)
服务器代码
public class Test04ServerBySelector { public static void main( String[] args ) throws IOException, InterruptedException { //获取选择器。 Selector=Selector.open(); //创建三个服务器通道并监听三个端口。 服务器通道1.configureBlocking(false); 服务器套接字通道2.configureBlocking(false);
Address(8888)); serverChannel3.configureBlocking(false); //将三个服务器通道注册给选择器 serverChannel1.register(selector, SelectionKey.OP_ACCEPT); serverChannel2.register(selector, SelectionKey.OP_ACCEPT); serverChannel3.register(selector, SelectionKey.OP_ACCEPT); //循环监听三个通道 while (true) { System.out.println("--------"); System.out.println("等待客户端连接..."); //获取触发的事件个数 int keyCount = selector.select();//阻塞式方法 System.out.println("有一个客户端连接成功..."); System.out.println("已就绪事件个数=" + keyCount); System.out.println("注册通道数量=" + selector.keys().size()); //获取触发事件集 Set<SelectionKey> selectionKeys = selector.selectedKeys(); System.out.println("触发事件数量=" + selectionKeys.size()); //获取事件集迭代器 Iterator<SelectionKey> it = selectionKeys.iterator(); //遍历事件集 while (it.hasNext()) { //获取注册键 SelectionKey selectionKey = it.next(); //使用选择器完成数据读取 if (selectionKey.isAcceptable()) { //获取通道对象 ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel(); //获取服务器与客户端的连接 SocketChannel server = channel.accept(); //设置非阻塞 server.configureBlocking(false); //注册读取事件 server.register(selector, selectionKey.OP_READ); //selectionKey.interestOps(selectionKey.OP_READ); } else if (selectionKey.isReadable()) { //获取客户端数据 ByteBuffer buffer = ByteBuffer.allocate(1024); SocketChannel server = (SocketChannel) selectionKey.channel(); server.read(buffer); buffer.flip(); String content = new String(buffer.array(), 0, buffer.limit()); System.out.println("客户端发送的数据:" + content); //关闭资源 server.close(); } //删除当前触发事件 it.remove(); } System.out.println("休息1秒,等待下一次操作..."); Thread.sleep(1000); } }}