Java NIO2:缓冲区
什么是缓冲区
一个缓冲区对象是固定数量的数据的容器,其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索。缓冲区像前篇文章讨论的那样被写满和释放,对于每个非布尔原始数据类型都有一个缓冲区类,尽管缓冲区作用于它们存储的原始数据类型,但缓冲区十分倾向于处理字节,非字节缓冲区可以再后台执行从字节或到字节的转换,这取决于缓冲区是如何创建的。
缓冲区的工作与通道紧密联系。通道是I/O传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。对于离开缓冲区的传输,待传递出去的数据被置于一个缓冲区,被传送到通道;待传回的缓冲区的传输,一个通道将数据放置在所提供的缓冲区中。这种在协同对象之间进行的缓冲区数据传递时高效数据处理的关键。
Buffer类的家谱
下图是Buffer的类层次图。在顶部是通用Buffer类,Buffer定义所有缓冲区类型共有的操作,无论是它们所包含的数据类型还是可能具有的特定行为:

缓冲区基础
概念上,缓冲区是包在一个对象内的基本数据元素数组。Buffer类相比一个简单数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中,Buffer类以及它专有的子类定义了一个用于处理数据缓冲区的API。下面来看一下Buffer类所具有的属性和方法:
1、属性
所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息,它们是:
| 属 性 | 作 用 |
| capacity | 容量,指缓冲区能够容纳的数据元素的最大数量,这一容量在缓冲区创建时被设定,并且永远不能被改变 |
| limit | 上界,指缓冲区的第一个不能被读或写的元素,或者说是,缓冲区中现存元素的计数 |
| position | 位置,指下一个要被读或写的元素的索引,位置会自动由相应的get()和put()函数更新 |
| mark | 标记,指一个备忘位置,调用mark()来设定mark=position,调用reset()来设定postion=mark,标记未设定前是未定义的 |
这四个属性总是遵循以下的关系:0 <= mark <= position <= limit <= capacity
2、方法
下面看一下如何使用一个缓冲区,Buffer中提供了以下的一些方法:
| 方 法 | 作 用 |
| Object array() | 返回此缓冲区的底层实现数组 |
| int arrayOffset() | 返回此缓冲区的底层实现数组中第一个缓冲区还俗的偏移量 |
| int capacity() | 返回此缓冲区的容量 |
| Buffer clear() | 清除此缓冲区 |
| Buffer flip() | 反转此缓冲区 |
| boolean hasArray() | 告知此缓冲区是否具有可访问的底层实现数组 |
| boolean hasRemaining() | 告知在当前位置和限制之间是否有元素 |
| boolean isDirect() | 告知此缓冲区是否为直接缓冲区 |
| boolean isReadOnly() | 告知此缓冲区是否为只读缓存 |
| int limit() | 返回此缓冲区的上界 |
| Buffer limit(int newLimit) | 设置此缓冲区的上界 |
| Buffer mark() | 在此缓冲区的位置设置标记 |
| int position() | 返回此缓冲区的位置 |
| Buffer position(int newPosition) | 设置此缓冲区的位置 |
| int remaining() | 返回当前位置与上界之间的元素数 |
| Buffer reset() | 将此缓冲区的位置重置为以前标记的位置 |
| Buffer rewind() | 重绕此缓冲区 |
关于这个API有一点值得注意的,像clear()这类函数,通常应当返回的是void而不是Buffer引用。这些函数将引用返回到它们在(this)上被引用的对象,这是一个允许级联调用的类设计方法。级联调用允许这种类型的代码:
buffer.mark();
buffer.position(5);
buffer.reset();
被简写成:
buffer.mark().position(5).reset();
缓冲区代码实例
对缓冲区的使用,先看一段代码,然后解释一下:
public class TestMain
{
/**
* 待显示的字符串
*/
private static String[] strs =
{
"A random string value",
"The product of an infinite number of monkeys",
"Hey hey we're the monkees",
"Opening act for the Monkees:Jimi Hendrix",
"Scuse me while I kiss this fly",
"Help Me! Help Me!"
}; /**
* 标识strs的下标索引
*/
private static int index = 0; /**
* 向Buffer内放置数据
*/
private static boolean fillBuffer(CharBuffer buffer)
{
if (index >= strs.length)
return false; String str = strs[index++];
for (int i = 0; i < str.length(); i++)
{
buffer.put(str.charAt(i));
} return true;
} /**
* 从Buffer内把数据拿出来
*/
private static void drainBuffer(CharBuffer buffer)
{
while (buffer.hasRemaining())
{
System.out.print(buffer.get());
}
System.out.println("");
} public static void main(String[] args)
{
CharBuffer cb = CharBuffer.allocate(100);
while (fillBuffer(cb))
{
cb.flip();
drainBuffer(cb);
cb.clear();
}
}
}
逐一解释一下:
1、第52行,CharBuffer是一个抽象类,它不能被实例化,因此利用allocate方法来实例化,相当于是一个工厂方法。实例化出来的是HeapCharBuffer,默认大小是100。根据上面的Buffer的类家族图谱,可以看到每个Buffer的子类都是使用allocate方法来实例化具体的子类的,且实例化出来的都是Heap*Buffer。
2、第24行~第36行,每次取String数组中的一个,利用put方法放置一个数据进入CharBuffer中
3、第55行,调用flip方法,这是非常重要的。在缓冲区被写满后,必须将其清空,但是如果现在在通道上直接执行get()方法,那么它将从我们刚刚插入的有用数据之外取出未定义数据;如果此时将位置重新设置为0,就会从正确的位置开始获取数据,但是如何知道何时到达我们所插入数据末端呢?这就是上界属性被引入的目的----上界属性指明了缓冲区有效内容的末端。因此,在读取数据的时候我们需要做两件事情:
(1)将上界属性limit设置为当前位置 (2)将位置position设置为0
这两步操作,JDK API给开发者提供了一个filp()方法来完成,flip()方法将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态,因此每次准备读出元素前,都必须调用一次filp()方法
4、第41行~第48行,每次先判断一下是否已经达到缓冲区的上界,若存在则调用get()方法获取到此元素,get()方法会自动移动下标position
5、第57行,对Buffer的操作完成之后,调用clear()方法将所有属性回归原位,但是clear()方法并不会改变缓冲区中的任何数据
缓冲区比较
缓冲区的比较即equals方法,缓冲区的比较并不像我们想像得这么简单,两个缓冲区里面的元素一样就是相等,两个缓冲区相等必须满足以下三个条件:
1、两个对象类型相同,包含不同数据类型的buffer永远不会像等,而且buffer绝不会等于非buffer对象
2、两个对象都剩余相同数量的元素,Buffer的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。但每个缓冲区中剩余元素的数目(从position到limit)必须相同
3、在每个缓冲区中应被get()函数返回的剩余数据元素序列必须一致
如果不满足上面三个条件,则返回false。下面两幅图演示了两个缓冲区相等和不相等的场景,首先是两个属性不同的缓冲区也可以相等:

