• Java NIO


    java NIO包括了以下几个核心部分:

    • Channel

    • Buffer

    • Selector

    java NIO是非阻塞IO,比如:线程请求把channel中的数据读入到buffer,当channle执行读数据到buffer时,线程可以去做其他事,不用一直等它。一旦数据读入缓冲区,线程就可以继续处理它。同理,将buffer中的数据写入到channer也是如此。

    channel

    在标准的IO API中,使用的是字节流和字符流。在NIO中,使用的是Channel 和Buffer。数据从Channel 读入Buffer,或者从Buffer写入Channel 。

    通常,NIO中的所有IO都从一个channel开始的,channel有点类似流。

     Java NIO中使用核心channel如下

    • FileChannel 用于文件的数据读写

    • DatagramChannel 用于UDP的数据读写

    • SocketChannel 用于TCP的数据读写,一般是客户端实现

    • ServerSocketChannel 允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel,一般是服务器实现

    buffer(缓冲区)

    Java NIO中使用的核心缓冲区如下:

    • ByteBuffer

    • CharBuffer

    • DoubleBuffer

    • FloatBuffer

    • IntBuffer

    • LongBuffer

    • ShortBuffer

    Selector

    Selector允许一个线程处理多个channel,比如在聊天服务器中,可能会打开许多连接(channel),但是每个连接(channel)的流量很低,这种情况下,selector这种机制就很适合啦。

    使用buffer读写数据

    利用Buffer读写数据的步骤:

    • 把数据写入buffer

    • 调用flip

    • 从Buffer中读取数据

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

    当写入数据到buffer中时,buffer会记录已经写入的数据大小。当需要读数据时,通过 flip() 方法把buffer从写模式切换为读模式;在读模式下,可以读取所有已经写入的数据。 当读取完数据后,需要清空buffer,以便后续写入操作。清空buffer有两种方式:调用 clear() 或 compact() 方法。clear会清空整个buffer,compact则只清空已读取的数据,未被读取的数据会被移动到buffer的开始位置,写入位置则近跟着未读数据之后。

    Buffer的容量,位置,上限(Buffer Capacity, Position and Limit)

    Buffer缓冲区实质上就是一块内存,用于写入数据,也供后续再次读取数据。这块内存被NIO Buffer管理,并提供一系列的方法用于更简单的操作这块内存。 一个Buffer有三个属性是必须掌握的,分别是:

    • capacity容量

    • position位置

    • limit限制

    position和limit的具体含义取决于当前buffer的模式。capacity在两种模式下都表示容量。 下面有张示例图,描诉了读写模式下position和limit的含义:

     

    容量(Capacity) 作为一块内存,buffer有一个固定的大小,叫做capacit(容量)。也就是最多能写入容量值的字节,整形等数据。一旦buffer写满了就需要清空已读数据以便下次继续写入新的数据.

    位置(Position) 当写入数据到Buffer的时候需要从一个确定的位置开始,默认初始化时这个位置position为0,一旦写入了数据比如一个字节,整形数据,那么position的值就会指向数据之后的一个单元,position最大可以到capacity-1.

    当从Buffer读取数据时,也需要从一个确定的位置开始。buffer从写入模式变为读取模式时,position会归零,每次读取后,position向后移动。

    上限(Limit) 在写模式,limit的含义是我们所能写入的最大数据量,它等同于buffer的容量。

    分配一个buffer(Allocating a Buffer)

    在每个Buffer类中,allocate()方法用于分配缓冲区。 下面来看看ByteBuffer分配容量为28字节的例子:

    ByteBuffer buf = ByteBuffer.allocate(28);

    下面来看看另一个示例:CharBuffer分配空间大小为2048个字符

    CharBuffer buf = CharBuffer.allocate(2048);

    写数据到buffer(Writing Data to a Buffer)

    写数据到Buffer有两种方式

    1. 从Channel中写数据到Buffer

    2. 手动写数据到Buffer,通过调用put方法

    从Channel写数据到Buffer示例:

     int bytesRead = inChannel.read(buf); //read into buffer.

    通过put写数据示例:

    buf.put(127);

    put方法有很多不同版本,对应不同的写数据方法。例如把数据写到特定的位置,或者把一个字节数据写入buffer。看考JavaDoc文档可以查阅的更多数据。

    flip()切换

    flip()方法可以把Buffer从写模式切换到读模式。调用flip方法会把position归零,并设置limit为之前的position的值。 也就是说,现在position代表的是读取位置,limit标示的是已写入的数据位置。

    从Buffer读取数据(Reading Data from a Buffer)

    从Buffer读数据也有两种方式

    1. 从buffer读数据到channel

    2. 从buffer直接读取数据,调用get方法

    读取数据到channel的例子:

    int bytesWritten = inChannel.write(buf);

    调用get读取数据的例子:

    byte aByte = buf.get();

    get也有诸多版本,对应了不同的读取方式。

    rewind()

    Buffer.rewind()方法将position置为0,这样我们可以重复读取buffer中的数据。limit保持不变。

    clear() and compact()

    • 一旦我们从buffer中读取完数据,需要复用buffer为下次写数据做准备。只需要调用clear()或compact()方法。

    • 如果调用的是clear()方法,position将被设置为0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

    • 如果Buffer还有一些数据没有读取完,调用clear就会导致这部分数据被“遗忘”,因为我们没有标记这部分数据未读。 针对这种情况,如果需要保留未读数据,那么需要使用compact()。

    • 因此 compact() 和 clear() 的区别就在于: 对未读数据的处理,是保留还是一起清空 。

    mark()与reset()方法

    通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:

    buffer.mark();
    //call buffer.get() a couple of times, e.g. during parsing.
    buffer.reset();  //set position back to mark.    

    equals() and compareTo()

    可以用eqauls和compareTo比较两个buffer equals():

    判断两个buffer相对,需满足:

    • 类型相同

    • buffer中剩余字节数相同

    • 所有剩余字节相等

    从上面的三个条件可以看出,equals只比较buffer中的部分内容,并不会去比较每一个元素。 compareTo(): compareTo也是比较buffer中的剩余元素,只不过这个方法适用于比较排序的:

    一个简单的java NIO读写数据例子

            RandomAccessFile aFile = new RandomAccessFile("I:\nio-data.txt", "rw");
           FileChannel inChannel = aFile.getChannel();

           //分配一个48字节的缓冲区
           ByteBuffer buf = ByteBuffer.allocate(48);
           //读取数据到buffer,会记录当前已经读到buffer中数据的大小
           int bytesRead = inChannel.read(buf);
           while (bytesRead != -1) {

               System.out.println("Read " + bytesRead);
               //使用flip()方法把Buffer从写模式切换到读模式
               buf.flip();

               while(buf.hasRemaining()){
                   //reads data from Buffer
                   System.out.print((char) buf.get());//每次读取出一个字节
              }

               //buffer已经完全读完,将buffer清空,为写做准备
               buf.clear();

               //接着读数据到buffer
               bytesRead = inChannel.read(buf);
          }
           aFile.close();

     

    参考:

    http://tutorials.jenkov.com/java-nio/index.html

    https://github.com/wangzhiwubigdata

  • 相关阅读:
    innobackupex备份命令输出
    Percona XtraBackup原理详解
    MongoDB性能分析工具mongostat
    MongoDB查看当前连接数
    事务、拦截器
    HttpServletResponse和HttpServletRequest的简单实用
    Maven环境配置
    SQL Server 时间戳与时间格式互相转换
    虚拟机、云主机、VPS 三者之间的区别
    Elasticsearch 空值过滤
  • 原文地址:https://www.cnblogs.com/zz-ksw/p/12814123.html
Copyright © 2020-2023  润新知