Java NIO之Buffer的使用

目录

  • Buffer简介
  • Buffer的核心属性
  • Buffer的建立与使用(ByteBuffer为例)
  • 总结
  • 参考资料

Buffer简介

缓冲区(Buffer):本质上是一个数组,用于临时保存、写入以及读取数据。在Java NIO中,
该内存块包含在NIO Buffer对象当中,NIO Buffer对象还提供了一组接口来访问该内存块。java

根据数据类型的不一样,Java为除了boolean类型以外的其他7种基本类型提供了相应类型的缓冲区,
分别是ByteBufferCharBufferShortBufferIntBufferLongBuffer
FloatBufferDoubleBuffer。他们都继承自抽象类Buffer类,他们的管理方式也都几乎同样。
UML类图以下:
数组

Buffer的核心属性

BUffer类的部分实现以下:安全

public abstract class Buffer {
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

    //构造方法
    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }
    
    /**
     * Returns this buffer's capacity.
     *
     * @return  The capacity of this buffer
     */
    //返回这个Buffer的容量
    public final int capacity() {
        return capacity;
    }

    /**
     * Returns this buffer's position.
     *
     * @return  The position of this buffer
     */
    //返回这个Buffer中当前的位置(当前操做数)
    public final int position() {
        return position;
    }

    /**
     * Returns this buffer's limit.
     *
     * @return  The limit of this buffer
     */
    //返回当前Buffer中能够被操做的元素的个数
    public final int limit() {
        return limit;
    }

    /**
     * Sets this buffer's mark at its position.
     *
     * @return  This buffer
     */
    //记录当前position的位置
    public final Buffer mark() {
        mark = position;
        return this;
    }
    
    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

}

其中定义了四个Buffer属性,对应的描述以下app

属性 描述
capacity 容量;用于描述这个Buffer大小,即建立的数组的长度,一旦声明不能够被改变
position 位置,表示当前缓冲区中正在操做的数据的位置,在切换读取时会将其置0
limit 界限、限制;表示当前缓冲区中能够操做的数据的大小,默认状况下为Buffer的大小,切换为读取模式后为数组中元素的个数(准确的说时切换以前position的值)
mark 标记;用于记录当前position的位置,后续操做过程当中可使用reset()方法将position还原至最后一次mark的位置

Buffer的建立与使用(ByteBuffer为例)

Buffer的建立

Java NIO中可使用对应Buffer类的allocate()或者allocateDirect()静态方法建立。性能

//使用allocate()建立
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);

//使用allocateDirect()建立
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);

Buffer的本质是一个数组,建立时须要指定数组的大小this

Buffer的使用

Buffer的使用通常分为四个步骤spa

  1. Buffer中写入数据
  2. Buffer切换为读取模式
  3. 读取Buffer
  4. Buffer清空,供后续写入使用

1. 写如数据操作系统

//使用put()方法向Buffer中写入数据
byteBuffer.put("bmilk".getBytes());

//使用Channel#read()向Buffer中写入数据
channel.read(byteBuffer);

2. 将Buffer切换为读取模式指针

能够经过调用flip()方法将Buffer从写模式切换到读模式。code

byteBuffer.flip()

调用flip()方法会将position设回0,并将limit设置成以前position的值。
即,如今使用position标记读的位置,limit表示以前写进了多少个byte,也就是如今
能读取多少个byte等。

3. 读取Buffer
读取Buffer有两种方式:

  1. Buffer种读取数据到Channel
  2. 使用get()方法从Buffer种读取数据
//从Buffe中将数据写入通道
inChannel.write(byteBuffer)

//使用get()方法从BUffer中读取数据
byte[] bytes=new byte[byteBuffer.limit()];
byteBuffer.get(bytes);

4. 将Buffer清空,供后续写入使用
使用clear()清空缓冲区,清空缓冲区只是使各个指针恢复初始位置,
更具体的说是position设置为0,limit设置为容量的初始大小。
并不会真实清空其中数据,可是能够经过后续的写覆盖以前的数据

byteBuffer.clear()

其余的一些方法

  1. 使用rewind()Buffer重复读取数据
