首页 > 编程语言 >Java NIO——缓冲区Buffer

Java NIO——缓冲区Buffer

时间:2022-10-17 16:34:36浏览次数:77  
标签:Java NIO Buffer buffer limit 缓冲区 position public

基本介绍

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。

 

所谓的输入,输出,就是把数据移除或移入缓冲区。

硬件不能直接访问用户空间(JVM)。

 

基于存储的硬件设备操控的是固定大小的数据块儿,用户请求的是任意大小的或非对齐的数据块儿。

 

虚拟内存:使用虚拟地址取代取代物理地址。

  • 1、可以有多个虚拟地址指向同一个物理地址。
  • 2、虚拟内存空间可以大于实际的硬件内存。

应用:直接内存使用--使用户空间虚拟地址和内核虚拟地址同时指向同一个物理地址,可以使硬件直接访问用户空间内存。

数据库等应用严重依赖文件锁定

Buffer类及其子类

  • 1、在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类,类的层级关系图

在NIO中主要有八种缓冲区类(其中MappedByteBuffer是专门用于内存映射的一种ByteBuffer)

  • 2、Buffer类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息

所有缓冲区都有4个属性:capacity、limit、position、mark,并遵循:mark <= position <= limit <= capacity,下表格是对着4个属性的解释:

属性 描述
Capacity 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
Limit 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的
Position 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备
Mark 标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置
public abstract class Buffer {
	//JDK1.4时,引入的api
	public final int capacity( );//返回此缓冲区的容量
	public final int position( );//返回此缓冲区的位置
	public final Buffer position (int newPositio);//设置此缓冲区的位置
	public final int limit( );//返回此缓冲区的限制
	public final Buffer limit (int newLimit);//设置此缓冲区的限制
	public final Buffer mark( );//在此缓冲区的位置设置标记
	public final Buffer reset( );//将此缓冲区的位置重置为以前标记的位置
	public final Buffer clear( );//清除此缓冲区, 即将各个标记恢复到初始状态,但是数据并没有真正擦除, 后面操作会覆盖
	public final Buffer flip( );//反转此缓冲区
	public final Buffer rewind( );//重绕此缓冲区
	public final int remaining( );//返回当前位置与限制之间的元素数
	public final boolean hasRemaining( );//告知在当前位置和限制之间是否有元素
	public abstract boolean isReadOnly( );//告知此缓冲区是否为只读缓冲区

	//JDK1.6时引入的api
	public abstract boolean hasArray();//告知此缓冲区是否具有可访问的底层实现数组
	public abstract Object array();//返回此缓冲区的底层实现数组
	public abstract int arrayOffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
	public abstract boolean isDirect();//告知此缓冲区是否为直接缓冲区
}

ByteBuffer

从前面可以看出对于 Java 中的基本数据类型(boolean除外),都有一个 Buffer 类型与之相对应,最常用的自然是ByteBuffer 类(二进制数据),该类的主要方法如下:

public abstract class ByteBuffer {
	//缓冲区创建相关api
	//直接内存读写效率高(少一次拷贝),不受GC影响,分配效率低
	public static ByteBuffer allocateDirect(int capacity);//创建直接缓冲区
	
	//java堆内存,读写效率较低,受到GC影响
	public static ByteBuffer allocate(int capacity);//设置缓冲区的初始容量
	public static ByteBuffer wrap(byte[] array);//把一个数组放到缓冲区中使用
	
	//构造初始化位置offset和上界length的缓冲区
	public static ByteBuffer wrap(byte[] array,int offset, int length);
	
	 //缓存区存取相关API
	public abstract byte get( );//从当前位置position上get,get之后,position会自动+1
	public abstract byte get (int index);//从绝对位置get
	public abstract ByteBuffer put (byte b);//从当前位置上添加,put之后,position会自动+1
	public abstract ByteBuffer put (int index, byte b);//从绝对位置上put
}

下面这个例子展示buffer的读写:

public class NioTest1 {

