前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java NIO字节缓存区【源码笔记】

Java NIO字节缓存区【源码笔记】

作者头像
瓜农老梁
发布2020-01-17 16:15:31
6750
发布2020-01-17 16:15:31
举报
文章被收录于专栏:瓜农老梁

目录

代码语言:javascript
复制
一、复制缓冲区    
    1.复制一个缓冲区   
    2.只读缓冲区   
    3.分割缓冲区
二、字节缓冲区   
    1.字节顺序   
    2.直接缓冲区   
    3.视图缓冲区
三、总结
四、参考资料
五、系列文章阅读提示:可只读标题及红色小结部分。

一、复制缓冲区

1.复制一个缓冲区

Duplicate示例
代码语言:javascript
复制
CharBuffer buffer = CharBuffer.allocate(10);
char[] chars =  {'A','B','C','D','E','F','G','H','I','J'};
buffer.put(chars,0,10);
buffer.position(2).limit(7);
CharBuffer newBuffer = buffer.duplicate(); // @1
buffer.clear(); // @2
System.out.println(newBuffer.get());

@1 newBuffer拥有了与buffer相同的mark、position、limit

@2 buffer执行clear后不会对newBuffer造成影响

Duplicate源码

代码语言:javascript
复制
public CharBuffer duplicate() {
    return new HeapCharBuffer(hb,
        this.markValue(),
        this.position(),
        this.limit(),
        this.capacity(),
        offset); // @1
 }

@1 通过new HeapCharBuffer创建新的buffer有自己的mark、position、limit; hb使用了原来buffer的变量char[] hb,即新的buffer共用原来buffer的内存数据

小结:新旧buffer对象共用一份内存数据,拥有各自的mark、position、limit。

2.只读缓冲区

AsReadOnlyBuffer示例

代码语言:javascript
复制
CharBuffer buffer = CharBuffer.allocate(10);
char[] chars =  {'A','B','C','D','E','F','G','H','I','J'};
buffer.put(chars,0,10);
buffer.position(2).limit(7);
CharBuffer newBuffer = buffer.asReadOnlyBuffer(); // @1
buffer.clear(); // @2
System.out.println(newBuffer.get());

@1 创建只读缓冲区拥有原缓冲区相同的mark、position、limit

@2 原缓冲区的清理不会影响新缓冲区

AsReadOnlyBuffer源码

代码语言:javascript
复制
public CharBuffer asReadOnlyBuffer() {
return new HeapCharBufferR(hb, // @1
 this.markValue(),
 this.position(),
 this.limit(),
 this.capacity(),
 offset);
}
public CharBuffer put(char x) {
   throw new ReadOnlyBufferException(); // @2
}
public CharBuffer put(int i, char x) {
   throw new ReadOnlyBufferException(); // @2
}

@1 构建只读缓冲区对象HeapCharBufferR;HeapCharBufferR与HeapCharBuffer的区别isReadOnly=true

@2 只读缓冲区在使用put操作时,会抛出ReadOnlyBufferException

小结:构建只读缓冲区实例对象为HeapCharBufferR,只读缓冲区拥有独立的mark、position、limit。

3.分割缓冲区

Slice示例

代码语言:javascript
复制
CharBuffer buffer = CharBuffer.allocate(10);
char[] chars =  {'A','B','C','D','E','F','G','H','I','J'};
buffer.put(chars,0,10);
buffer.position(2).limit(7);
CharBuffer newBuffer = buffer.slice(); // @1
buffer.clear();
System.out.println(newBuffer.get());

@1 newBuffer是一个mark=-1、position=0、limit=capacity=5的缓冲区

Slice示例运行内存截图

Slice源码

代码语言:javascript
复制
public CharBuffer slice() {
return new HeapCharBuffer(hb, // @1
    -1, // @2
    0, // @3
    this.remaining(), // @4
    this.remaining(), // @5
    this.position() + offset);
}

@1 与原缓冲区共用内存数据hb

@2 mark重置为-1

@3 position重置为0

@4 limit设置为剩余元素(原limit-原position)

@5 capacity设置为剩余元素

小结:通过slice()创建的缓冲区是一个mark=-1、position=0、limit=capacity=(原limit-原position)的子缓冲区。

二、字节缓冲区

系统层面的I/O是面向字节的。字节是操作系统及其I/O设备使用的基本数据类型,当JVM与操作系统之间数据交付时,将其他数据类型拆分成字节。

1.字节顺序

字节顺序:多字节数值被存储在内存中的方式

大端顺序:高字节数据存放在低地址处,低字节数据存放在高地址处

小端顺序:低字节数据存放在内存低地址处,高字节数据存放在内存高地址处

字节示例

代码语言:javascript
复制
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
System.out.println(byteBuffer.order()); // @1
System.out.println(ByteOrder.nativeOrder()); // @2
byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // @3
System.out.println(byteBuffer.order()); // @4

@1 输出BIG_ENDIAN;ByteBuffer默认大端顺序

@2 输出LITTLE_ENDIAN;本地为小端顺序

@3 修改ByteBuffer为小端顺序

@4 输出LITTLE_ENDIAN;ByteBuffer的字节顺序已修改

