Java NIO

一、NIO概述

NIO即 non-blocking IO,非阻塞IO,在java中实现主要通过Channel、Buffer、和Selector来实现。
Channel和Buffer :channel相当于一个通道,Buffer相当于输入输出

channel和buffer

Selector:选择器,实现了使用单线程操作多个channel,IO复用
Selector原理:首先向Selector注册某几个Channel。然后调用select()方法,这样,一旦有某个注册的了Channel有事件就绪,线程就可以处理这些事件。事件包括(新连接进来,数据接收等)
channel、buffer、selector这些原理与实现、使用,会在下文详细讲到

二、Channel介绍

2.1 Channel三个特点

  1. 既可以写又可以读
  2. 可以异步读写
  3. 要从一个Buffer读或者从一个Buffer写入

2.2.Channel的实现

基本的几个实现:

  1. FileChannel
  2. DatagramChannel
  3. SocketChannel
  4. ServerSocketChannel
    FileChannel 从文件中读写数据。

DatagramChannel 能通过UDP读写网络中的数据。

SocketChannel 能通过TCP读写网络中的数据。

ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
//从通道中读取一个缓冲区大小的数据,如果已经读完了则返回-1。
int bytesRead = inChannel.read(buf);
//判断是否读了数据
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
//反转Buffer,从写模式转换成读模式
buf.flip();
//使用get()读取Buffer
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
//清空buf的全部(全部清空了就不用flip反转模式了,因为本来就是空的,非空不反转直接写会造成数据反转了从而混乱)
buf.clear();
//再次给buf写入数据
bytesRead = inChannel.read(buf);
}
//关闭文件
aFile.close();

三、Buffer

Buffer相当于在Java NIO中与通道进行交互的缓冲区,数据从缓冲区读到通道或者从通道读到缓冲区

3.1 Buffer的相关用法

Buffer的使用步骤

  1. 写入数据到Buffer
  2. 调用flip()方法把Buffer从写模式切换为读模式(反转Buffer)
  3. 从Buffer中读取数据
  4. 读完数据调用clear()或者compact()方法清除数据,compact()用于清除已经读过的数据,而clear()方法用于清除全部的数据

代码示例:

//创建文件类型
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
//创建文件通道
FileChannel inChannel = aFile.getChannel();
//创建一个48 byte的缓冲区
ByteBuffer buf = ByteBuffer.allocate(48);
//从通道中读取一个缓冲区大小的数据,如果已经读完了则返回-1。
int bytesRead = inChannel.read(buf); 
while (bytesRead != -1) {
//反转Buffer,从写模式转换成读模式
  buf.flip();  
  //使用get()读取Buffer
  while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
  }
  //清空buf的全部(全部清空了就不用flip反转模式了,因为本来就是空的,非空不反转直接写会造成数据反转了从而混乱)
  buf.clear(); 
  //再次给buf写入数据
  bytesRead = inChannel.read(buf);
}
//关闭文件
aFile.close();

3.2 buffer三个属性capacity、position、limit

1.capacity 容量
作为一个内存缓存块,capacity为Buffer的大小,你只能往Buffer里写入capacity个单位,一旦Buffer满了,需要将其清空才能继续写数据。(单位:byte、char、long等,取决于Buffer的类型比如ByteBuffer、CharBuffer、LongBuffer)
2.position 指针
当写数据时候,position指向当前写的位置,position初始值为0,最大可为capacity-1,相当于数组
当读取数据,Buffer从写模式切换到读模式,position置零,每读一个单位,position+1
3.limit 当前操作的限制大小
也分为写模式和读模式
当写的时候,限制是最多能写多少数据,相当于Buffer的capacity
当读模式的时候,限制是可以读到多少数据,这时候limit相当于之前写模式的position,即之前写了多少数据,你就最多可以读多少数据。

3.3 Buffer的类型

Java NIO 有以下Buffer类型

  1. ByteBuffer
  2. MappedByteBuffer
  3. CharBuffer
  4. DoubleBuffer
  5. FloatBuffer
  6. IntBuffer
  7. LongBuffer
  8. ShortBuffer

不同的类型相当于不同的容器,里面可以写数据的类型也不一样。

3.4 Buffer大小的分配

ByteBuffer buf = ByteBuffer.allocate(48);

3.5 Buffer的比较

  1. equeals()
    只比较 Buffer本身的一部分,不对里面的内容比较
  2. compareTo()
    比较每一个元素,如果第一个不相等的小于另一个对应的元素,则认为小于。如果全部元素都相等,则比较剩余的长度,先耗尽的为小。

四、Scatter/Gather

scatter(分散):将channel写入多个Bufffer
示例:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);

按照数组中的顺序写入buffer,当第一个buffer写完后,写第二个。

gather(聚集):将多个Buffer的内容读进同一个channel
示例:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

按照顺序将Buffer数组中的内容读到channel中,只会读取position到limit之间的数据

五、channel之间传输

通道中如果有一个通道是FileChannel,那么可以实现两个channel之间的数据传输

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
//获取通道
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
//获取通道
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
//使用transferFrom传输
toChannel.transferFrom(position, count, fromChannel);

注:把transferFrom改成transferTo则按照相反的传输方向传输。

六、Selector

Selector可以理解为选择器,是Java Nio中管理多个channel的选择器。可以实现一个线程操作多个channel
示例:

//创建一个Selector
Selector selector = Selector.open();
channel.configureBlocking(false);
把通道注册到selector
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);

channel与selector一起使用的时候,channel必须处于非阻塞模式下。所以selector不能与FileChannel一起使用,FileChannel不能切换到非阻塞模式,而套接字都可以。
register()方法的第二个参数是selector的监听事件,当监听事件触发的时候表示该事件已经就绪
可以监听下面四种不同的事件类型
1.Connect
2.Accept
3.Read
4.Write
监听多个事件示例:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey key = channel.register(selector,interesSet)