	//通过nio生成随机数,然后在打印出来
    public static void main(String[] args) {
		//设置缓冲区的初始容量
        IntBuffer buffer = IntBuffer.allocate(10);
        System.out.println("capacity:"+buffer.capacity());
        for (int i = 0;i < 5;i++){
            int randomNumber = new SecureRandom().nextInt(20);
            //这里相当于把数据写到buffer中
            buffer.put(randomNumber);
        }

        System.out.println("before flip limit:"+buffer.capacity());

        //反转此缓冲区,上面是写,下面为读,通过flip()方法进行读写的切换
        buffer.flip();

        System.out.println("after flip limit:"+buffer.capacity());

        System.out.println("enter while loop");

		//告知在当前位置和限制之间是否有元素
        while(buffer.hasRemaining()){
            System.out.println("position:" + buffer.position());
            System.out.println("limit:" + buffer.limit());
            System.out.println("capacity:" + buffer.capacity());

            //这里相当于从buffer中读出数据
            System.out.println(buffer.get());
        }

    }
}

Buffer本质是一个数组,通过4个值管理这个工作数组的可读/写区域(我将其称之为“活动区域”),正确理解这些值是用好所有Buffer子类的关键。

 

结合示意图来理解这4个值:

假设,该Buffer处于读模式,即用户正在从该buffer读取数据

  • capacity:该buffer能容纳的数据总长度,即工作数组的长度,这里是16,数组元素下标为0~15。
  • position:读取位置起点3,get()操作返回该位置的值”h”,并将该值+1。
  • limit:可读取有效数据的界限,类似于数组的length。当前值为13,即get()操作最多读取到position=13-1=12的位置。
  • remaining=limit - position:剩余可读取数据的数量,这里还有10个数据可以读取。

数值关系:position <= limit <= capacity

注意,当position == limit 时表示无数据可读取(或无位置可写入)。

flip()翻转

  • flip():(向前)翻转“活动区域”,准备开始读取
//Flips this buffer
//该方法由抽象类Buffer定义
public final Buffer flip() {
    limit = position;
    position = 0;
    return this;
}

为何叫“翻转”?看图就理解了: flip()示意图

上图中绿色的“活动区域”经过flip()后,像翻书一样地向前(向左)翻转到了下图中绿色部分。将活动区域翻到开头后,就可以使用get()从头开始读取数据。因此,flip()一般用于读写转换操作buffer。

compact()压实

  • compact():(向前)压实“活动区域”,准备开始写入。
/**
 * Compacts this byte buffer.
 * The remaining bytes will be moved to the head of the
 * buffer, starting from position zero. Then the position is set to remaining(); the limit is set to capacity; ...
 */
//该方法由Buffer的子类定义和实现
public abstract ByteBuffer compact();

该方法由Buffer的子类定义和实现,从注释可知:将Buffer中剩余的数据区域移动到头部,再将“活动区域”设置为除数据区域外的末尾部分。 看图: compact()示意图

上图中绿色的“活动区域”经过compact()后,往前(往左)压实,挤掉了无效的0~2位置的三个数据;并重新将“活动区域”指向了尾部。压实后,就可以使用put()从绿色部分末尾,开始写入新数据。因此,compact()一般用于准备写入buffer。

注意:

  • 1、ByteBuffer.warp(byte[])以一个数组创建buffer后,该buffer默认处于读模式,即position=0,limit=capacity。此时若使用put进行写入,将从0位置开始覆盖掉初始化数组的数据。因此,若初始化的数据是有用数据,在写入开始前应使用compact()压实后,从尾部开始写入。

  • 2、一定要注意get/put重载的方法是否影响当前位置,在读取、写入前都要用remaining()进行可读/写判断。

  • 3、remaining()是动态计算得到,谨记在get/put后,该值可能发生改变。

  • 4、position==limit时,get/put越界。即position=limit-1是最后一个数据

 

参考: https://blog.csdn.net/al_assad/article/details/79272691

https://www.cnblogs.com/niejunlei/p/5994130.html

https://blog.csdn.net/wkw1125/article/details/77126604

https://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-buffer.html

标签:Java,NIO,Buffer,buffer,limit,缓冲区,position,public
From: https://blog.51cto.com/u_14014612/5763477

相关文章