Buffer 是一个对象, 它包含一些要写入或者刚读出的数据,即数据的缓冲区。在 NIO 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别,在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream 对象中。buffer + channel 类似原io包中的BufferedInputStream/BufferedOutputStream,对数据流做了缓冲,减少磁盘或者网络io次数,提高性能等。缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
下面是Buffer的类结构图:
首先从最顶层的Buffer开始
四个私有属性用于处理缓冲区的数据。buffer类中定义如下:
private int mark = -1; private int position = 0; private int limit; private int capacity;
Capacity(容量):
缓冲区能装载的最大数据量,缓冲区被创建时被设置,并不会被更改。
Position(位置):
被读取或被写入的下一个元素的索引,调用get( ) 和 put( )方法时会关联的更新position,换句话说,写入buffer时,Position代表buffer第一个未被写入元素的索引【即put下个元素存放的位置】,读取时,Position代表buffer代表第一个未被读取的索引【即get取时第一个元素位置】。
Limit(限制):
缓冲区中不能被读取或被写入的第一元素。换句话说,在缓冲区中可以使用的元素个数,写入buffer时,Limit为Capacity即能写入buffer最多元素个数,读取buffer是Limit为buffer中元素的个数即写入状态是Position的值。
Mark(标记):
被记录的位置,调用mark( )方法时设置mark=position。调用reset( )设置position = mark,mark属性未被设置时,默认为-1;
四个属性的关系如下:
mark <= position <= limit <= capacity
创建buffer
使用的是实现类中的allocate方法。创建一个容量大小为7的ByteBuffer示例如下:
ByteBuffer readBuffer = ByteBuffer.allocate(7);
创建完毕buffer后可以被写入【即写模式】,position代表未被写入第一元素为0,被写入时limit 等于capacity为7代表缓冲区能写入的byte数,mark为-1(后面有原代码说明)。capacity为固定值7,其他三个属性在buffer被使用的能被修改。
mark属性的看下原代码,在buffer的allocate方法如下
public static ByteBuffer allocate(int capacity) { (capacity < 0) throw new IllegalArgumentException(); urn new HeapByteBuffer(capacity, capacity); }HeapByteBuffer的构造方法如下
class HeapByteBuffer extends ByteBuffer { // For speed these fields are actually declared in X-Buffer; // these declarations are here as documentation /* protected final byte[] hb; protected final int offset; */ HeapByteBuffer(int cap, int lim) { // package-private super(-1, 0, lim, cap, new byte[cap], 0); /* hb = new byte[cap]; offset = 0; */ }super为 ByteBuffer,super的各个值属性如下,
ByteBuffer(int mark, int pos, int lim, int cap, // package-private byte[] hb, int offset) { super(mark, pos, lim, cap); this.hb = hb; this.offset = offset; }从这里我们可以发现mark的被设置为-1。
上面的源码我们发现buffer实际是数组的包装,实际数据存储对象为一个数组,byteBuffer的构造函数中 new byte[cap],创建的就是一个byte的数组。
Accessing(访问,读写buffer)
API如下:
public abstract class ByteBuffer extends Buffer implements Comparable { // This is a partial API listing public abstract byte get( ); public abstract byte get (int index); public abstract ByteBuffer put (byte b); public abstract ByteBuffer put (int index, byte b); }填充:
ByteBuffer在子类HeapByteBuffer有put方法的实现如下
public ByteBuffer put(byte x) { hb[ix(nextPutIndex())] = x; return this; } //获取写入索引位置 final int nextPutIndex() { // package-private //写入范围校验,必须为position和limit之间 if (position >= limit) throw new BufferOverflowException(); //先将position返回,position再自加1 return position++; }从代码中我们可以看出put就是数据放置到数组对应的position位置,然后position=position+1指向下个空闲位置。从这里我们也看可以看出put值放入的范围为position位置到limit之间。
示例:
buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l') .put((byte)'o');
各属性值变化如下
往里进行put时,只有position值发生变化,例如上面的填充了5个字节的大小,position值为5,指上buffer里面的第6个元素。
put (int index, byte b)可以让我们操作buffer中已经操作过位置的值。
buffer.put((byte)'w');
变化后的图示如下
使用 put (int index, byte b)并不会影响到buffer的相关属性值的变化
Flipping(将读状态转换到写状态)
buffer被填充后,我们怎样将数据读出来呢,flip(),能将buffer由写模式切换至读模式。
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
其实flip主要是调整position位置为0,limit设置为position ,mark 设置为-1。
从这里我们可以看出如果执行了flip方法,直接往buffer里面写入值的话,整个buffer从0开始被重新,但是最多只能写入到limit,而如果执行读的话,我们从索引为0的开始读,最多读取limit个数据。
上个buffer执行flip后各属性状态变成为
从图可以看出buffer的position位置变为0,代表可以从0索引开始读取数据,limit设置为原来的position位置6,代表我能从索引位置0读取到5。
Draining(从buffer读取)
get方法是从buffer里面读取数据,在子类HeapByteBuffer有put方法的实现如下
public byte get() { return hb[ix(nextGetIndex())]; } //注意这里offset值为0 protected int ix(int i) { return i + offset; } //获取position值 final int nextGetIndex() { // package-private if (position >= limit) throw new BufferUnderflowException(); return position++; }
可以看出get方法获取的是position与limit之间的数据,每获取后position位置+1,即下个get索引所在位置,所以无论我们调用什么方法操作buffer后,最后使用get方法获取的都是position与limit之间的数据。
调用示例如下
buffer.flip(); System.out.println( (char)buffer.get() ); buffer.mark(); System.out.println( (char)buffer.get() ); buffer.reset(); System.out.println( (char)buffer.get() );
上面的buffer我们flip后调用get整个存储变成
position指向下个索引位置即为1,其他属性不变化
mark后存储变成:
get后:
reset后:
clear(清空buffer)
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
清空比较明确,即调整position调整为0,limit 调整为capacity,mark调整为-1。我们的上面的示例buffer执行clear后各个属性情况如下
从这里我们可以看出,clear()并未实际的清空数据,而只是调整相关属性。
构造方法:
ByteBuffer没有public的构造方法,而是通过一下三种方式来创建:
public static ByteBuffer allocate(int capacity) public static ByteBuffer allocateDirect(int capacity) public static ByteBuffer wrap(byte[] array, int offset, int length)
allocate和allocateDirect分别调用了其子类HeapByteBuffer与DirectByteBuffer。前者可以看出分配的buffer是在heap区域的,而后者是通过unsafe.allocateMemory(cap + ps);在Java虚拟机外的内存中分配了一块,通过unsafe直接分配操作系统内存来使用。这块directMemory可以通过-XX:MaxDirectMemorySize来配置默认大小,通过fullGC回收。
直接使用系统内存的好处是可以减少操作系统内存到虚拟机内存的拷贝,而提高性能。
相关推荐
Java NIO(New Input/Output)是Java标准库在JDK 1.4版本中引入的一个新特性,它提供了一种不同于传统IO流的高效I/O处理方式。NIO的核心概念包括通道(Channel)和缓冲区(Buffer),这两个组件使得数据以块的形式...
ByteBuffer的用法是Java NIO学习中的核心内容。 首先,我们了解下ByteBuffer的基本概念。ByteBuffer是一个字节缓冲区,可以存储字节序列。在NIO中,所有的数据读写都通过缓冲区进行,ByteBuffer与其他类型的Buffer...
### Java NIO 学习笔记 #### 一、概述 Java NIO (Non-Blocking IO,也称为 Java New IO),是 Java 对传统 IO 模型的一次重大改进,旨在提高程序处理大量并发连接的能力。NIO 的核心组件包括 Channels、Buffers 和 ...
Java NIO(New Input/Output)是Java标准库在JDK 1.4引入的一组新的I/O API,它提供了一种不同于传统IO的高效、非阻塞的I/O操作方式。NIO的核心概念包括Channel、Buffer和Selector,它们共同构建了一个与操作系统...
Java NIO,全称Non-Blocking Input/Output,是非阻塞式输入输出,它是Java从1.4版本开始引入的一种新的I/O模型,为Java程序员提供了处理I/O操作的新方式。NIO的主要特点是其能够使Java程序以更有效的方式处理I/O流,...
Java NIO库提供了多种实现,如`java.nio.channels`包下的各种Channel和Selector类,以及`java.nio`包下的Buffer类。 在学习NIO时,首先需要理解Channel、Buffer、Selector的基本概念和使用方法,然后通过实例来熟悉...
Java JDK 5是Java开发工具集的...在"java jdk 5学习笔记"中,你可能会找到这些特性的详细使用示例和实践教程,帮助你深入理解和应用这些知识。通过学习和实践,你可以提升自己的Java编程技能,更好地应对各种开发场景。
在IO流方面,NIO(New IO)在Java 5.0中被引入,提供了一种基于通道(Channel)和缓冲区(Buffer)的I/O模型,相比于传统的流模型,NIO具有更好的非阻塞和选择器特性,适合处理大量并发连接。 最后,JDK 5.0对日期...
Java公司培训经典学习笔记是针对Java编程语言进行深入学习的一份宝贵资料,涵盖了从基础到高级的诸多知识点,旨在帮助开发者提升技能,适应企业级项目开发的需求。以下将详细阐述这些笔记中的关键点: 1. **Java...
《NIO学习笔记》 在Java编程领域,NIO(Non-blocking Input/Output,非阻塞I/O)是一种重要的I/O模型,与传统的BIO(Blocking I/O)相对应。NIO提供了一种新的方式来处理I/O操作,特别是在处理大量并发连接时,它的...
这份"Java-J2SE学习笔记"涵盖了Java编程语言的基础到高级特性,是深入理解Java编程的重要参考资料。以下是对笔记中可能包含的关键知识点的详细解释: 1. **Java语言基础**: - **数据类型**:包括基本类型(如int...
在这个“Java客户端服务器程序学习笔记”中,我们将深入探讨这一主题,包括如何设计、实现和交互这两个关键组件。 首先,客户端是用户与系统进行交互的部分,它发送请求到服务器并接收响应。服务器端则处理这些请求...
### Java学习笔记知识点总结 #### 一、JVM与内存管理 **1.1 JVM基本概念** - **JVM(Java Virtual Machine)**: Java虚拟机是执行Java字节码的虚拟机,它提供了运行Java程序所需的环境。 **1.2 线程** - **线程...
在`CoreJava_day21`的学习笔记中,主要探讨了Java中的NIO(New I/O)包及其关键类和接口,包括`ByteBuffer`、`FileChannel`以及网络编程中常用的`ServerSocket`和`Socket`等。NIO自JDK 1.4引入以来,因其高效性和...
学习笔记(持续更新中) 所有文章均同步发布到微信公众号【JavaRobot】,关注微信公众号,及时得到文章推送,谢谢支持。 说明:如无特别说明,所有代码都基于JDK8 JavaSE(Java基础) Java Core 关键字 synchronized...
NIO的核心组件包括Channel、Buffer和Selector,它们极大地提升了多路复用IO的能力。 总结,Java IO流是Java平台中处理输入输出的基础,其丰富的类库和设计模式为开发者提供了灵活、高效的数据传输手段。通过深入...