6.1 SelectionKey

通道注册到selector后会返回一个SelectionKey,这个类型里包含了一些属性:
1.interest集合
2.ready集合
3.Channel
4.Selector
5.附加的对象(可选)

interest集合

用于查看之前注册所选择的事件,获取示例

int interestSet = selectionKey.interestOps();

ready集合

ready集合是已经准备就绪的操作的集合,可以用来检测什么事件已经就绪,用以下四个方法,如果事件就绪则返回true。

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

获取Channel和Selector

代码如下:

Channel channel  = selectionKey.channel();
Selector selector = selectionKey.selector();

附加对象

可以将一个或者多个对象信息附在selectionKey上,方便管理或者操作,比如将Buffer附上
代码示例:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

或者:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

在注册的时候直接附上

6.2 Selector选择通道

select()方法

执行Selector的select()方法可以指导是否有感兴趣的通道就绪
select() 阻塞到至少有一个通道在你注册的事件上就绪了。
select(long timeout) 和select()一样,除了最长会阻塞timeout毫秒(参数)。
selectNow() 不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。
注意:调用select方法返回的数字是上一次调用select到这次期间新就绪的通道
一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。如下所示:

Set selectedKeys = selector.selectedKeys();

当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。
可以遍历这个已选择的键集合来访问就绪的通道。如下:

//返回通道集合
Set selectedKeys = selector.selectedKeys();
遍历 Set
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
    //从keyset中拿到SelectionKey
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        //访问就绪逻辑
    } else if (key.isConnectable()) {
        //连接就绪逻辑
    } else if (key.isReadable()) {
        //读就绪逻辑
    } else if (key.isWritable()) {
        //写就绪逻辑
    }
    keyIterator.remove();
}

七、SocketChannel

SocketChannel是一个用于处理TCP网络套接字连接的Channel,可以通过两种方式创建SoketChannel
1.主动连接:通过打开一个SocketChannel类型的Channel主动连接到互联网某台服务器
2.接受连接:通过ServerSocketChannel接受一个连接,会生成一个SocketChannel。
这里主要讲主动连接:

//打开一个SocketChannel
SocketChannel socketChannel = SocketChannel.open();
//把SocketChannel连接到ip端口
socketChannel.connect(new InetSocketAddress("http://jianshu.com", 80));
//创建一个字节型的缓冲区
ByteBuffer buf = ByteBuffer.allocate(48);
//把通道内容读进缓冲区,read表示读了多少字节进Buffer里,如果返回-1则表示已经读到了末尾
int bytesRead = socketChannel.read(buf);
while(byteRead!=-1){
    bytesRead=soketChannel.read(buf)
    //doSomeThing
}
String returnDate="ok";
ByteBuffer outBuffer= ByteBuffer.allocate(48);
//先清空
outBuffer.clear();
//把数据放入Buffer
outBuffer.put(returnDate.getBytes());
//切换到读模式
outBuffer.flip();
//循环写入通道
while(outBuffer.hasRemaining()) {
    socketChannel.write(buf);
}
//关闭通道
socketChannel.close();

非阻塞模式:SocketChannel的非阻塞模式主要体现在connect(),read(),write(),通过调用SocketChannel的configureBlocking(false|truee)方法来设置是否为非阻塞模式。
下面是connect的非阻塞示例,其他的与此类似。

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jianshu.com", 80));
while(! socketChannel.finishConnect() ){
    //wait, or do something else...
}

八、ServerSocketChannel

在javaNio中,ServerSocketChannel可以监听某个端口新连接的tcp,就像标准IO的ServerSocket一样。
同时可以使用ServerSocketChannel的accept()方法来获取SocketChannel
阻塞模式:

//打开ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//监听9999端口
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
//通过while(true)来持续性监听
while(true){
//accept()方法会一致阻塞直到新的连接到达,新连接到达会返回一个SocketChannel
 SocketChannel socketChannel =serverSocketChannel.accept();

    //do something with socketChannel...
}
关闭ServerSockketChannel
serverSocketChannel.close();

非阻塞模式

//打开ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//监听9999端口
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
//设置为非阻塞模式,accept立即返回,如果没有连接返回的对象是null
serverSocketChannel.configureBlocking(false);
//通过while(true)来持续性监听
while(true){
//accept()方法会一致阻塞直到新的连接到达,新连接到达会返回一个SocketChannel
 SocketChannel socketChannel =serverSocketChannel.accept();
if(socketChannel != null){

    //do something with socketChannel...
    }
}
//关闭ServerSockketChannel
serverSocketChannel.close();

相信看完这些,读者们已经知道如何使用JavaNIO了吧,推荐刚学的读者手动写个使用selector+异步channel的demo,有助于串联起上面说的有些零散的知识,如果需要示例代码请私聊回复。
点击关注不迷路

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    JackChen1024阅读 7,483评论 1 143
  • # Java NIO # Java NIO属于非阻塞IO,这是与传统IO最本质的区别。传统IO包括socket和文...
    Teddy_b阅读 549评论 0 0
  • 简介 Java NIO 是由 Java 1.4 引进的异步 IO.Java NIO 由以下几个核心部分组成: Ch...
    永顺阅读 1,736评论 0 15
  • NIO概述 Java NIO全称为Non-blocking IO或者New IO,从名字我们知道NIO是非阻塞的I...
    zhong0316阅读 565评论 0 7
  • (转载说明:本文非原创,转载自http://ifeve.com/java-nio-all/) Java NIO: ...
    柳岸阅读 790评论 0 3