`
zhangshixi
  • 浏览: 675554 次
  • 性别: 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 学习探究

    ##### 1.2 缓冲区内部实现机制 缓冲区内部的实现涉及到了多个重要的属性: - **容量(Capacity)**:缓冲区的容量是指该缓冲区可以容纳的最大数据量,一旦定义不可改变。 - **位置(Position)**:表示当前操作的位置...

    JAVA_NIO学习总结

    #### 缓冲区的内部实现机制 缓冲区的内部实现基于数组结构,提供了对数据的高效访问。缓冲区通过三个关键属性进行管理:capacity(容量)、position(位置)和limit(限制)。其中,capacity是指缓冲区的最大容量,...

    NIO学习与总结

    ##### 1.2 缓冲区内部实现机制 缓冲区(Buffer)是NIO中处理数据的基本单位,它是一个连续的内存区域,用于存储一定类型的数据。缓冲区的主要属性包括: - **容量(capacity)**:缓冲区能容纳的数据的最大数量。 - **...

    NIO学习总结经典

    对于NIO的学习,阅读源码也是提升理解的好方法,可以深入理解其内部工作机制。 总的来说,《NIO学习总结经典》这篇博客提供了关于Java NIO的全面概述,涵盖了核心概念、用法和实践技巧,是Java开发者提升I/O处理...

    NIO入门pdf分享

    传统的IO基于流(Stream)和缓冲区(Buffer)操作,而NIO的核心在于通道(Channels)和缓冲区(Buffer)。 1. **通道(Channel)**:通道是数据传输的路径,可以想象为水管,它连接到数据源和目的地,如文件、套接字等。Java ...

    NIO学习总结

    2. **大文件传输**:由于NIO的缓冲区机制,处理大文件时可以减少磁盘I/O次数,提高效率。 3. **数据管道**:NIO的通道之间可以直接进行数据传输,无需经过CPU,减少了数据复制。 五、NIO源码分析 对于NIO的源码,...

    Java中的缓冲区(直接缓冲区、非直接缓冲区等).docx

    缓冲区内部包含了一个数组,该数组用于存储数据。缓冲区有四个核心属性: - `position`:当前操作的位置。 - `limit`:读取或者写入数据的边界。 - `capacity`:缓冲区的最大容量。 - `mark`:标记位置,可选属性。 ...

    Servlet API 和 NIO: 最终组合在一起

    解决这些问题通常需要深入理解NIO的工作原理,包括缓冲区(Buffer)、通道(Channel)、选择器(Selector)等核心概念,并且需要对Servlet容器的内部机制有一定的了解。 在提供的文件列表中,"src"目录可能包含了源...

    NIO实例

    标签中的"源码"可能意味着博客中会深入到NIO的内部实现,讨论相关类和方法的工作原理,这对于理解NIO机制及其性能优化至关重要。而"工具"可能是指博主介绍了一些辅助开发NIO应用的库或框架,例如Netty,它是一个基于...

    java网络编程NIO视频教程

    - **学习目标**:掌握Buffer的内部机制和不同类型的Buffer。 #### 15. Java NIO-Buffer-分配和读写数据 - **主要内容**:演示如何分配Buffer以及如何通过Buffer进行数据的读写操作。 - **学习目标**:熟练使用...

    Java-NIO-Netty框架学习

    Java NIO (Non-blocking Input/Output) 是Java平台中用于高效处理I/O操作的一种机制,它与传统的IO模型( Blocking I/O)相比,提供了...同时,阅读Netty的源码也是一个很好的学习途径,能让你更深入地了解其内部机制。

    Nio和Io的详细描述.docx

    与标准IO API中的字节流和字符流不同,NIO操作基于通道(Channels)和缓冲区(Buffers)。数据总是从通道读入缓冲区,或者从缓冲区写入通道。 **Java NIO:选择器(Selectors)** 选择器是NIO中的一个重要概念,它...

    大数据学习之旅——NIO源码

    在NIO中,关键组件包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。通道是数据传输的路径,它可以是文件、套接字或者网络连接。缓冲区用于存储数据,提供了更灵活的数据读写方式。选择器则用于监听...

    NIO trick and trap .pdf

    ### NIO陷阱与解读 #### 一、NIO概述及变迁 **NIO**(New I/O)是Java为提高I/O操作效率而引入的新特性...开发者需充分了解其内部机制,并结合具体的应用场景选择合适的设计方案和技术栈,才能真正发挥出NIO的优势。

    JavaNIO_API帮助文档详解

    Java NIO_API通过引入基于缓冲区的非阻塞I/O机制,显著提升了I/O处理的性能和灵活性,尤其是在高并发服务器应用中。通过对`Buffer`、`Channel`和`Selector`等核心概念的理解与运用,开发者能够构建出更加高效、响应...

    基于JavaNIO的非阻塞通信的研究与实现

    NIO是一种现代I/O处理方法,通过引入缓冲区、通道和选择器等新概念,显著提升了文件处理和网络服务器程序的性能。本文首先概述了NIO的基本组件,然后分析了非阻塞通信的工作机制,并通过一个具体的网络应用实例展示...

    Java nio源码

    下面我们将深入探讨Java NIO的核心概念和实现机制。 1. **通道(Channel)与缓冲区(Buffer)** - **通道(Channel)**:通道类似于流,但它们是双向的,可以读写数据。Java NIO提供了多种通道,如FileChannel、...

    Java NIO介绍

    #### 缓冲区内部机制 缓冲区是Java NIO的核心组成部分之一,它的内部机制主要包括以下几个方面: 1. **容量(Capacity)**:缓冲区的最大容量,一旦定义后就不能改变。 2. **位置(Position)**:当前读写数据的位置,...

    ScalableIOJava(NIO如何实现Selector模式的).pdf

    Java NIO提供了与标准I/O不同的I/O操作方式,主要基于Channel(通道)和Buffer(缓冲区)来进行数据的读写。NIO是事件驱动的,它在内部使用了事件循环机制,以达到高效处理大量并发连接的目的。NIO的核心组件是...

Global site tag (gtag.js) - Google Analytics