Java NIO缓存区基本操作【源码笔记】

目录
一、基本概念
二、缓存区
    1.Buffer类图
    2.创建Buffer缓存区
三、基本操作
    1.填充与读取
    2.翻转
    3.释放
    4.压缩
    5.标记
    6.比较
    7.批量移动
四、参考资料    
一、基本概念

缓冲区(Buffer):包在一个对象内的基本数据元素数组
容量(Capacity):缓冲区能够容纳的数据元素的最大数量
上界(Limit):缓冲区的第一个不能被读或写的元素
位置(Position):下一个要被读或写的元素的索引
标记(Mark):一个备忘位置。调用mark()来设定mark=postion。调用reset()设定position= mark

概念关系
0 <= mark <= position <= limit <= capacity

二、缓存区
1.Buffer类图
Buffer类图.jpg

备注:从Buffer类图中体现的一系列操作方法,下文中将对其重要的方法源码逐一分析。

2.创建Buffer缓存区

以一个例子来分析Buffer缓存区的创建。

CharBuffer buffer = CharBuffer.allocate(10); // @1
public static CharBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapCharBuffer(capacity, capacity); // @2
}
HeapCharBuffer(int cap, int lim) {
    super(-1, 0, lim, cap, new char[cap], 0); // @3
}

备注:
@1 通过CharBuffer.allocate创建缓存区,例子中缓存区的容量为100个字符
@2 通过new char[cap]字符数组构造缓存区容器,数组大小即缓存区容量
@3 默认 Mark为-1即没有标记;Position为0;Limit与容量Capacity相同;offset默认为0

三、基本操作
1.填充与读取

1.1 填充示例

CharBuffer buffer = CharBuffer.allocate(10);
String mystr = "ABCDEFGHIG";
for (int i = 0; i < mystr.length( ); i++) {
    buffer.put(mystr.charAt(i)); // @1
}
buffer.position(0); // @2
System.out.println(buffer.get()); // @3

@1 填充缓冲区
@2 重置位置到开始位置
@3 从缓存区读取

1.2 填充源码

public CharBuffer put(char x) {
    hb[ix(nextPutIndex())] = x; // @1
    return this;
}
final int nextPutIndex() {                         
    if (position >= limit)
        throw new BufferOverflowException();
    return position++; // @2
}
protected int ix(int i) {
    return i + offset; // @3
}

@1 给数组赋值,hb即char[]hb,在缓存区构造时创建
@2 position需要小于等于limit;返回position后自增向后移位
@3 offset默认为0,可以在构造缓存区时对其赋值;每次填充时position需要加上offset

1.3 读取源码

public char get() {
   return hb[ix(nextGetIndex())]; // @1
}

@1 从缓存区读取,即从数组中获取position下标对应的值;返回值后下标向后移动(position自增)

小结:缓存区的填充即填充数组,每个元素填充后,位置会向后移位;当缓存区满时,possion也移动到了数组的最后位置;possion不能超过limit,否则抛出BufferOverflowException。读取即从数组中取出,读取不能超过limit限制;每次读取后位置会向后移位。

2.翻转
2.1 Flip示例
CharBuffer buffer = CharBuffer.allocate(10);
String mystr = "ABCDEFGHIG";
for (int i = 0; i < (mystr.length()-2); i++) {
    buffer.put(mystr.charAt(i));
}
buffer.flip();
System.out.println(buffer.get());

2.1.1 Flip前内存截图

Flip之前内存截图.jpg

备注:Flip前position=8; limit=10; capacity=10

2.1.2 Flip后内存截图


Flip后内存截图.jpg

备注:Flip后position=0; limit=8; capacity=10

2.1.3 Flip源码

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

备注:由Flip源码可以看出,将limit设置为当前的position; position重置为0;mark重置为-1.

2.2 Rewind示例
CharBuffer buffer = CharBuffer.allocate(10);
String mystr = "ABCDEFGHIG";
for (int i = 0; i < (mystr.length()-2); i++) {
    buffer.put(mystr.charAt(i));
}
buffer.rewind();
System.out.println(buffer.get());

2.2.1 Rewind前内存截图

!
Rewind之前内存截图.jpg

备注:Rewind前position=8; limit=10; capacity=10

2.2.2 Rewind后内存截图


Rewind后内存截图.jpg

备注:Rewind后position=0; limit=10; capacity=10

小结:Flip和Rewind都会将position设置为0,即转换成准备读出元素的释放状态;两者的区别在于Rewind不会改变limit的值,而Flip会改变limit的值,变更为原position。

3.释放

3.1 释放示例

CharBuffer buffer = CharBuffer.allocate(10);
String mystr = "ABCDEFGHIG";
for (int i = 0; i < mystr.length(); i++) {
    buffer.put(mystr.charAt(i));
}
buffer.flip();
while(buffer.hasRemaining()){ // @1
    System.out.print(buffer.get());
}

3.2 HasRemaining源码

public final boolean hasRemaining() {
    return position < limit; // @2
}

@1 通过buffer.hasRemaining()判断是否还有剩余元素;可以通过此方式将Buffer数据释放
@2 剩余元素即当前位置是否已经达到上界

3.3 Clear源码

public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

备注:clear将缓存区恢复,即:当前位置设置为0;limit与容量相等;mark设置为-1;clear并不会改变buffer中的值。

4.压缩

4.1 压缩示例

CharBuffer buffer = CharBuffer.allocate(10);
String mystr = "ABCDEFGHIG";
for (int i = 0; i < mystr.length(); i++) {
    buffer.put(mystr.charAt(i));
}
 buffer.position(2).limit(7); // @1
