在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主要实现有:

  1. SocketChannel (服务端,客户端都能用)
  2. ServerSocketChannel (TCP,服务端专用)
  3. DatagramChannel (UDP)
  4. 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方法

  1. 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));}

输出结果请读者自行理解下

  1. 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的时候如果读取的数据不够,也不会阻塞调用线程。

  1. 总结

本文介绍了NIO的核心Channel, Buffer和Selector,它们的设计意图和解决的问题,同时举了些简单的例子来说明用法。NIO的根本还是I/O多路复用, 操作系统告诉你哪个fd可读可写,内核帮你做了Event Loop,比在应用层用户空间做无疑是提升了太多的
本文转自

NIO核心之Channel,Buffer和Selector简介相关推荐

  1. 反射 Nio channel Buffer

    1.反射     1.反射的简介         java的反射机制 在运行状态中 对于任意一个类 都能知道任意一个类的所有属性和方法         对于任意一个对象 都能够调用它的任意一个属性和方 ...

  2. java之NIO(Channel,Buffer,Selector)

    java之NIO 1 什么是NIO Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API.NIO支持面向缓冲区的.基于通道的IO操作 ...

  3. Netty导学之NIO,Channel、Buffer、Selector详解

    介绍 NIO可翻译为Non-Blocking IO非阻塞IO,也可以称其为New IO 因为其是JDK1.4新出现的. java中的流要么是输入流,要么是输出流,不可能都是,它面向流编程.而在NIO中 ...

  4. NIO详解Channel、Buffer、Selector看这一篇就够了

    NIO是同步阻塞队列-->IO复用模型很像,请仔细看这幅图 NIO和IO的到底有什么区别?有什么关系? 1.NIO是以块的方式处理数据,但是IO是以最基础的字节流形式去写入和读出的.所以在效率上 ...

  5. Java NIO三大核心之缓冲区Buffer概述

    三大核心原理示意图  说明: 每个 Channel 都会对应一个 Buffer: Selector 对应一个线程,一个线程对应多个 Channel(连接): 该图反应了有三个 Channel 注册到该 ...

  6. 【Netty】NIO 选择器 ( Selector ) 简介

    文章目录 I . 选择器 ( Selector ) II . 选择器 ( Selector ) 与 NIO 特性 III . 选择器 ( Selector ) API 简介 IV . Selectio ...

  7. IO到NIO的前因后果,以及NIO的用法(2)——Selector、Channel

    Selector Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 .它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读.可写 ...

  8. java channel源码_彤哥说netty系列之Java NIO核心组件之Channel

    你好,我是彤哥,本篇是netty系列的第五篇. 欢迎来我的工从号彤哥读源码系统地学习源码&架构的知识. 简介 上一章我们一起学习了如何使用Java原生NIO实现群聊系统,这章我们一起来看看Ja ...

  9. java channel源码_5. 彤哥说netty系列之Java NIO核心组件之Channel

    你好,我是彤哥,本篇是netty系列的第五篇. 简介 上一章我们一起学习了如何使用Java原生NIO实现群聊系统,这章我们一起来看看Java NIO的核心组件之一--Channel. 思维转变 首先, ...

最新文章

  1. oracle 回收碎片,Oracle10g中表的碎片空间回收
  2. LCDS与Blazeds区别与配置
  3. int能表示的数据范围(在VS2017下,int和long都是32位)
  4. 排序 (5)计数排序“概念”
  5. 交换机的4种网络结构方式你知道是什么吗?
  6. DCMTK:OFStandard类中的ASCII /双转换方法的测试代码
  7. 你与弄懂promise之间可能只差这篇文章(二)
  8. html5引入外联样式的优先级,CSS的4种引入方式及优先级
  9. 【数组】 - 有序数组设计
  10. u盘启动 联想一体机_联想Y430pAT-ISE(H)U盘安装Win7系统教程
  11. 20-10-010-安装-kafka_2.11-1.1.0-单节点测试
  12. sqlserver text最大长度_1156. 单字符重复子串的最大长度
  13. 在LaTeX中使用BibTeX时的一个问题及其解决:编译PDF不随bib文件更新
  14. Python密码生成器
  15. python统计汉字和标点_Python处理中文标点符号大集合
  16. 大话设计模式——解释器模式
  17. 从Palm到Pocket PC(转)
  18. dimm和udimm_服务器内存类型(UDIMM、RDIMM和LRDIMM)
  19. 纸飞机 --2013-08-08博客搬家
  20. 搜索 dfs+bfs

热门文章

  1. JAVA 设计的七大原则
  2. 博客使用的CSS代码备份
  3. Java学习----你的选择是什么-条件结构
  4. 图像梯度处理MATLAB代码
  5. 吴恩达 coursera ML 第九课总结+作业答案
  6. Git学习笔记:远程仓库
  7. Softmax vs. SoftmaxWithLoss 推导过程
  8. 【Python】函数默认参数怎么改变?
  9. 《应试教育洗礼的“好”学生的学习行为特征分析》 2019-11-02
  10. <马哲>世界2017-12-27