查看原文
其他

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

梁勇 瓜农老梁 2022-09-08
目录

一、基本概念

二、缓存区    

    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类图中体现的一系列操作方法,下文中将对其重要的方法源码逐一分析。

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创建缓存区,例子中缓存区的容量为10个字符

@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); // @2System.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前position=8; limit=10; capacity=10

2.1.2 Flip后内存截图


备注: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前position=8; limit=10; capacity=10

2.2.2 Rewind后内存截图


备注: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); // @1buffer.compact(); // @2System.out.println(buffer.get());



@1 将缓存区位置设置为2, limit设置为7

@2 执行压缩

4.2 压缩前内存截图


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


4.3 压缩后内存截图



备注:压缩后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()); // @1buffer.position(5);System.out.println(buffer.position()); // @2buffer.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); // @2boolean isEquals = buffer.equals(buffer2);System.out.println(isEquals); // @3



Equals内存截图



@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); // @1System.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); // @1buffer.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》第二章

五、系列文章


系统层面I/O【原理笔记】



「瓜农老梁  学习同行」


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存