buffer.compact(); // @2
System.out.println(buffer.get());

@1 将缓存区位置设置为2, limit设置为7
@2 执行压缩

4.2 压缩前内存截图


压缩前内存截图.jpg

备注:压缩前position=2; limit=7; capacity=10

4.3 压缩后内存截图


压缩后内存截图.jpg

备注:压缩后position=5; limit=10; capacity=10
此处观察position与limit的变化,具体原因见下面源码追踪

4.4 压缩源码

public CharBuffer compact() {
    System.arraycopy(hb, ix(position()), hb, ix(0), remaining()); // @1
    position(remaining()); // @2
    limit(capacity()); // @3
    discardMark(); // @4
    return this;
}

public final int remaining() {
    return limit - position;
}

final void discardMark() {
    mark = -1;
}

@1 通过数组拷贝的方式将剩余的元素拷贝到数组头部,即从下标0开始填充;
已释放的(position前面的)将被丢弃
@2 将position重置为剩余元素的末端(limit-position)
@3 将limit重置为Buffer容量
@4 丢弃mark即设置为-1

小结:调用compact()的作用是丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪。

5.标记

5.1 标记示例

CharBuffer buffer = CharBuffer.allocate(10);
String mystr = "ABCDEFGHIG";
for (int i = 0; i < mystr.length(); i++) {
    buffer.put(mystr.charAt(i));
}
buffer.position(2).limit(7);
buffer.mark();
System.out.println(buffer.position()); // @1
buffer.position(5); 
System.out.println(buffer.position()); // @2
buffer.reset(); 
System.out.println(buffer.position()); // @3

@1 设置标记后打印位置,此时输出为2
@2 将位置重置到5后打印,此时输出为5
@3 执行reset后打印,此时输出为2

5.2 标记源码

public final Buffer mark() {
    mark = position; // @1
    return this;
}
public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m; // @2
    return this;
}

@1 mark()将position赋值给变量mark
@2 reset()重新将mark赋给position

小结:mark作用将当前position记下来,在执行reset后还原position

6.比较

6.1 Equals示例

CharBuffer buffer = CharBuffer.allocate(10);
String mystr = "ABCDEFGHIG";
for (int i = 0; i < mystr.length(); i++) {
    buffer.put(mystr.charAt(i));
}
buffer.position(0).limit(5); // @1

CharBuffer buffer2 = CharBuffer.allocate(10);
String mystr2 = "FGHIGABCDE";
for (int i = 0; i < mystr2.length(); i++) {
    buffer2.put(mystr2.charAt(i));
}
buffer2.position(5).limit(10); // @2
boolean isEquals = buffer.equals(buffer2);
System.out.println(isEquals); // @3

Equals内存截图


Equals内存截图.jpg

@1 buffer剩余元素(limit-position)为ABCDE
@2 buffer2剩余元素(limit-position)为ABCDE
@3 buffer与buffer2相等

6.3 Equals源码

 public boolean equals(Object ob) {
    if (this == ob)
        return true;
    if (!(ob instanceof CharBuffer)) // @1
        return false;
    CharBuffer that = (CharBuffer)ob;
    if (this.remaining() != that.remaining()) // @2
        return false;
    int p = this.position();
    for (int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j--)
        if (!equals(this.get(i), that.get(j))) // @3
            return false;
    return true;
}

@1 equals比较两个buffer须类型相同
@2 equals比较两个buffer须剩余元素个数相等(limit-position)
@3 equals比较两个buffer须在剩余元素中下标对应的每个元素都相同

小结:两个buffer的equals比较须:类型相同、剩余元素个数相同、对应下标的每个元素相同。

7.批量移动

7.1 批量获取示例

CharBuffer buffer = CharBuffer.allocate(10);
String mystr = "ABCDEFGHIG";
for (int i = 0; i < mystr.length(); i++) {
    buffer.put(mystr.charAt(i));
}
buffer.flip();
char[] chars = new char[3];
buffer.get(chars,0,3); // @1
System.out.println(chars); // @2

@1 buffer.get中传入字符数组chars,给该数组填充3个元素
@2 输出结果:ABC

7.2 批量获取源码

public CharBuffer get(char[] dst, int offset, int length) {
    checkBounds(offset, length, dst.length);
    if (length > remaining()) // @1
        throw new BufferUnderflowException();
    int end = offset + length;
    for (int i = offset; i < end; i++)
        dst[i] = get(); // @2
    return this;
}

@1 要填充数组的长度需要小于剩余元素,否则抛出异常
@2 填充数组

备注:批量获取,即将buffer的当前position到limit的剩余元素,填充指定的长度到传入的数组中。

7.3 批量写入示例

CharBuffer buffer = CharBuffer.allocate(10);
char[] chars =  {'a','c','c'};
buffer.put(chars,0,3); // @1
buffer.flip();
System.out.println(buffer.get()); // @2

@1 将字符数组chars写入到buffer
@2 输出为a

7.4 批量源码

public CharBuffer put(char[] src, int offset, int length) {
    checkBounds(offset, length, src.length);
    if (length > remaining()) // @1
        throw new BufferOverflowException();
    int end = offset + length;
    for (int i = offset; i < end; i++)
        this.put(src[i]); // @2
    return this;
}

@1 批量写入的长度不能大于剩余元素
@2 将数组元素写入到buffer

四、参考资料

《Java NIO》第二章


欢迎关注公众号“瓜农老梁”

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342