然后是两个属性相同但是被等为不相等的缓冲区:

批量移动数据
缓冲区的设计目的就是为了能够高效地传输数据。一次移动一个数据元素,其实并不高效,如在下面的程序清单中所看到的那样,Buffer API提供了向缓冲区内外批量移动数据元素的函数:
public abstract class CharBuffer
extends Buffer
implements Comparable<CharBuffer>, Appendable, CharSequence, Readable
{
...
public CharBuffer get(char[] dst){...}
public CharBuffer get(char[] dst, int offset, int length){...}
public final CharBuffer put(char[] src){...}
public CharBuffer put(char[] src, int offset, int length){...}
public CharBuffer put(CharBuffer src){...}
public final CharBuffer put(String src){...}
public CharBuffer put(String src, int start, int end){...}
...
}
其实这种批量移动的合成效果和前文的循环在底层实现上是一样的,但是这些方法可能高效得多,因为这种缓冲区实现能够利用本地代码或其他的优化来移动数据。
字节缓冲区
字节缓冲区和其他缓冲区类型最明显的不同在于,它们可能成为通道所执行I/O的源头或目标,如果对NIO有了解的朋友们一定知道,通道只接收ByteBuffer作为参数。
如我们所知道的,操作系统在内存区域进行I/O操作,这些内存区域,就操作系统方面而言,是相连的字节序列。于是,毫无疑问,只有字节缓冲区有资格参与I/O操作。也请回想一下操作系统会直接存取进程----在本例中是JVM进程的内存空间,以传输数据。这也意味着I/O操作的目标内存区域必须是连续的字节序列,在JVM中,字节数组可能不会在内存中连续存储,或者无用存储单元收集可能随时对其进行移动。在Java中,数组是对象,而数据存储在对象中的方式在不同的JVM实现中各有不同。
出于这一原因,引入了直接缓冲区的概念。直接缓冲区被用于与通道和固有I/O线程交互,它们通过使用固有代码来告知操作系统直接释放或填充内存区域,对用于通道直接或原始存取的内存区域中的字节元素的存储尽了最大的努力。
直接字节缓冲区通常是I/O操作最好的选择。在设计方面,它们支持JVM可用的最高效I/O机制,非直接字节缓冲区可以被传递给通道,但是这样可能导致性能损耗,通常非直接缓冲不可能成为一个本地I/O操作的目标,如果开发者向一个通道中传递一个非直接ByteBuffer对象用于写入,通道可能会在每次调用中隐含地进行下面的操作:
1、创建一个临时的直接ByteBuffer对象
2、将非直接缓冲区的内容复制到临时缓冲中
3、使用临时缓冲区执行低层次I/O操作
4、临时缓冲区对象离开作用于,并最终成为被回收的无用数据
这可能导致缓冲区在每个I/O上复制并产生大量对象,而这种事都是我们极力避免的。
直接缓冲区是I/O的最佳选择,但可能比创建非直接缓冲区要花费更高的成本。直接缓冲区使用的内存是通过调用本地操作系统的代码分配的,绕过了标准JVM堆栈。建立和销毁直接缓冲区会明显比具有堆栈的缓冲区更极爱破费,这取决于主操作系统以及JVM实现。直接缓冲区的内存区域不受无用存储单元收集支配,因为它们位于标准JVM堆栈之外。
直接ByteBuffer是通过调用具有所需容量的ByteBuffer.allocateDirect()函数产生的:
public abstract class ByteBuffer
extends Buffer
implements Comparable<ByteBuffer>
{
...
public static ByteBuffer allocateDirect(int capacity)
{
return new DirectByteBuffer(capacity);
}
...
}
Java NIO2:缓冲区的更多相关文章
- Java NIO2 File API介绍
Introduction to the Java NIO2 File API GitHub NIO2中的文件API是Java 7附带的Java平台的主要新功能之一,特别是新的文件系统API的一个子集以 ...
- java nio 缓冲区(一)
本文来自于我的个人博客:java nio 缓冲区(一) 我们以Buffer类開始对java.nio包的浏览历程.这些类是java.nio的构造基础. 这个系列中,我们将尾随<java NIO ...
- java nio2
Buffer的基本用法 使用Buffer读写数据一般遵循以下四个步骤: 写入数据到Buffer 调用flip()方法 从Buffer中读取数据 调用clear()方法或者compact()方法 当向b ...
- Java NIO 缓冲区学习笔记
Buffer其实就是是一个容器对象,它包含一些要写入或者刚读出的数据.在NIO中加入Buffer对象,体现了新库与原I/O的一个重要区别.在面向流的I/O中,您将数据直接写入或者将数据直接读到Stre ...
- Java自定义缓冲区MyBufferedReader
package IODemo; import java.io.FileReader; import java.io.IOException; import java.io.Reader; /** * ...
- Java NIO2:NIO概述
一.概述 从JDK1.4开始,Java提供了一系列改进的输入/输出处理的新特性,被统称为NIO(即New I/O).新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原j ...
- Java NIO -- 缓冲区(Buffer)的数据存取
缓冲区(Buffer): 一个用于特定基本数据类型的容器.由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类.Java NIO 中的 Buffer 主要用于与 NIO 通道进行 ...
- java nio 缓冲区读写数据(图形详解)
Position 您可以回想一下,缓冲区实际上就是美化了的数组.在从通道读取时,您将所读取的数据放到底层的数组中. position 变量跟踪已经写了多少数据.更准确地说,它指定了下一个字节将放到数组 ...
- JAVA NIO缓冲区(Buffer)------ByteBuffer常用方法
参考:https://blog.csdn.net/xialong_927/article/details/81044759 缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对输入/输出(I ...
随机推荐
- day4(homework)
第八单元 1) 将用户信息数据库文件和组信息数据库文件纵向合并为一个文件/1.txt(覆盖) 2) 将用户信息数据库文件和用户密码数据库文件纵向合并为一个文件/2.txt(追加) 3) 将/1.txt ...
- 大数据项目实践:基于hadoop+spark+mongodb+mysql+c#开发医院临床知识库系统
一.前言 从20世纪90年代数字化医院概念提出到至今的20多年时间,数字化医院(Digital Hospital)在国内各大医院飞速的普及推广发展,并取得骄人成绩.不但有数字化医院管理信息系统(HIS ...
- 通过配置web.config使WCF向外提供HTTPS的Restful Service
如何通过WCF向外提供Restful的Service请看如下链接 http://www.cnblogs.com/mingmingruyuedlut/p/4223116.html 那么如何通过对web. ...
- 深入浅出JMS(一) JMS基本概念
摘要:The JavaMessage Service (JMS) API is a messaging standard that allows application components base ...
- [原创]Centos7 从零整合LNMP一体包
按照前几章配置好后,我们就可以把这些工具打包啦.生成LNMP一体包. # export LD_LIBRARY_PATH=/package/libmemcached/lib:$LD_LIBRARY_PA ...
- Copy-On-Write容器
Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改, ...
- Android Studio中获取SHA1或MD5的方法
原来在Eclipse中获取SHA1或者MD5,在IDE界面上就可以查找到. 切换到Android Studio后,如何查看呢?找了半天没找到.那就老办法命令行. 第一步.打开Android Studi ...
- js模拟抛出球运动
js练手之模拟水平抛球运动 -匀加速运动 -匀减速运动 模拟运动有些基本的思路,当前所在点的坐标,元素的长宽是多少,向右/向下运动x/y增加,向上/向左运动x/y减少,运动的路程是多少,用什么方程进行 ...
- python 入门(一)矩阵处理
numpy 使用 1.使用 array 定义矩阵 dataSet = array([[1.0,1.1],[1.0,1.0],[0.0,0.0],[0,0.1]]) 2.使用 shape 返回矩阵的行数 ...
- 已知服务器ftp的账号密码,求解数据库表的内容
一开始觉得这两个是完全不相干的东西,直到出现了这样一个问题,对方网站只有ftp的账号密码,并且能正常访问到代码.但是当需求了解注册人数的时候,后台没有显示,只能到数据库去找,这时怎么找呢? 原来是可以 ...