NIO核心之Channel,Buffer和Selector简介
在NIO的API中,Channel就是实现非阻塞的组件,而事件分发(Dispatcher)使用的是Selector组件
,在传统的I/O流(Stream)是有方向的,而NIO支持双向读写,这样就需要将流中的数据读取到某个缓冲组件里,即Buffer组件
.Buffer组件还有个特殊的实现DirectByteBuffer, 可以申请堆外内存
,关于为什么要申请堆外内存后续会谈。
一、Channel(对比于原生的javaAPI,channel是可以双向的通道
)
Channel是NIO中用来实现非阻塞数据操作的桥梁
,笔者猜测是借鉴的Berkly Socket的设计,代表某种通道,和I/O Stream只支持读或者写(单向)不一样,Channel同时支持读和写, 但是只能读和写到Buffer中
,因为,支持了非阻塞,读出的数据要找个地方临时存放.
Channel主要实现有:
- SocketChannel (服务端,客户端都能用)
- ServerSocketChannel (TCP,服务端专用)
- DatagramChannel (UDP)
- FileChannel
基本类图如下:
以上Channel涵盖了文件,TCP, UDP网络的支持, 也是我们用的最多的。
Channel都不是手动new出来的,基本都是用静态方法Open出来的,或者从BIO的Stream里封装得到的(本质上也是调用某Channel的open方法)。
比如使用FileChannel来读写文件的一个例子:
/*** 测试FileChannel模拟传统IO用竹筒多次取水的过程* * @author sound2gd**/
public class FileChannelTest2 {public static void main(String[] args) throws Exception{FileInputStream sr = new FileInputStream("src/com/cris/chapter15/f6/FileChannelTest2.java");FileChannel fc = sr.getChannel();ByteBuffer bf = ByteBuffer.allocate(256);//创建Charset对象Charset charset = Charset.forName("UTF-8");CharsetDecoder decoder = charset.newDecoder();while((fc.read(bf))!=-1){//锁定Buffer的空白区bf.flip();//转码CharBuffer cbuff = decoder.decode(bf);System.out.print(cbuff);//buffer初始化,用于下一次读取bf.clear();}}
}
用法还是比较简单的,从Channel读数据到Buffer用read, 从Buffer写数据到Channel用write
这里的FileChannel就是从FileInputStream上得到的, 查看其源码:
public FileChannel getChannel() {synchronized (this) {if (channel == null) {channel = FileChannelImpl.open(fd, path, true, false, this);}return channel;}
}
可以看到,还是调用了FileChannelImpl的open方法
- Scatter && Gather
上面的类图还可以看到ScatteringByteChannel和GatheringByteChannel
,它们分别代表Scatter和Gather操作
- Scatter是分散操作,可以将一个Channel里的数据读取到多个Buffer
- Gather是聚合操作,可以将多个Buffer的数据读取到一个Channel
在网络编程中这俩是常用操作,比如http协议的解析通常会将header和body分散到俩Buffer,方便后续处理。
Scatter和Gather的细节限于篇幅不展开叙述,感兴趣的读者可以自行了解
二、 Buffer
Buffer是一个容器,本质上就是一个数组.用于接受从Channel里传过来的数据
Buffer的实现常见有:
看名字就知道是存放什么类型数据的Buffer.
Buffer的创建时通过Buffer类的静态方法来创建的。 Buffer有三个核心概念:
- position:位置,用于指明下一个可以被读出的或者写入的缓冲区位置索引
- limit:界限,第一个不应该被读出或者写入的缓冲区位置索引
- capacity:容量,创建后不能改变
Buffer类有一个实例方法:flip()
。其作用是将limit设置为position所在的位置,然后将position置为0 ,这就使得Buffer的读写指针又回到了开始位置。 clear()方法就是将position置为0,limit置为capacity.
为啥要有这种操作?因为Buffer是支持读和写的,写完了给别的地方用就要flip, 免得数据处理出错
下面以CharBuffer为例举个简单的例子
public static void main(String[] args) {// 创建BufferCharBuffer buffer = CharBuffer.allocate(8);System.out.println("buffer的容量:" + buffer.capacity());System.out.println("buffer的位置:" + buffer.position());System.out.println("buffer的界限:" + buffer.limit());buffer.put('s');buffer.put('o');buffer.put('u');buffer.put('n');buffer.put('d');System.out.println("加入5个元素后position:" + buffer.position());// 调用flipbuffer.flip();System.out.println("buffer的容量:" + buffer.capacity());System.out.println("buffer的位置:" + buffer.position());System.out.println("buffer的界限:" + buffer.limit());// 取出第一个元素System.out.print("buffer中的元素:" + buffer.get());while (buffer.hasRemaining()) {System.out.print(buffer.get());}System.out.println();System.out.println("取出第一个元素后position=" + buffer.position());// 调用clearbuffer.clear();System.out.println("第3个元素" + buffer.get(2));}
输出结果请读者自行理解下
- DirectByteBuffer
上面还有个特殊的类,就是这个DirectByteBuffer
, 这个是有名的冰山对象,分配DirectByteBuffer的时候,JVM是申请一块直接内存(堆外), 然后地址关联到DirectByteBufer里的address
。它的回收器sun.misc.Cleaner使用的是虚引用, 当DirectByteBuffer被回收的时候,其关联的堆外内存也会使用Unsafe释放掉。虽然在DireactByteBuffer堆内占用内存少,但是可能关联一块非常大的堆外内存,和冰山一样,所以称为冰山对象。
后面还会对DirectByteBuffer进行解析,这个是NIO的一个重要feature之一
三、Selector
Selector是NIO中用来实现事件分发的组件,
受AWT线程的启发,用于接收I/O事件并分发到合适的处理器。
Selector底层使用的依然是操作系统的select,poll和epoll系统调用,支持使用一个线程来监听多个fd的I/O事件,也即前面讲的I/O多路复用模型.
Selector
可以同时监控多个SelectableChannel的IO状况,是非阻塞IO的核心,一个Selector 有三个SelectionKey集合
所有的SelectionKey集合,代表了注册在该Selector上的Channel
。
- 被选择的SelectionKey集合:
代表了所有可以通过select 方法获取的,需要进行IO处理的Channel。
- 被取消的SelectionKey集合:
代表了所有被取消注册关系的Channel,在下次执行select方法时。这些Channel对应的SelectKey会被彻底删除
SelectableChannel代表可以支持非阻塞IO操作的Channel对象,它可以被注册到Selector上
, 这种注册关系由SelectionKey实例表示
下面举个聊天室的例子
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;/*** 使用NIO来实现聊天室*/
public class NServer {// 用于检测所有Channel状态的selectorprivate Selector selector = null;// 定义实现编码,解码的字符集对象private Charset charset = StandardCharsets.UTF_8;public void init() throws Exception {selector = Selector.open();//通过open方法来打开一个未绑定的ServerSocketChannel实例ServerSocketChannel server = ServerSocketChannel.open();InetSocketAddress isa = new InetSocketAddress("127.0.0.1", 8888);// 绑定到指定地址server.bind(isa);// 设置以非阻塞的方式工作server.configureBlocking(false);// 将Server注册到指定的Selector对象server.register(selector, SelectionKey.OP_ACCEPT);while (selector.select() > 0) {// 依次处理selector上的已选择的SelectionKeyfor (SelectionKey sk : selector.selectedKeys()) {//从selector上的已选择key集中删除正在处理的SelectionKeyselector.selectedKeys().remove(sk);//如果sk对应的Channel包含客户端的连接请求if (sk.isAcceptable()) {//调用accept方法接受此连接,产生服务器端的SocketChannelSocketChannel accept = server.accept();//采用非阻塞模式accept.configureBlocking(false);//将该SocketChannel注册到selectoraccept.register(selector, SelectionKey.OP_READ);//将sk对应的Channel设置成准备接受其他请求sk.interestOps(SelectionKey.OP_ACCEPT);}// 如果sk对应的Channel有数据需要读取if (sk.isReadable()) {// 获取该SelctionKey对应的Channel,该Channel有可读的数据SocketChannel channel = (SocketChannel) sk.channel();//定义准备执行读取数据的ByteBufferByteBuffer buffer = ByteBuffer.allocate(1024);String content = "";//开始读取数据try {while (channel.read(buffer) > 0) {buffer.flip();content += charset.decode(buffer);}//打印从该SK对应的Channel读取到的数据System.out.println("读取的数据" + content);//将sk对应的channel设置成准备下一次读取sk.interestOps(SelectionKey.OP_READ);} catch (Exception e) {//如果捕获到了该SK对应的Channel出现了异常,即表明//该Channel对应的Client出现了问题,所以从selctor中取消该Sk的注册sk.cancel();if (sk.channel() != null) {sk.channel().close();}}//如果content的长度大于0,即聊天信息不为空,if (content.length() > 0) {//遍历该selecor里注册的所有SelectionKeyfor (SelectionKey key : selector.keys()) {//获取该key对应的channelSelectableChannel target = key.channel();//如果该Channel是SocketChannel对象if (target instanceof SocketChannel) {// 将读取到的内容写入该channel中SocketChannel dest = (SocketChannel) target;dest.write(charset.encode(content));}}}}}}}public static void main(String[] args) {try {new NServer().init();} catch (Exception e) {e.printStackTrace();}}}
使用nc localhost 8888就可以测试了,多开几个终端以模拟多个客户端。
这个例子里使用了ServerSocketChannel,类似于BIO中ServerSocket,用于监听某个地址和端口, 是TCP服务端的代表.同时还可以看到accept之后得到了一个SocketChannel, 代表一个TCP socket通道.
我们均使用了非阻塞模式, 在read的时候如果读取的数据不够,也不会阻塞调用线程。
- 总结
本文介绍了NIO的核心Channel, Buffer和Selector
,它们的设计意图和解决的问题,同时举了些简单的例子来说明用法。NIO的根本还是I/O多路复用, 操作系统告诉你哪个fd可读可写,内核帮你做了Event Loop,比在应用层用户空间做无疑是提升了太多的
。
本文转自
NIO核心之Channel,Buffer和Selector简介相关推荐
- 反射 Nio channel Buffer
1.反射 1.反射的简介 java的反射机制 在运行状态中 对于任意一个类 都能知道任意一个类的所有属性和方法 对于任意一个对象 都能够调用它的任意一个属性和方 ...
- java之NIO(Channel,Buffer,Selector)
java之NIO 1 什么是NIO Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API.NIO支持面向缓冲区的.基于通道的IO操作 ...
- Netty导学之NIO,Channel、Buffer、Selector详解
介绍 NIO可翻译为Non-Blocking IO非阻塞IO,也可以称其为New IO 因为其是JDK1.4新出现的. java中的流要么是输入流,要么是输出流,不可能都是,它面向流编程.而在NIO中 ...
- NIO详解Channel、Buffer、Selector看这一篇就够了
NIO是同步阻塞队列-->IO复用模型很像,请仔细看这幅图 NIO和IO的到底有什么区别?有什么关系? 1.NIO是以块的方式处理数据,但是IO是以最基础的字节流形式去写入和读出的.所以在效率上 ...
- Java NIO三大核心之缓冲区Buffer概述
三大核心原理示意图 说明: 每个 Channel 都会对应一个 Buffer: Selector 对应一个线程,一个线程对应多个 Channel(连接): 该图反应了有三个 Channel 注册到该 ...
- 【Netty】NIO 选择器 ( Selector ) 简介
文章目录 I . 选择器 ( Selector ) II . 选择器 ( Selector ) 与 NIO 特性 III . 选择器 ( Selector ) API 简介 IV . Selectio ...
- IO到NIO的前因后果,以及NIO的用法(2)——Selector、Channel
Selector Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 .它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读.可写 ...
- java channel源码_彤哥说netty系列之Java NIO核心组件之Channel
你好,我是彤哥,本篇是netty系列的第五篇. 欢迎来我的工从号彤哥读源码系统地学习源码&架构的知识. 简介 上一章我们一起学习了如何使用Java原生NIO实现群聊系统,这章我们一起来看看Ja ...
- java channel源码_5. 彤哥说netty系列之Java NIO核心组件之Channel
你好,我是彤哥,本篇是netty系列的第五篇. 简介 上一章我们一起学习了如何使用Java原生NIO实现群聊系统,这章我们一起来看看Java NIO的核心组件之一--Channel. 思维转变 首先, ...
最新文章
- oracle 回收碎片,Oracle10g中表的碎片空间回收
- LCDS与Blazeds区别与配置
- int能表示的数据范围(在VS2017下,int和long都是32位)
- 排序 (5)计数排序“概念”
- 交换机的4种网络结构方式你知道是什么吗?
- DCMTK:OFStandard类中的ASCII /双转换方法的测试代码
- 你与弄懂promise之间可能只差这篇文章(二)
- html5引入外联样式的优先级,CSS的4种引入方式及优先级
- 【数组】 - 有序数组设计
- u盘启动 联想一体机_联想Y430pAT-ISE(H)U盘安装Win7系统教程
- 20-10-010-安装-kafka_2.11-1.1.0-单节点测试
- sqlserver text最大长度_1156. 单字符重复子串的最大长度
- 在LaTeX中使用BibTeX时的一个问题及其解决:编译PDF不随bib文件更新
- Python密码生成器
- python统计汉字和标点_Python处理中文标点符号大集合
- 大话设计模式——解释器模式
- 从Palm到Pocket PC(转)
- dimm和udimm_服务器内存类型(UDIMM、RDIMM和LRDIMM)
- 纸飞机 --2013-08-08博客搬家
- 搜索 dfs+bfs