【Java.NIO】API —— Buffer接口

java.nio包提供了Buffer API,使得Java程序可以直接控制和运用缓冲区。

java.nio
public abstract class Buffer extends Object


Buffer接口

缓冲区是包在一个对象内的基本数据元素的数组。Buffer类相比简单数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。Buffer类以及它的自定义子类定义了一个用于处理数据缓冲区的API。


参考文档

http://docs.oracle.com/javase/7/docs/api/java/nio/Buffer.html


Thread safety

Buffers are not safe for use by multiple concurrent threads. If a buffer is to be used by more than one thread then access to the buffer should be controlled by appropriate synchronization.


所有的缓冲区都有以下属性

  • capacity - 表示该缓冲区可以保存多少数据,该值不为负且指定后保持不变
  • limit - 不为负且不大于capacity;为不能读写的第一个元素的索引值;不能对缓冲区中超过极限的区域进行读写操作;limit是可以修改的
  • position - 表示缓冲区中下一个可读写单元的位置,每次读写缓冲区的数据时,都会改变该值;非负不大于limit
  • capacity > limit > position > 0

【Java.NIO】API —— Buffer接口_第1张图片

  • 当写数据到Buffer中时,position表示当前的位置。初始的position的值为0,当数据写到Buffer后,position会向前移动到下一个可插入数据的Buffer单元。
  • 当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0.当从Buffer的position出读取数据时,position向前移动到下一个可读的位置。
  • 在写模式下,Buffer的limit表示最多能往Buffer里写多少数据。写模式下,limit等于capacity.
  • 当切换到Buffer的读模式时,limit表示最多能读到多少数据。


操作1—— 新建一个容量为10的Buffer:

【Java.NIO】API —— Buffer接口_第2张图片

操作2—— 向新建的缓冲区写入数据:buffer.put(....): (写模式下的 position 和 limit)

【Java.NIO】API —— Buffer接口_第3张图片

操作3 —— 反转flip(),切换到读模式:(读模式下的position和limit

【Java.NIO】API —— Buffer接口_第4张图片



Buffer提供了用于改变以上3个属性的方法:

  • 一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成
    • clear() - 把limit设capacity,再把position设置0;换句话说,Buffer被清空了,Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据
    • 如果Buffer中有一些未读的数据,调用clear()方法,数据将被遗忘,意味着不在有任何标记会告诉你哪些数据被读过,哪些还没有。
    • 如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么使用compact()方法
    • compact() - 将所有未读的数据拷贝到Buffer起始处,然后将position蛇到最后一个未读元素后面,limit属性依然向clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据
  • flip() - 将Buffer从写模式切换到读模式;调用flip()方法会将position设为0,并将limit设置成之前的position(写时的position)的值;换句话说,position现在用于标记读的位置,limit表示之前写进了多少个元素——现在能读多少个元素
  • rewind() - 将position设置0,可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素


Buffer的分配


要想获得一个Buffer对象首先要进行分配。每个Buffer类都有一个allocate()方法。

ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);



缓冲区提供的读写缓冲区的方法:

  • 向Buffer中写数据:
    • 从Channel写到Buffer: 

int bytesRead = inChannel.read(buf); // read into buffer

    • 通过put方法写Buffer:

buf.put(127);

  • 从Buffer中读取数据:
    • 从Buffer读取数据到Channel:

int bytesWritten = inChannel.write(buf);  // read from buffer into channel

    • 使用get()方法从Buffer中读取数据:

byte aByte = buf.get();


缓冲区的类型:

  • ByteBuffer —— 主要使用ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
  • MappedByteBuffer


Buffer的局限性

  • Buffer长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当需要编码的pojo对象大于Buffer的容量时,会发生越界
  • Buffer只有一个标识位置的指针position,读写的时候需要手动调用flip()和rewind()等,使用者需要小心处理这些API,否则容易导致程序处理失败
  • Buffer的功能有限,一些高级和使用的特性不支持


常用Buffer子类

ByteBuffer

Direct vs. non-direct buffers(Heap Buffer)

A byte buffer is either direct or non-direct. Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer's content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system's native I/O operations.

A direct byte buffer may be created by invoking the allocateDirect factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.

A direct byte buffer may also be created by mapping a region of a file directly into memory. An implementation of the Java platform may optionally support the creation of direct byte buffers from native code via JNI. If an instance of one of these kinds of buffers refers to an inaccessible region of memory then an attempt to access that region will not change the buffer's content and will cause an unspecified exception to be thrown either at the time of the access or at some later time.

Whether a byte buffer is direct or non-direct may be determined by invoking its isDirect method. This method is provided so that explicit buffer management can be done in performance-critical code.


Direct Buffer  vs.  Heap Buffer 

1、 劣势:创建和释放Direct Buffer的代价比Heap Buffer得要高; 

2、 区别:Direct Buffer不是分配在堆上的,它不被GC直接管理(但Direct Buffer的JAVA对象是归GC管理的,只要GC回收了它的JAVA对象,操作系统才会释放Direct Buffer所申请的空间),它似乎给人感觉是“内核缓冲区(buffer in kernel)”。Heap Buffer则是分配在堆上的,或者我们可以简单理解为Heap Buffer就是byte[]数组的一种封装形式,查看JAVA源代码实现,Heap Buffer也的确是这样。 

3、 优势:当我们把一个Direct Buffer写入Channel的时候,就好比是“内核缓冲区”的内容直接写入了Channel,这样显然快了,减少了数据拷贝(因为我们平时的read/write都是需要在I/O设备与应用程序空间之间的“内核缓冲区”中转一下的)。而当我们把一个Heap Buffer写入Channel的时候,实际上底层实现会先构建一个临时的Direct Buffer,然后把Heap Buffer的内容复制到这个临时的Direct Buffer上,再把这个Direct Buffer写出去。当然,如果我们多次调用write方法,把一个Heap Buffer写入Channel,底层实现可以重复使用临时的Direct Buffer,这样不至于因为频繁地创建和销毁Direct Buffer影响性能。 

简单的说,我们需要牢记三点: 

(1) 平时的read/write,都会在I/O设备与应用程序空间之间经历一个“内核缓冲区”。 

(2) Direct Buffer就好比是“内核缓冲区”上的缓存,不直接受GC管理;而Heap Buffer就仅仅是byte[]字节数组的包装形式。因此把一个Direct Buffer写入一个Channel的速度要比把一个Heap Buffer写入一个Channel的速度要快。 

(3) Direct Buffer创建和销毁的代价很高,所以要用在尽可能重用的地方。 



你可能感兴趣的:(java.nio)