ByteBuffer源码

代码语言:javascript
复制
boolean bigEndian = true; // @1
boolean nativeByteOrder = (Bits.byteOrder() == ByteOrder.BIG_ENDIAN);

public final ByteOrder order() {
    return bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
}

public final ByteBuffer order(ByteOrder bo) {
    bigEndian = (bo == ByteOrder.BIG_ENDIAN);
    nativeByteOrder =
    (bigEndian == (Bits.byteOrder() == ByteOrder.BIG_ENDIAN));
return this;
} // @2

@1 从ByteBuffer的源代码可以看出默认使用大端字节顺序

@2 修改ByteBuffer的字节顺序小结:ByteBuffer默认使用大端顺序,可以通过order(ByteOrder bo)进行修改;网络通信时,当本地主机字节顺序和通用的网络字节顺序不一致需要转换。

2.非直接缓冲区

非直接缓冲区源码
代码语言:javascript
复制
ByteBuffer byteBuffer = ByteBuffer.allocate(10); // @1

public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
}

HeapByteBuffer(int cap, int lim) {
   super(-1, 0, lim, cap, new byte[cap], 0); // @2
}

@1 创建非直接缓冲区

@2 非直接缓冲区通过new byte[cap]在堆空间分配内存小结:非直接缓冲区在堆空间分配内存;JVM运行在用户空间。

3.直接缓冲区

直接缓冲区源码
代码语言:javascript
复制
ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(10);

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

DirectByteBuffer(int cap) {
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize(); // @1
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap); // @2

    long base = 0;
    try {
        base = unsafe.allocateMemory(size); // @3
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    unsafe.setMemory(base, size, (byte) 0); // @4
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

@1 pageSize通常为4k

@2 判断堆外内存是否有足够的空间分配;可以通过-XX:MaxDirectMemorySize来设置;默认为64M(directMemory=67108864L)

@3 分配堆外内存空间;返回在内存中的地址

@4 初始化内存空间

示例程序
代码语言:javascript
复制
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
long address = unsafe.allocateMemory(10);
unsafe.setMemory(address, 10, (byte) 0);
System.out.println(unsafe.pageSize());
System.out.println(address);

小结:由源码可以看出直接缓冲区在堆外分配内存,不受堆内存大小限制;用户空间和内核空间可以通过虚拟内存地址对直接内存进行访问;减少用户空间与内核空间的拷贝。

4.视图缓冲区

I/O本质上是字节的传递,ByteBuffer提供方便的API创建视图缓冲区。

1. 通过工厂方法创建视图缓冲区,有自己独立的属性、容量、位置、上界和标记。例如:上文中复制缓冲区和分割缓冲区。 2.通过ByteBuffer提供API映射为基本类型缓冲区。例如:asCharBuffer()、asLongBuffer()等通过工厂类创建视图缓冲区见上文复制/分割缓冲区源码部分,下面以asCharBuffer()分析下如何转换的。

代码示例

代码语言:javascript
复制
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
CharBuffer charBuffer = byteBuffer.asCharBuffer();
charBuffer.put('A');

小结:创建字节缓冲区后转换为CharBuffer,然后填充字符A。

源码分析

代码语言:javascript
复制
public CharBuffer asCharBuffer() {
    int size = this.remaining() >> 1;
    int off = offset + position();
    return (bigEndian
        ? (CharBuffer)(new ByteBufferAsCharBufferB(this,
               -1,
               0,
               size,
               size,
               off))
        : (CharBuffer)(new ByteBufferAsCharBufferL(this,
               -1,
               0,
               size,
               size,
               off))); // @1
}

ByteBufferAsCharBufferB(ByteBuffer bb,
 int mark, int pos, int lim, int cap,
 int off){
   super(mark, pos, lim, cap);
   this.bb = bb; // @2
   offset = off;
}

public CharBuffer put(char x) {
     Bits.putCharB(bb, ix(nextPutIndex()), x); // @3
     return this;
}
static void putCharB(ByteBuffer bb, int bi, char x) {
    bb._put(bi    , char1(x)); // @4
    bb._put(bi + 1, char0(x)); // @4
}
private static byte char1(char x) { return (byte)(x >> 8); } // @4
private static byte char0(char x) { return (byte)(x     ); } // @4

@1 由于大端和小端字节顺序不通,位移方向不同;由不通的类实现,以大端为例分析

@2 ByteBufferAsCharBufferB中封装的是字节缓冲 即:ByteBuffer bb

@3 向charBuffer中写入字符。例如例子中的字符'A'

@4 通过为位操作将char转换为byte;存入ByteBuffer bb中

小结:ByteBuffer映射为基本数据缓冲区后,其内部依然为字节缓冲区ByteBuffer;当写入和取出时通过基本类型(例如:char)与byte互相转换来实现。

三、总结

本文从源码角度跟踪分析了复制缓冲区、只读缓冲区、分割缓冲区、字节顺序、非直接缓冲区、直接缓冲区、视图缓冲区的实现原理。

四、参考资料

《Java NIO》第二章(完)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 瓜农老梁 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Duplicate示例
  • 非直接缓冲区源码
  • 直接缓冲区源码
  • 示例程序
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档