或者
/* * 通道之间的数据传输(直接缓冲区) */ FileChannel inChannel3 = FileChannel.open(Paths.get("123.txt"), StandardOpenOption.READ); FileChannel outChannel3 = FileChannel.open(Paths.get("output123.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); long start = System.currentTimeMillis(); inChannel3.transferTo(0, inChannel3.size(), outChannel3); System.out.println("耗时: "+(System.currentTimeMillis()-start) ); //等价于 // outChannel3.transferFrom(inChannel3, 0, inChannel3.size()); inChannel3.close(); outChannel3.close();零拷贝仅需要耗时 6 就可以完成
NIO的非阻塞与IO的阻塞什么是阻塞? 举个例子, 如果有一天我碰到了不会的作业题,于是我给老师发了条短息请教咋做, 这时,假如我进入了阻塞模式,我就会一直瞅着手机,别的也不干,就等着老师回信息, 假如我进入了非阻塞的模式,发完短信后跳过这个题,去做别的题
常见的阻塞比如, 键盘录入, Socket的accept()以及IO的read write, 全部会卡在那行代码直到执行完毕才会往下执行, 这种风格的好处是显而易见的, 及其容易的进行顺序编程
但是在NIO中,channel的read,write可以是阻塞的,也可以是非阻塞的,这取决于channel是否阻塞, 一般在进行网络编程时,要搭配上selector选择器,一起用, 同时channel我们也会设置成非阻塞的, 想想也不能让服务器的读写阻塞住,因为它可不是面对一两个用户,我们需要它可以一遍一遍的正常流水运行
在客户端,connect方法不再是阻塞的,和服务端进行数据交互之前,java提供了检查机确保连接百分百健康, 如果服务端没有接受连接,客户端是是没办法进一步操作的
if (selectionKey.isConnectable()) { // 强转成 有连接事件发生的Channel client = (SocketChannel) selectionKey.channel(); // 完成连接 if (client.isConnectionPending()) { client.finishConnect();从通道中的read和write方法也不是阻塞的,即可返回,可以让服务端的业务代码很流畅的执行完,再接受新的请求,处理新请求
SelectorSelector选择器NIO的第三个组件,三者的关系图如上所示
什么是selector? 作用是什么?selector是选择器的意思, 和它直接关联的组件是Channel, 没错,它的作用就是不断的轮询绑定在他身上的所有channel. 一旦有通道发生了它感兴趣的事件,接着处理此事件
selector维护了什么?无论是服务端的Selector 还是客户端的Selector 它都维护了三个Set集合 , 里面封装的是 SelectionKey, 他是channel注册进Selector的产物,一般是使用它反向获取channel
key set
他是一个全集,每当channel通过register方法注册进选择器时,于此同时也会把包含自己信息的key添加到这个全集中来 注册的信息就会以SelectionKey的封装形式保存在这个集合中, 选择器每次轮询的channel,就是这里面的channel
selected key
感兴趣的key的集合, 举个例子, 通道1注册进选择器时,告诉选择器,我可能会给你发信息,你得盯着我,读我给你的信息, 于是选择器对通道1感性趣的事件是 read, 那么在选择器轮询channel时, 一旦通道1出现了write操作,就会被选择器感知,开始read
每次遍历selected key时我们会执行这行代码:Set<SelectionKey> selectionKeys = selector.selectedKeys(); 它的意思是,我们取出了 选择器的感性事件的set集合,只要程序还在运行,只要选择器一旦被open(),除非我们手动的close() 否则选择器对象就不会被释放,所以它的感兴趣的set集合是不会被自动会收到,于是我们就得收到的把处理过的感兴趣的事件对应的SelectionKey移除出这个set集合,不然下一次轮询时,这个事件还会再一次被处理,并且无限制的处理下去
key有且仅有两种方式从 selected-key-set 中剔除 1. 通过Set的remove()方法, 2.通过迭代器的remove()方法
cannelled key
取消的key的集合,代表原来感兴趣的事件,现在不感兴趣了. 下一次轮询,进行select() 本集合中的SelectionKey会从key set中移除, 意味着它所关联的channel将会被选择器丢弃掉,不再进行监听
关闭channel 或者是调用了cancel()方法都会将key添加到cannelled key 集合中
使用场景: 一般会在客户端主动断开连接的时候使用它.
selector的select()方法 select(long); // 设置超时时间 selectNow(); // 立即返回,不阻塞 select(); 阻塞轮询select()过程的细节:
第一步, cannelled-key中的每一个元素会从全集key set中剔除,表示这些可以关联的通道不会被注册
第二步操作系统帮我们轮询每一个通道是否有选择器感性趣的事情发生
对于一条准备就绪的channel(发生事件通道),他至少会发生下面两件事之一:
它的key会被添加进selected-key-set中,来标识它将被选中,进而处理
如果它的key,已经存在于这个集合中了,下一步就是它的 read-operation将被更新
第三步: 如果在轮询时发现了有任何key被放置在了cannelled-key-set中,重复第一步,不再注册它关联的通道
romove key 和 cannel key 的区别前者是把key从selected key set集合,也就是被选中的集合中剔除出去,表示当前的事件已经处理完了
后者是表示,把key从全集中剔除出去, 表示想要废弃这个key关联的channel
selector的创建他是根据不同操作系统提供的不同的Provider使用provide()创建出来的
NIO编程模型