`

Java NIO缓冲区内部实现机制

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

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

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

在从通道读取时,所读取的数据将放被到底层的数组中;同理,向通道中写入时,将从底层数组中将数据写入通道。下面我们来具体介绍这三个变量的作用:
position
  a)    position
   position变量跟踪了向缓冲区中写入了多少数据或者从缓冲区中读取了多少数据。
   更确切的说,当您从通道中读取数据到缓冲区中时,它指示了下一个数据将放到数组的哪一个元素中。比如,如果您从通道中读三个字节到缓冲区中,那么缓冲区的position将会设置为3,指向数组中第4个元素。反之,当您从缓冲区中获取数据进行写通道时,它指示了下一个数据来自数组的哪一个元素。比如,当您从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。
Position 
The index of the next element to be read or written. The position is updated  automatically by relative get( ) and put( )

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

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

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

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

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


回想一下 ,limit决不能大于capacity,此例中这两个值都被设置为8。我们通过将它们指向数组的尾部之后(第8个槽位)来说明这点。


  我们再将position设置为0。表示如果我们读一些数据到缓冲区中,那么下一个读取的数据就进入 slot 0。如果我们从缓冲区写一些数据,从缓冲区读取的下一个字节就来自slot 0。position设置如下所示:

由于缓冲区的最大数据容量capacity不会改变,所以我们在下面的讨论中可以忽略它。

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


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


flip:
   现在我们要将数据写到输出通道中。在这之前,我们必须调用flip()方法。 其源代码如下:
public final Buffer flip() {  
    limit = position;  
    position = 0;  
    mark = -1;  
    return this;  
}  

这个方法做两件非常重要的事:
   i  它将limit设置为当前position。
   ii 它将position设置为0。
   上一个图显示了在flip之前缓冲区的情况。下面是在flip之后的缓冲区:


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

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


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


clear:
   最后一步是调用缓冲区的clear()方法。这个方法重设缓冲区以便接收更多的字节。其源代码如下:
public final Buffer clear() {  
    osition = 0;  
    limit = capacity;  
    mark = -1;  
    return this;  
}  

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


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()方法用于让缓冲区在读和写之间切换。

参考:http://zhangshixi.iteye.com/blog/681704
  • 大小: 2.8 KB
  • 大小: 5.1 KB
  • 大小: 4.2 KB
  • 大小: 3.8 KB
  • 大小: 3.8 KB
  • 大小: 3.9 KB
  • 大小: 3.9 KB
  • 大小: 3.8 KB
  • 大小: 3.5 KB
分享到:
评论

相关推荐

    Java nio源码

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

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

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

    JavaNIO_API帮助文档详解

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

    java网络编程NIO视频教程

    Java NIO-Buffer-只读缓冲区 - **主要内容**:解释如何创建只读缓冲区以及其应用场景。 - **学习目标**:理解只读缓冲区的用途。 #### 19. Java NIO-Buffer-直接缓冲区 - **主要内容**:介绍直接缓冲区的特性和...

    Java NIO介绍

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

    JAVA_NIO学习总结

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

    DatagramChannelImpl.rar_java nio

    - **缓冲区管理**:处理`ByteBuffer`的读写操作,包括缓冲区的分配、复制和释放。 - **系统调用封装**:封装了底层的网络操作,如接收和发送数据包,以及处理错误和异常。 - **多线程同步**:由于可能涉及多线程并发...

    Java-NIO-Netty框架学习

    3. **高性能的缓冲区**:Netty使用ByteBuf作为缓冲区,相较于Java原生的ByteBuffer,提供了更丰富的操作接口和更优秀的性能。 4. **强大的编码解码器**:Netty提供了一系列预定义的编解码器,用于处理各种常见协议...

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

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

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

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

    Java_NIO_API详解

    NIO提供了一种基于缓冲区(Buffer)的非阻塞I/O操作机制,极大地提高了I/O处理的性能和效率。本文将详细介绍NIO的主要组成部分及其实现原理,帮助开发者更好地理解和应用NIO技术。 #### 二、NIO API概述 NIO API...

    主题JAVANIO简介知识点.pdf

    ### 主题JAVANIO简介知识点 #### 一、基本概念与Java标准IO回顾 **基本概念** 在计算机科学中,I/O(Input/Output,输入/输出)是指主存和外部设备(如硬盘、终端、网络等)之间拷贝数据的过程。I/O操作是操作...

    NIO入门pdf分享

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

    Nio和Io的详细描述.docx

    Java NIO的核心组件包括通道、缓冲区和选择器。此外,NIO还有许多其他类和组件,如文件通道、套接字通道、服务器套接字通道、散列通道、管道等,它们共同构成了一个强大且灵活的IO框架,为开发者提供了处理高并发和...

    基于Java_NIO_开发高性能并发型服务器程序的研究.pdf

    NIO的核心组件包括Buffer(缓冲区)、Channel(通道)和Selector(选择器)。 - **Buffer(缓冲区)**:缓冲区是用于存放数据的容器。在NIO中,所有的数据都是通过缓冲区进行读写的。Buffer内部维护了一个字节数组...

    使用Java_NIO编写高性能的服务器.doc

    NIO的核心特性包括缓冲区(Buffer)、通道(Channel)和选择器(Selector)。 - **非阻塞IO**:非阻塞IO允许程序在等待IO操作完成时不会阻塞当前线程,这使得单个线程能够管理更多的并发连接,提高了资源利用率和应用程序...

    Android-LightKV是基于JavaNIO的轻量级高性能高可靠的key-value存储组件

    通过阅读源码,可以深入理解其内部机制,例如如何使用NIO进行文件读写,如何实现并发控制,以及如何进行数据序列化和反序列化等。 **5. 应用场景** LightKV适用于需要快速本地存储的应用场景,如: - 缓存用户设置...

    NIO实例

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

Global site tag (gtag.js) - Google Analytics