java NIO (一)

2 篇文章 0 订阅

概述

NIO的关键组成包括三个部分:channel(通道)、buffer(缓存区)和selector(选择区)。传统IO是基于字节流和字符流的,而NIO则是基于通道和缓存区的,数据可以从通道读取到缓存区,也可以从缓存区写入到通道。

NIO和传统IO区别

  • channel和传统IO的InputStream、OutputStream比较相似,但是传统的IO是单向的,只能写或者只能读。而NIO的channel是双向的。

  • 传统IO是面向字节流和字符流的,每次只能读取一个或者多个字节,需要记录读取了多少个字节数,而NIO是面向缓存区的。

  • 传统IO没法动态移动已经读取的数据的位置,如果想从数据的某个特定位置获取数据,需要将这些数据先缓存起来,在指定位置读取;而NIO是可以动态指定读取数据的位置,灵活性高。

  • 传统IO是阻塞式的,比如socket的accept(),当没有连接时线程会一直阻塞;而NIO是非阻塞式的,当通道没有就绪时,线程可以去做其它事情不用一直阻塞等待。

channel

NIO中的channel是双向的,这是和传统的IO中的FileInputStream、FileOutputStream的最本质的区别

channel最常用的有以下四种

  • FileChannel (文件IO)
  • DatagramChannel (UDP)
  • SocketChannel (TCP服务器)
  • ServerSocketChannel (TCP客户端)

buffer

buffer是NIO中的关键,常用的buffer有以下几种

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • LongBuffer
  • IntBuffer
  • ShortBuffer
  • MappedByteBuffer

具体用法后面会讲到

selector

selector采用的是使用单线程去访问多个channel渠道,对于渠道数量比较多,但是访问流量少的场景selector最实用。

selector会轮询检查各个channel是否已经准备就绪,前提是每个channel要先register到这个selector中,然后调用selector的select的方法,这个时候单线程会轮询访问各个channel,如果channel没有准备好就返回,如果有准备就绪的channel,则线程就会立刻去执行。


在讲解案例之前,首先还是来了解一下buffer的一些属性和内部原理,这样有助于我们讲解和理解案例代码

Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

Buffer的用法

buffer的用法主要有四种

  • 写入数据

  • 调用flip()方法

  • 读取数据

  • 调用clear()或者compact()清空数据

首先channel向buffer中写入数据,当数据写完后读取数据时,首先调用flip方法切换模式,将buffer从写模式切换到读模式(具体切换细节后面会讲到),然后开始去读数据。

当数据读取结束后需要清空buffer,而清空buffer有两种方式,一种是clear它会将buffer中的数据全部抹去(实际上数据是存在的,只是记录数据的位置清零,后面会详细说明);而compact则是将已经读取过的数据清空,没有读取的数据会移动到buffer的头部,后面写入的数据会写入到这些数据的后面。

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");

FileChannel inChannel = aFile.getChannel();

//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {

  buf.flip();  //make buffer ready for read

  while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
  }

  buf.clear(); //make buffer ready for writing
  bytesRead = inChannel.read(buf);
}
aFile.close();

buffer的capacity,position和limit

要了解buffer,首先要了解它的这三个属性

  • capacity: buffer的大小

  • position

  • limit

position和limit的含义取决于当前是写入还是读取,而capacity的含义是固定的,就是buffer的大小。

image

  • 写入数据

    当写入数据的时候,position的位置是0,而limit的位置和capacity的位置一样,指向当前buffer的最大位置
    
    每次写入一个数据,position的位置就会+1
    
  • 读取数据

    当调用slip方法后由写模式转换为读模式,这个时候position会指向位置0,即第一个可以读取数据的位置;而limit则指向刚才position的位置,也就是接下来可以写入数据的位置,而capacity仍然指向的是buffer的最大位置。
    
    也就是说position和limit直接的区域就是可以读取的数据区域,也是有数据的区域。
    

其它方法

  • mark()和reset()

    mark()用于记录当前的position的一个特定位置,后面可以通过reset()方法恢复到这个特定位置
    
  • rewind()

    rewind() 用于将position的位置重置为0,而limit的位置不变,这样做的目的可以让我们重新读取buffer中的数据。
    
  • equals()和compareTo()

    可以使用equals()和compareTo()方法比较两个Buffer。
    
    * equals()
    
    当满足下列条件时,表示两个Buffer相等:
    
    有相同的类型(byte、char、int等)。
    Buffer中剩余的byte、char等类型的个数相等。
    Buffer中所有剩余的byte、char等类型都相同。
    
    如你所见,equals只是比较Buffer的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer中的剩余元素。
    
    * compareTo()方法
    
    compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer:
    
    第一个不相等的元素小于另一个Buffer中对应的元素 。
    所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。
    
  • clear()与compact()

    一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。
    
    如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。
    
    如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。
    
    如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。
    
    compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值