java NIO Buffer 详解(1)

1.java.io  最为核心的概念是流(stream),面向流的编程,要么输入流要么输出流,二者不可兼具;

2.java.nio 中拥有3个核心概念:

    Selector Channel, Buffer ,在java nio 中我们面向的是块(block)或是缓冲区(buffer) 编程 ;Buffer 本身就是一块内存,底层实现上,是个数组,

    数据的读写都是通过Buffer 来实现的,flip 进行状态的翻转,读写切换;

java 的种原生数据类型中都有各自对应的Buffer  如:IntBuffer LongBuffer  ByteBuffer CharBuffer 等,没有boolean

Channel  是指可以向其写入数据或者是从中读取数据的对象,类似 io stream,所有数据读写是通过BUFFER  执行的,永远不会出现直接向Channel 写入数据的情况以及直接向channel 读取数据的情况;channel 是双向的,可以进行读写的操作;可以更好反映底层操作情况

3. 关于NIO  Buffer 中3个重要状态属性含义:position:  limit: capacity:

  

Buffer 源码解读:

   A container for data of a specific primitive type.  Buffer 是一个具体的原始类型(除去boolean)的数据容器

    

   A buffer is a linear, finite sequence of elements of a specific primitive type. Aside from its content, the essential properties of a  buffer are its capacity, limit, and position:

   buffer 是一个 线性的,限定的,序列元素的 具体原始类型,除了他的内容之外, 一个buffer 必不可少的特性就是 capacity(容量)。limit    .position这三部分

   A buffer's <i>capacity</i> is the number of elements it contains.
   The capacity of a buffer is never negative and never changes.   capacity 是 buffer 容器里元素的个数, 它从来不会是负数,也不会被改变,在我们初始化的时候就会被我们自己定义它的大小   

    A buffer's <i>position</i> is the index of the next element to be
    * read or written. A buffer's position is never negative and is never
    * greater than its limit.

    position(位置) 是 读写下一元素的索引,position 也是从来不会是负数,从来不会大于limit

  

  

    

    <p> A buffer's <i>limit</i> is the index of the first element that should
    * not be read or written. A buffer's limit is never negative and is never
    * greater than its capacity. </p>

    limit(限定) 是不应该被读到或者被写到的元素的首位置, 它不会是负数,不会超过 capacity

 

经过以上源码解读,可以看出 capacity 在读写过程中是个不变的值,在定义的时候就固定了。

position 是随着channel 读写变换的

limit 是一个限定,用来告诉 position 应该只能写的这里,只能读到这里

比较就是 position < =limit <= capacity

即:

参数

写模式

读模式

position

当前写入的单位数据数量。

当前读取的单位数据位置。

limit

代表最多能写多少单位数据和容量是一样的。

代表最多能读多少单位数据,和之前写入的单位数据量一致。

capacity

buffer 容量

buffer 容量

<p> There is one subclass of this class for each non-boolean primitive type.
  每一个非布尔的原始类型在这个class 里都会有一个子类

<p> Transferring data

Each subclass of this class defines two categories of <i>get</i> and
* <i>put</i> operations: </p>

数据的传输 ,每一个子类都会定义 get 和put 两种方式操作

*<p> <i>Relative</i> operations read or write one or more elements starting
* at the current position and then increment the position by the number of
* elements transferred. If the requested transfer exceeds the limit then a
* relative <i>get</i> operation throws a {@link BufferUnderflowException}
* and a relative <i>put</i> operation throws a {@link
* BufferOverflowException}; in either case, no data is transferred. </p>

相对的 操作读和写一个或者多个元素在目前的position(位置坐标)通过传递元素的数量增加这个position 位置
如果请求超出了limit 的范围,相对的get 操作会抛出BufferUnderflowException 异常
put 方法操作会抛出BufferOverflowException 异常
无论发生何种情况,都没有数据传递

* <p> <i>Absolute</i> operations take an explicit element index and do not
* affect the position. Absolute <i>get</i> and <i>put</i> operations throw
* an {@link IndexOutOfBoundsException} if the index argument exceeds the
* limit. </p>

绝对的操作 进行明确一个元素的索引,不会影响postion 位置,如果索引参数超出了limit 就会抛出 IndexOutOfBoundsException 异常


标记与重置的概念

