Java NIO4:缓冲区Buffer(续)
一、什么是缓冲区
一个缓冲区对象是固定数量的数据的容器,其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索。缓冲区像前篇文章讨论的那样被写满和释放,对于每个非布尔原始数据类型都有一个缓冲区类,尽管缓冲区作用于它们存储的原始数据类型,但缓冲区十分倾向于处理字节,非字节缓冲区可以在后台执行从字节或到字节的转换,这取决于缓冲区是如何创建的。
缓冲区的工作与通道紧密联系。通道是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();
四、缓冲区代码实例
对缓冲区的使用,先看一段代码,然后解释一下:
package com.demo.nio;
import java.nio.CharBuffer;
public class TestBuffer1 {
/**
* 待显示的字符串
*/
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、第28行~第37行,每次取String数组中的一个,利用put方法放置一个数据进入CharBuffer中。
3、第54行,调用flip方法,这是非常重要的。在缓冲区被写满后,必须将其清空,但是如果现在在通道上直接执行get()方法,那么它将从我们刚刚插入的有用数据之外取出未定义数据;如果此时将位置重新设置为0,就会从正确的位置开始获取数据,但是如何知道何时到达我们所插入数据末端呢?这就是上界属性被引入的目的----上界属性指明了缓冲区有效内容的末端。因此,在读取数据的时候我们需要做两件事情:
(1)将上界属性limit设置为当前位置 (2)将位置position设置为0
这两步操作,JDK API给开发者提供了一个filp()方法来完成,flip()方法将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态,因此每次准备读出元素前,都必须调用一次filp()方法。
4、第42行~第49行,每次先判断一下是否已经达到缓冲区的上界,若存在则调用get()方法获取到此元素,get()方法会自动移动下标position。
5、第56行,对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 NIO4:缓冲区Buffer(续)的更多相关文章
- Java NIO -- 缓冲区(Buffer)的数据存取
缓冲区(Buffer): 一个用于特定基本数据类型的容器.由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类.Java NIO 中的 Buffer 主要用于与 NIO 通道进行 ...
- JAVA NIO缓冲区(Buffer)------ByteBuffer常用方法
参考:https://blog.csdn.net/xialong_927/article/details/81044759 缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对输入/输出(I ...
- Java NIO 缓冲区 Buffer
缓冲区 Buffer 是 Java NIO 中一个核心概念,它是一个线性结构,容量有限,存放原始类型数据(boolean 除外)的容器. 1. Buffer 中可以存放的数据类型 java.nio.B ...
- Java NIO3:缓冲区Buffer
在上一篇中,我们介绍了NIO中的两个核心对象:缓冲区和通道,在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,如 ...
- Java NIO之Buffer(缓冲区)
Java NIO中的缓存区(Buffer)用于和通道(Channel)进行交互.数据是从通道读入缓冲区,从缓冲区写入到通道中的. 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这 ...
- Java-NIO(二):缓冲区(Buffer)的数据存取
缓冲区(Buffer): 一个用于特定基本数据类行的容器.有java.nio包定义的,所有缓冲区都是抽象类Buffer的子类. Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通 ...
- NIO(一)——缓冲区Buffer
NIO(一)--Buffer NIO简介 NIO即New IO,是用来代替标准IO的,提供了与标准IO完全不同传输方式. 核心: ...
- NIO之缓冲区(Buffer)的数据存取
缓冲区(Buffer) 一个用于特定基本数据类行的容器.有java.nio包定义的,所有缓冲区都是抽象类Buffer的子类. Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道 ...
- java nio 缓冲区(一)
本文来自于我的个人博客:java nio 缓冲区(一) 我们以Buffer类開始对java.nio包的浏览历程.这些类是java.nio的构造基础. 这个系列中,我们将尾随<java NIO ...
随机推荐
- loj#6029. 「雅礼集训 2017 Day1」市场(线段树)
题意 链接 Sol 势能分析. 除法是不能打标记的,所以只能暴力递归.这里我们加一个剪枝:如果区间内最大最小值的改变量都相同的话,就变成区间减. 这样复杂度是\((n + mlogn) logV\)的 ...
- 关系数据库标准语言SQL——概述
SQL是一种介于关系代数与关系演算之间的结构化查询语言,其功能并不仅仅是查询.SQL是一个通用的.功能极强的关系数据库语言.SQL(Structured Query Language)结构化查询语 ...
- ionic 兼容title居中显示和tab栏在显示底部
在app.js里的 .config 里添加配置,同时在函数中引入 $ionicConfigProvider,具体格式如下所示: .config(function($stateProvider, $ur ...
- Ubuntu16.04下postgresql-10
这里使用源码安装的方式进行: Linux版本:ubuntu16.04 PostgresSQL版本:postgresql-10.3 在PostgreSQL官网下载地址:https://www.pos ...
- HttpWebRequest 请求带OAuth2 授权的webapi
OAuth 2.0注意事项: 1. 获取access_token时,请使用POST private static string GetAuthorization(string username, st ...
- Keras实现卷积神经网络
# -*- coding: utf-8 -*- """ Created on Sun Jan 20 11:25:29 2019 @author: zhen "& ...
- [20190226]删除tab$记录的恢复6.txt
[20190226]删除tab$记录的恢复6.txt --//春节前几天做了删除tan$记录的测试,链接:http://blog.itpub.net/267265/viewspace-2565245/ ...
- c#判断两个对象和对象中的属性是否相同(以及记录对象中的哪些字段,和详细的改变情况)
当前项目需要记录变更记录,即用户在进行编辑后,将变更操作记录下来.但是数据没有发生变化,则不记录. 代码1:(仅仅返回是否变化的标识) /// <summary> /// 反射对比实体属性 ...
- xp,windows7,windows8,windows10那个系统好用些
Windows XP:这曾经是微软史上最好的.最受欢迎.最受好评的可以说空前绝后的系统,虽然,XP系统对电脑配置的要求很低,基本现在所有的电脑都支持安装该系统,可它太老旧了,到2014年4-5月份微软 ...
- JavaScript -- 时光流逝(十三):DOM -- Console 对象
JavaScript -- 知识点回顾篇(十三):DOM -- Console 对象 (1) assert() : 如果断言为 false,则在信息到控制台输出错误信息.(2) clear() : 清 ...