`
zhangshixi
  • 浏览: 671858 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

NIO学习系列:缓冲区内部实现机制

阅读更多

接上一篇NIO学习系列:核心概念及基本读写 ,本文继续探讨和学习缓冲区的内部实现机制。

5.    缓冲区内部实现
   从上面对NIO的学习中,我们知道每一个缓冲区都有复杂的内部统计机制,它会跟踪已经读了多少数据以及还有多少空间可以容纳更多的数据,以便我们对缓冲区的操作。在本节我们就将学习NIO的两个重要的缓冲区组件:状态变量和访问方法。虽然NIO的内部统计机制初看起来可能很复杂,但是您很快就会看到大部分的实际工作都已经替您完成了。您只需像平时使用字节数组和索引变量一样进行操作即可。


   1)    状态变量:
   状态变量是前一节中提到的"内部统计机制"的关键。 每一个读/写操作都会改变缓冲区的状态。通过记录和跟踪这些变化,缓冲区就可能够内部地管理自己的资源。
   每一种Java基本类型的缓冲区都是抽象类Buffer的子类,从Buffer的源代码中可以发现,它定义了三个私有属性:

private int position = 0;
private int limit;
private int capacity;

   实际上,这三个属性值可以指定缓冲区在任意时刻的状态和它所包含的数据。
   我们知道,每一个基本类型的缓冲区底层实际上就是一个该类型的数组。如在ByteBuffer中,有:

final byte[] hb;

   在从通道读取时,所读取的数据将放被到底层的数组中;同理,向通道中写入时,将从底层数组中将数据写入通道。下面我们来具体介绍这三个变量的作用:


   a)    position
   position变量跟踪了向缓冲区中写入了多少数据或者从缓冲区中读取了多少数据。
   更确切的说,当您从通道中读取数据到缓冲区中时,它指示了下一个数据将放到数组的哪一个元素中。比如,如果您从通道中读三个字节到缓冲区中,那么缓冲区的position将会设置为3,指向数组中第4个元素。反之,当您从缓冲区中获取数据进行写通道时,它指示了下一个数据来自数组的哪一个元素。比如,当您从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。


   b)    limit
   limit变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
   position总是小于或者等于limit。

   c)    capacity
   capacity变量表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小—或者至少是指定了准许我们使用的底层数组的容量。
   limit总是小于或者等于capacity。

 

   d)    举例说明:

   下面我们就以数据从一个输入通道拷贝到一个输出通道为例,来详细分析每一个变量,并说明它们是如何协同工作的:


   初始变量:
   我们首先观察一个新创建的缓冲区,以ByteBuffer为例,假设缓冲区的大小为8个字节,ByteBuffer初始状态如下:

NIO缓冲区内部实现机制
   回想一下 ,limit决不能大于capacity,此例中这两个值都被设置为8。我们通过将它们指向数组的尾部之后(第8个槽位)来说明这点。
NIO缓冲区内部实现机制
   我们再将position设置为0。表示如果我们读一些数据到缓冲区中,那么下一个读取的数据就进入 slot 0。如果我们从缓冲区写一些数据,从缓冲区读取的下一个字节就来自slot 0。position设置如下所示:
NIO缓冲区内部实现机制
   由于缓冲区的最大数据容量capacity不会改变,所以我们在下面的讨论中可以忽略它。

   第一次读取:
   现在我们可以开始在新创建的缓冲区上进行读/写操作了。首先从输入通道中读一些数据到缓冲区中。第一次读取得到三个字节。它们被放到数组中从position开始的位置,这时position被设置为0。读完之后,position就增加到了3,如下所示,limit没有改变。
NIO缓冲区内部实现机制

   第二次读取:
   在第二次读取时,我们从输入通道读取另外两个字节到缓冲区中。这两个字节储存在由position所指定的位置上, position因而增加2,limit没有改变。
NIO缓冲区内部实现机制

   flip:
   现在我们要将数据写到输出通道中。在这之前,我们必须调用flip()方法。 其源代码如下:

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

   这个方法做两件非常重要的事:
   i  它将limit设置为当前position。
   ii 它将position设置为0。

   上一个图显示了在flip之前缓冲区的情况。下面是在flip之后的缓冲区:

NIO缓冲区内部实现机制

   我们现在可以将数据从缓冲区写入通道了。position被设置为0,这意味着我们得到的下一个字节是第一个字节。limit已被设置为原来的position,这意味着它包括以前读到的所有字节,并且一个字节也不多。

   第一次写入:
   在第一次写入时,我们从缓冲区中取四个字节并将它们 写入输出通道。这使得position增加到4,而limit不变,如下所示:

NIO缓冲区内部实现机制


   第二次写入:
   我们只剩下一个字节可写了。limit在我们调用flip()时被设置为5,并且position不能超过limit。 所以最后一次写入操作从缓冲区取出一个字节并将它写入输出通道。这使得position增加到5,并保持limit不变,如下所示:
NIO缓冲区内部实现机制

   clear:
   最后一步是调用缓冲区的clear()方法。这个方法重设缓冲区以便接收更多的字节。其源代码如下:

public final Buffer clear() {
    osition = 0;
    limit = capacity;
    mark = -1;
    return this;
}

   clear做两种非常重要的事情:
   i 它将limit设置为与capacity相同。
   ii 它设置position为0。
   下图显示了在调用clear()后缓冲区的状态, 此时缓冲区现在可以接收新的数据了。

NIO缓冲区内部实现机制

 

   2)    访问方法:
   到目前为止,我们只是使用缓冲区将数据从一个通道转移到另一个通道。然而,程序经常需要直接处理数据。例如,您可能需要将用户数据保存到磁盘。在这种情况下,您必须将这些数据直接放入缓冲区,然后用通道将缓冲区写入磁盘。 或者,您可能想要从磁盘读取用户数据。在这种情况下,您要将数据从通道读到缓冲区中,然后检查缓冲区中的数据。
   实际上,每一个基本类型的缓冲区都为我们提供了直接访问缓冲区中数据的方法,我们以ByteBuffer为例,分析如何使用其提供的get()和put()方法直接访问缓冲区中的数据。

 

   a)    get()
   ByteBuffer类中有四个get()方法:

byte get();
ByteBuffer get( byte dst[] );
ByteBuffer get( byte dst[], int offset, int length );
byte get( int index ); 

   第一个方法获取单个字节。第二和第三个方法将一组字节读到一个数组中。第四个方法从缓冲区中的特定位置获取字节。那些返回ByteBuffer的方法只是返回调用它们的缓冲区的this值。
   此外,我们认为前三个get()方法是相对的,而最后一个方法是绝对的。“相对”意味着get()操作服从limit和position值,更明确地说,字节是从当前position读取的,而position在get之后会增加。另一方面,一个“绝对”方法会忽略limit和position值,也不会影响它们。事实上,它完全绕过了缓冲区的统计方法。
   上面列出的方法对应于ByteBuffer类。其他类有等价的get()方法,这些方法除了不是处理字节外,其它方面是是完全一样的,它们处理的是与该缓冲区类相适应的类型。

   b)    put()
   ByteBuffer类中有五个put()方法:

ByteBuffer put( byte b );
ByteBuffer put( byte src[] );
ByteBuffer put( byte src[], int offset, int length );
ByteBuffer put( ByteBuffer src );
ByteBuffer put( int index, byte b ); 

   第一个方法 写入(put)单个字节。第二和第三个方法写入来自一个数组的一组字节。第四个方法将数据从一个给定的源ByteBuffer写入这个ByteBuffer。第五个方法将字节写入缓冲区中特定的 位置 。那些返回ByteBuffer的方法只是返回调用它们的缓冲区的this值。
   与get()方法一样,我们将把put()方法划分为“相对”或者“绝对”的。前四个方法是相对的,而第五个方法是绝对的。
   上面显示的方法对应于ByteBuffer类。其他类有等价的put()方法,这些方法除了不是处理字节之外,其它方面是完全一样的。它们处理的是与该缓冲区类相适应的类型。

   c)    类型化的 get() 和 put() 方法
   除了前些小节中描述的get()和put()方法, ByteBuffer还有用于读写不同类型的值的其他方法,如下所示:
   getByte()
   getChar()
   getShort()
   getInt()
   getLong()
   getFloat()
   getDouble()
   putByte()
   putChar()
   putShort()
   putInt()
   putLong()
   putFloat()
   putDouble()
   事实上,这其中的每个方法都有两种类型:一种是相对的,另一种是绝对的。它们对于读取格式化的二进制数据(如图像文件的头部)很有用。

 

   3)    如何使用?
   下面的内部循环概括了使用缓冲区将数据从输入通道拷贝到输出通道的过程。

while (true) {
     buffer.clear();
     int r = fcin.read( buffer );

     if (r==-1) {
       break;
     }

     buffer.flip();
     fcout.write( buffer );
}

   read()和write()调用得到了极大的简化,因为许多工作细节都由缓冲区完成了。clear()和flip()方法用于让缓冲区在读和写之间切换。

 

后续: 在下一篇文章中,会具体介绍连网和非阻塞IO的原理及使用,这也是NIO重要的一部分。有兴趣的可以共同学习、讨论。

33
7
分享到:
评论
12 楼 kingquake21 2011-05-20  
讲解的非常细致,赞
摘抄一段话
Capacity
The maximum number of data elements the buffer can hold. The capacity is set when
the buffer is created and can never be changed. 
Limit
The first element of the buffer that should not be read or written. In other words, the count of live elements in the buffer. 
Position 
The index of the next element to be read or written. The position is updated  automatically by relative get( ) and put( ) methods. 
Mark  
A remembered position. Calling mark( ) sets mark = position. Calling  reset( ) sets position = mark. The mark is undefined until set. 

The following relationship between these four attributes always holds:
0 <= mark <= position <= limit <= capacity 
11 楼 lzh20044178 2011-03-06  
limit变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
   position总是小于或者等于limit

根据后面的讲解,这里对limit的解释应该是问题的。
表示有多少数据需要取出 是在反转之后,表示有多少空间可以放入数据,是在第一次存入数据之前。如果连续2次存入数据,在第二次的时候,limit的表示的意思就不再是还有多少空间可以放入数据了。
同时,position总小于或者等于limit也难以理解。没有限制条件,limit被理解为一个实时标示的动态变量。虽然它确实是可以动态改变,但不是存一次就改变一次。

刚看的时候就觉得这里理解很别扭。
欢迎指正我的想法。
10 楼 sanshao 2011-01-12  
楼主的图画滴不错!!!清晰易懂。。。
9 楼 luo877280 2010-11-25  
   
8 楼 nada_forever 2010-11-17  
哥们,写的非常好~
7 楼 yangguo 2010-07-14  
中国就缺少像楼主这样深入浅出讲解学问的作家。
6 楼 yiihsia 2010-07-06  
对我帮助很大
5 楼 wgy_superpower 2010-06-13  
相当不错,真的,希望博主坚持写,很期待博主的下一篇哦  
4 楼 archerfrank 2010-06-05  
期待下一篇 
3 楼 zhangshixi 2010-06-03  
个人觉得其实Java IO的设计也复杂化了很多事情,NIO在改善了性能的同时,也以较简单的概念做了较大改进,认真学习一下,用起来还是很好的。
icefire 写道
NIO用起来太累了。基本也只有在改善性能时才用用。一直就觉得JAVA IO太复杂了。

2 楼 icefire 2010-06-03  
NIO用起来太累了。基本也只有在改善性能时才用用。一直就觉得JAVA IO太复杂了。
1 楼 langyu 2010-06-02  
说的非常详细,感谢楼主的分享

相关推荐

    JAVA Nio 学习探究

    包含NIO核心概念、基本文件读写、缓冲区内部实现机制、异步IO、缓冲区更多特性探究、文件锁与字符集

    Java NIO介绍

    概述 基本概念 基本读写 缓冲区内部机制 异步IO 总结

    1_6_zh_CN.CHM

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    JDK_API_1_6_zh_CN_downcc.com.zip 良心一级分

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    JAVA上百实例源码以及开源项目源代码

     Java数据压缩与传输实例,可以学习一下实例化套按字、得到文件输入流、压缩输入流、文件输出流、实例化缓冲区、写入数据到文件、关闭输入流、关闭套接字关闭输出流、输出错误信息等Java编程小技巧。 Java数组倒置...

    JAVA_API1.6文档(中文)

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    JAVA上百实例源码以及开源项目

     Java数据压缩与传输实例,可以学习一下实例化套按字、得到文件输入流、压缩输入流、文件输出流、实例化缓冲区、写入数据到文件、关闭输入流、关闭套接字关闭输出流、输出错误信息等Java编程小技巧。 Java数组倒置...

    java api最新7.0

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    JDK_1_6 API

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    Java 1.6 API 中文 New

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    [Java参考文档].JDK_API 1.6

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    JavaAPI1.6中文chm文档 part1

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    JavaAPI中文chm文档 part2

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    [Java参考文档]

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    java jdk-api-1.6 中文 chmd

    java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的...

    java核心知识点整理.pdf

    本地方法区(线程私有) ................................................................................................................ 23 2.2.4. 堆(Heap-线程共享)-运行时数据区 ...........................

Global site tag (gtag.js) - Google Analytics