* <h2> Marking and resetting </h2>
*
* <p> A buffer's <i>mark</i> is the index to which its position will be reset
* when the {@link #reset reset} method is invoked. The mark is not always
* defined, but when it is defined it is never negative and is never greater
* than the position. If the mark is defined then it is discarded when the
* position or the limit is adjusted to a value smaller than the mark. If the
* mark is not defined then invoking the {@link #reset reset} method causes an
* {@link InvalidMarkException} to be thrown. 标记与重置 buffer 的 标记是position 将会被重置的索引,当reset 方法被调用的时候。这个标记不总是被
定义的,但是当他定义的时候,他的值不应该是负数以及它不会超出position 的值,
如果标记被定义,当position 或者limit 调整成了一个比标记还小的值,那么这个标记就会被丢弃(-1)
如果标记没有被定义,但是你调用了reset 方法,将会抛出 InvalidMarkException 异常 一句话,mark 是一个标记,当调用reset 方法时候,position 位置将会是mark 的标记的索引位置

构建一个新的buffer,构建buffer 时候做了什么事情呢?

 * <p> A newly-created buffer always has a position of zero and a mark that is
* undefined. The initial limit may be zero, or it may be some other value
* that depends upon the type of the buffer and the manner in which it is
* constructed. Each element of a newly-allocated buffer is initialized
* to zero. 一个新创建的buffer 总会有一个从0开始的position 以及 未被定义的mark(-1)
初始的limit可能是0 或者是其他的值主要取决于你buffer 的类型和构造函数的方式
新分配的缓冲区每一个元素初始化为零

-------------例子详解------------

我们以IntBuffer 为例,进行构造它的Buffer 对象


IntBuffer buffer =IntBuffer.allocate(10);//分配一个容量为10的IntBuffer


我们跟踪源码点进去,第一步,可以看出它去创建了HeapIntBuffer 对象,


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


点进去 HeapIntBuffer,可以差不多看到刚开始构造时候,capacity = limit 值


HeapIntBuffer(int cap, int lim) { // package-private


super(-1, 0, lim, cap, new int[cap], 0);


}


点进去super,看到会创建出一个容量为capacity的数组,所以说buffer 底层是数组,且初始化的position 为0 ,mark 为-1(即无效)


IntBuffer(int mark, int pos, int lim, int cap, // package-private
int[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}

 

常用的方法文档解释

clear 方法

 *   <li><p> {@link #clear} makes a buffer ready for a new sequence of
* channel-read or relative <i>put</i> operations: It sets the limit to the
* capacity and the position to zero. </p></li> clear 方法 使buffer 准备以一个全新的方式进行 channel -read 操作或者相对的buffer put 操作
它会将limit 值设置成buffer capacity的值,以及position 从0 开始; 如下源码方法:可以看出clear 方法执行的操作。position 置位0 limit 等于 capacity ,清除标记-1 public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
} ------例子详解------

public static void main(String[] args) {

//分配一个容量为10 IntBuffer 底层是数组
IntBuffer buffer =IntBuffer.allocate(10);
//在够着函数创建之后,就会有了capacity limit position 的 值了
System.err.println("起始capacity值==>"+buffer.capacity());
System.err.println("起始limit值==>"+buffer.limit());
System.err.println("起始position值==>"+buffer.position());

for(int i =0;i<buffer.capacity();i++) {
//遍历在每一个buffer 节点 插入随机数
buffer.put(new SecureRandom().nextInt(10));
//打印当前的 capacity limit position 的 值;
System.out.println("循环capacity值==>"+buffer.capacity());
System.out.println("循环limit值==>"+buffer.limit());
System.out.println("循环position值==>"+buffer.position());
}

//buffer clear 方法调用

buffer.clear();

System.err.println("clear 后capacity值==>"+buffer.capacity());
System.err.println("clear 后limit值==>"+buffer.limit());
System.err.println("clear 后position值==>"+buffer.position());

//......再进行自己的put 操作

}

 
flip 方法

 *   <li><p> {@link #flip} makes a buffer ready for a new sequence of
* channel-write or relative <i>get</i> operations: It sets the limit to the
* current position and then sets the position to zero. </p></li> clear 方法 使buffer 准备以一个全新的方式进行 channel -write操作或者相对的buffer get操作
他会将limit 值设置为当前position 值,将position 设置为0 如下源码方法:可以看出flip 方法执行的操作。 public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
} -------例子详解

public static void main(String[] args) {

//分配一个容量为10 IntBuffer 底层是数组
IntBuffer buffer =IntBuffer.allocate(10);
//在够着函数创建之后,就会有了capacity limit position 的 值了
System.err.println("起始capacity值==>"+buffer.capacity());
System.err.println("起始limit值==>"+buffer.limit());
System.err.println("起始position值==>"+buffer.position());

//为了看到limit 效果,这里设置循环5次
for(int i =0;i<5;i++) {
//遍历在每一个buffer 节点 插入随机数
buffer.put(new SecureRandom().nextInt(10));
//打印当前的 capacity limit position 的 值;
System.out.println("循环capacity值==>"+buffer.capacity());
System.out.println("循环limit值==>"+buffer.limit());
System.out.println("循环position值==>"+buffer.position());
}

//buffer flip 方法调用

buffer.flip();

System.err.println("flip 后capacity值==>"+buffer.capacity());
System.err.println("flip 后limit值==>"+buffer.limit());
System.err.println("flip 后position值==>"+buffer.position());

while(buffer.hasRemaining()) {//=position < limit的时候循环;

System.out.println("buffer的值===>"+buffer.get());
}

}

 
rewind(倒回) 方法;

 *   <li><p> {@link #rewind} makes a buffer ready for re-reading the data that
* it already contains: It leaves the limit unchanged and sets the position
* to zero. </p></li>
*
* </ul> rewind 方法是在已经存在的容器里重新读取数据做好准备,他会保持limit 不改变并且使position 为0 如下源码方法:可以看出rewind方法执行的操作。 public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}

    public static void main(String[] args) throws Exception {

        FileInputStream file = new FileInputStream("c:\\demo.txt");
//获取channel 对象
FileChannel fileChannel=file.getChannel();
//读写操作都会用到buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(5112);
//channel 读取数据到buffer
fileChannel.read(byteBuffer); //状态翻转,切换为读
byteBuffer.flip(); while(byteBuffer.hasRemaining()) { byte b =byteBuffer.get();
System.out.println((char)b);
} file.close();
}
上一篇:Java NIO ———— Buffer 缓冲区详解 入门


下一篇:Java NIO —— Buffer(缓冲区)