接上一篇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初始状态如下:
回想一下 ,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()方法用于让缓冲区在读和写之间切换。
后续:
在下一篇文章中,会具体介绍连网和非阻塞IO的原理及使用,这也是NIO重要的一部分。有兴趣的可以共同学习、讨论。
分享到:
相关推荐
#### 缓冲区的内部实现机制 缓冲区的内部实现基于数组结构,提供了对数据的高效访问。缓冲区通过三个关键属性进行管理:capacity(容量)、position(位置)和limit(限制)。其中,capacity是指缓冲区的最大容量,...
##### 1.2 缓冲区内部实现机制 缓冲区(Buffer)是NIO中处理数据的基本单位,它是一个连续的内存区域,用于存储一定类型的数据。缓冲区的主要属性包括: - **容量(capacity)**:缓冲区能容纳的数据的最大数量。 - **...
对于NIO的学习,阅读源码也是提升理解的好方法,可以深入理解其内部工作机制。 总的来说,《NIO学习总结经典》这篇博客提供了关于Java NIO的全面概述,涵盖了核心概念、用法和实践技巧,是Java开发者提升I/O处理...
传统的IO基于流(Stream)和缓冲区(Buffer)操作,而NIO的核心在于通道(Channels)和缓冲区(Buffer)。 1. **通道(Channel)**:通道是数据传输的路径,可以想象为水管,它连接到数据源和目的地,如文件、套接字等。Java ...
2. **大文件传输**:由于NIO的缓冲区机制,处理大文件时可以减少磁盘I/O次数,提高效率。 3. **数据管道**:NIO的通道之间可以直接进行数据传输,无需经过CPU,减少了数据复制。 五、NIO源码分析 对于NIO的源码,...
缓冲区内部包含了一个数组,该数组用于存储数据。缓冲区有四个核心属性: - `position`:当前操作的位置。 - `limit`:读取或者写入数据的边界。 - `capacity`:缓冲区的最大容量。 - `mark`:标记位置,可选属性。 ...
解决这些问题通常需要深入理解NIO的工作原理,包括缓冲区(Buffer)、通道(Channel)、选择器(Selector)等核心概念,并且需要对Servlet容器的内部机制有一定的了解。 在提供的文件列表中,"src"目录可能包含了源...
标签中的"源码"可能意味着博客中会深入到NIO的内部实现,讨论相关类和方法的工作原理,这对于理解NIO机制及其性能优化至关重要。而"工具"可能是指博主介绍了一些辅助开发NIO应用的库或框架,例如Netty,它是一个基于...
- **学习目标**:掌握Buffer的内部机制和不同类型的Buffer。 #### 15. Java NIO-Buffer-分配和读写数据 - **主要内容**:演示如何分配Buffer以及如何通过Buffer进行数据的读写操作。 - **学习目标**:熟练使用...
Java NIO (Non-blocking Input/Output) 是Java平台中用于高效处理I/O操作的一种机制,它与传统的IO模型( Blocking I/O)相比,提供了...同时,阅读Netty的源码也是一个很好的学习途径,能让你更深入地了解其内部机制。
与标准IO API中的字节流和字符流不同,NIO操作基于通道(Channels)和缓冲区(Buffers)。数据总是从通道读入缓冲区,或者从缓冲区写入通道。 **Java NIO:选择器(Selectors)** 选择器是NIO中的一个重要概念,它...
在NIO中,关键组件包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。通道是数据传输的路径,它可以是文件、套接字或者网络连接。缓冲区用于存储数据,提供了更灵活的数据读写方式。选择器则用于监听...
### NIO陷阱与解读 #### 一、NIO概述及变迁 **NIO**(New I/O)是Java为提高I/O操作效率而引入的新特性...开发者需充分了解其内部机制,并结合具体的应用场景选择合适的设计方案和技术栈,才能真正发挥出NIO的优势。
Java NIO_API通过引入基于缓冲区的非阻塞I/O机制,显著提升了I/O处理的性能和灵活性,尤其是在高并发服务器应用中。通过对`Buffer`、`Channel`和`Selector`等核心概念的理解与运用,开发者能够构建出更加高效、响应...
NIO是一种现代I/O处理方法,通过引入缓冲区、通道和选择器等新概念,显著提升了文件处理和网络服务器程序的性能。本文首先概述了NIO的基本组件,然后分析了非阻塞通信的工作机制,并通过一个具体的网络应用实例展示...
下面我们将深入探讨Java NIO的核心概念和实现机制。 1. **通道(Channel)与缓冲区(Buffer)** - **通道(Channel)**:通道类似于流,但它们是双向的,可以读写数据。Java NIO提供了多种通道,如FileChannel、...
#### 缓冲区内部机制 缓冲区是Java NIO的核心组成部分之一,它的内部机制主要包括以下几个方面: 1. **容量(Capacity)**:缓冲区的最大容量,一旦定义后就不能改变。 2. **位置(Position)**:当前读写数据的位置,...
Java NIO提供了与标准I/O不同的I/O操作方式,主要基于Channel(通道)和Buffer(缓冲区)来进行数据的读写。NIO是事件驱动的,它在内部使用了事件循环机制,以达到高效处理大量并发连接的目的。NIO的核心组件是...