//使用`rewind()`从`Buffer`重复读取数据
//Buffer.rewind()将position设回0,因此你能够重读Buffer中的全部数据。
//limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。
Buffer rewind = byteBuffer.rewind();
  1. compact()方法

clear()会使使各个指针恢复初始位置,可是实际中可能存在部分数据尚未被使用,然后续须要使用。
又必须清理一部分Buffer的空间,compact()方法会将全部未读数据拷贝到Buffer的起始处,
而后将position指针设置到最后一个未读元素的后面,如今Buffer能够进行写数据,
可是不会覆盖前面的未读的数据。

  1. mark()方法与reset()方法

经过调用Buffer.mark()方法,能够标记Buffer中的当前的position。以后能够经过调用Buffer.reset()方法恢复到这个position。

//使用mark标记当前的position位置
byteBUffer.mark()
//使用reset方法使position指针返回这个位置
byteBuffer.reset()

4.equals()方法与compareTo()方法

当须要比较两个Buffer时可使用equals()方法与compareTo()方法。

equals()方法判断两个方式是否相等,当知足下列条件时,表示两个Buffer相等

  • 有相同的类型(bytecharint等)
  • Buffer中剩余的bytechar等的个数相等。
  • \(\color{#FF3030}{`Buffer`中全部剩余的`byte`、`char`等都相同}\)

compareTo()方法比较两个两个Buffer的大小,仅比较剩余元素(bytechar等)
若是知足下列条件,则认为一个Buffer“小于”另外一个Buffer

  • 第一个不相等的元素小于另外一个Buffer中对应的元素
  • 全部元素都相等,但第一个Buffer比另外一个先耗尽(第一个Buffer的元素个数比另外一个少)。

直接缓冲区与非直接缓冲区

  • 非直接缓冲区:经过allocate()方法分配缓冲区,将缓冲区创建在JVM内存中
  • 直接俄缓冲区:经过allocateDirect()方法分配直接缓冲区,将缓冲区创建在物理内存中,能够在某些状况下提升效率

非直接缓冲区

  • 非直接缓冲区数据流向图

直接缓冲区

  • 直接缓冲区数据流向图

直接缓冲区(物理内存映射文件):相比非直接缓冲区省略了copy的过程,因此说直接缓区能够必定程度上提升效率

弊端:

  • 开辟空间时资源消耗大
  • 不安全,java程序将数据写入物理内存映射文件中,以后数据将不受Java程序控制,
    何时写入硬盘没法控制(由操做系统控制),当垃圾回收机制释放引用后才能断开与之的链接

小结

  • 缓冲区要么是直接的,要么是非直接的若是为直接字节缓冲区,则java虚拟机会见最大努力直接在此缓冲区上执行本机I/O
    也就是说,每次调用基础操做系统的I/O以前或以后,虚拟机都回尽可能避免将缓冲区的内容复制到中间缓冲区或者从中间缓冲区中复制内容。
  • 直接字节缓冲区能够经过调用此类的allocateDirect()工厂方法来建立,
    此方法返回的缓冲区进行分配和取消分配所需的程本一般高于非直接缓冲区,
    直接缓冲区的内容能够驻留在常规的垃圾回收堆以外,所以他们对应用程序内存需求形成的影响可能并不明显,
    因此建议直接缓冲区主要分配给易受基础系统的本机I/O操做影响的大型、持久得缓冲区。
    通常状况下,最好尽在直接缓冲区能在程序性能方面带来明显好处时分配他们。
  • 直接字节缓冲区还能够经过FileChannelmap()方法,将文件区域直接映射到内存中来建立,
    该方法返回MappedByteBufferJava的实现有助于JNI从本地及代码建立直接字节缓冲区,
    若是以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域。
    则试图访问该区域不会更改缓冲区的内容,而且将会在访问期间或稍后的时间致使抛出不肯定的异常
  • 字节缓冲区是直接缓冲区仍是非直接缓冲区能够经过调用其isDirect()方法来肯定,提供此方法是为了可以在性能关键型代码中执行显式缓冲区管理。

总结

本文简单介绍了Buffer的种类,并对经常使用方法进行乐简单的介绍

参考资料

Java NIO系列教程(三) Buffer

相关文章
相关标签/搜索