引言
缓冲区是一个用于特定基本类型的容器。由java.nio 包定义,所有缓冲区都是 Buffer 抽象类的子类。

Java NIO 中的 Buffer ,主要用于与NIO 通道进行交互。数据从通道存入缓冲区,从缓冲区取出到通道中。

一、创建缓冲区
缓冲区的本质是 数组 ,用于存储不同类型的数据,根据数据类型(boolean 除外),提供了相应类型的缓冲区,如ByteBuffer、IntBuffer等。这些缓冲区的管理方式都是类似的,都是通过 allocate() 方法指定容量并创建缓冲区。

// 创建一个 1 KB 大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
一般情况下,我们通过 allocate() 方法创建缓冲区,但是在需要高性能的地方,有时候往往需要使用 allocateDirect() 方法。

allocate() 创建非直接缓冲区,allocateDirect() 创建直接缓冲区。

二、缓冲区的四个核心属性
缓冲区的本质实际上是一个数组,最常用的ByteBuffer,本身就是一个 byte[] 数组,根据数据读取的场景,设计者为Buffer 设置了四个核心属性,定义在 Buffer 抽象类中:

private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
缓冲区的操作实际上是借由这四个 int 标记来完成的,可以理解为抽象的指针。它们的关系如下:

0 <= mark <= position <= limit <= capacity

position 表示位置,表示当前程序正在操作的数据的下一个索引值。

mark 表示标记,通过 mark() 方法,记录当前数据的索引。可以通过 reset() 重新找到 mark 所指向的数据。

limit 界限,表示缓冲区中可以操作数据的大小,limit 后的数据不能进行读写。

capacity 缓冲区容量,因为缓冲区本身就是数组,因此一旦声明不能改变该值。

2.1 初始的指针状态
假设我们声明了一个 capacity 为 5 的字节缓冲区:

ByteBuffer buf = ByteBuffer.allocate(5);
那么,缓冲区的初始状态就是如下图所示:

2.2 当缓冲区中有数据的状态
由于缓冲区独特的构造,在读和写的时候,limit 与 position 指针是有一定区别的。

// 写模式
byteBuffer.put("Tom".getBytes());

// 读模式
byteBuffer.get();

 三、缓冲区的核心方法
3.1 存取数据
缓冲区既然作为数据的容器,必然涉及到数据的存取操作,但要注意,存和取操作不可以连续执行,两个动作之间需要有一个 “翻转” 的操作。

put() 方法将数据放入到缓冲区中;get() 方法从缓冲区中取出数据。

3.2 flip()翻转、rewind()倒带、clear()清空
flip() : 翻转,将缓冲区进行读写切换。

rewind() : 倒带,可以将 position 和 limit 回退到上一次操作前。

clear() : 清空缓冲区,官方说明是“clears the buffer”,但详细解释是将 position 和 limit 恢复“出厂设置”,并丢弃 mark。注意,缓冲区中的数据并非清空,只是将两个指针重置,数据处在一种“被遗忘”状态,如果进行 get()操作依然可以取出。同时,clear 执行之后的缓冲区无法通过 rewind() 回退指针。

3.3 mark()标记、reset()定位
mark()方法可以记录当前 position 的位置,并可以通过 reset() 方法恢复到 mark()

3.4 hasRemaining()是否有未读数据、remaining()获取未读数据数量
hasRemaining() 用于判断读模式下的 Buffer 中是否还有未读数据;

remaining() 方法可以返回剩余可操作的元素个数。其值与 limit - position 的差值相等。

3.5 示例程序
从一开始创建一个 Buffer 开始,通过存入、读取数据来观察各个属性:capacity、limit、position、mark 等的变化。

创建:

// 分配 1 KB 大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
System.out.println("=============allocate()===========");
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

存数据:

System.out.println("=============put()===========");
String name = "abcde";
byteBuffer.put(name.getBytes());
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

反转:

System.out.println("============flip()===========");
byteBuffer.flip();
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

读数据:

System.out.println("============get()===========");
byte[] dst = new byte[byteBuffer.limit()];
byteBuffer.get(dst);
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());
System.out.println(new String(dst));

倒带:

System.out.println("============rewind()===========");
byteBuffer.rewind();
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

清空:

System.out.println("============clear()===========");
byteBuffer.clear();
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

标记、定位标记:

ByteBuffer buf = ByteBuffer.allocate(5);
buf.put("abcde".getBytes());
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst, 0, 2); // get(byte[] dst, int offset, int length)
System.out.println("第一次取出结果:" + new String(dst));
System.out.println("position:" + buf.position());
// mark()标记
buf.mark();
buf.get(dst, 2, 2); // get(byte[] dst, int offset, int length)
System.out.println("第二次取出结果:" + new String(dst));
System.out.println("position:" + buf.position());
// 恢复到mark
buf.reset();
System.out.println("reset 恢复到 mark 位置");
System.out.println("position:" + buf.position());

查询剩余数据:

// remaining() 获取缓冲区中还可以操作的数量
if (buf.hasRemaining()) {
System.out.println(buf.remaining());
System.out.println("limit - position = " + (buf.limit() - buf.position()));
}

四、直接缓冲区与非直接缓冲区
字节缓冲区要么是 直接缓冲区,要么是 非直接缓冲区。

非直接缓冲区属于常规操作,传统的 IO 流和 allocate() 方法分配的缓冲区都是非直接缓冲区,建立在 JVM 内存中。这种常规的非直接缓冲区会将内核地址空间中的内容拷贝到用户地址空间(中间缓冲区)后再由程序进行读或写操作,换句话说,磁盘上的文件在与应用程序交互的过程中会在两个缓存中来回进行复制拷贝。

而直接缓冲区绝大多数情况用于显著提升性能,缓冲区直接建立在物理内存(相对于JVM 的内存空间)中,省去了在两个存储空间中来回复制的操作,可以通过调用 ByteBuffer 的 allocateDirect() 工厂方法来创建。直接缓冲区中的内容可以驻留在常规的垃圾回收堆之外,因此它们对应用程序的内存需求量造成的影响可能并不明显。另外,直接缓冲区还可以通过 FileChannel 的 map() 方法将文件直接映射到内存中来创建,该方法将返回 MappedByteBuffer 。

直接或非直接缓冲区只针对字节缓冲区而言。字节缓冲区是那种类型可以通过 isDirect() 方法来判断。

注意!!!直接缓冲区性能虽然好,但是缓冲区直接建立在物理内存中,无法由 GC来释放,可控性差,同时分配和销毁成本很高!在对性能不是特别依赖的场景不建议使用!

————————————————
版权声明:本文为CSDN博主「圣斗士Morty」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014745069/article/details/99709696

Java NIO ———— Buffer 缓冲区详解 入门的更多相关文章

  1. Java NIO 缓冲技术详解

    缓冲区(buffer)是从即将写入通道(channel)或刚刚从通道中读出的一段数据.它是一个持有数据,并扮演NIO通道端点的对象.缓冲区为数据访问和读写过程提供正式机制. 它是NIO和老版Java ...

  2. Java NIO Buffer缓冲区

    原文链接:http://tutorials.jenkov.com/java-nio/buffers.html Java NIO Buffers用于和NIO Channel交互.正如你已经知道的,我们从 ...

  3. Protocol Buffer技术详解(Java实例)

    Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发 ...

  4. java之StringBuffer类详解

    StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...

  5. java之AbstractStringBuilder类详解

    目录 AbstractStringBuilder类 字段 构造器 方法   public abstract String toString() 扩充容量 void  expandCapacity(in ...

  6. java之StringBuilder类详解

    StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...

  7. Protocol Buffer技术详解(数据编码)

    Protocol Buffer技术详解(数据编码) 之前已经发了三篇有关Protocol Buffer的技术博客,其中第一篇介绍了Protocol Buffer的语言规范,而后两篇则分别基于C++和J ...

  8. Protocol Buffer技术详解(语言规范)

    Protocol Buffer技术详解(语言规范) 该系列Blog的内容主体主要源自于Protocol Buffer的官方文档,而代码示例则抽取于当前正在开发的一个公司内部项目的Demo.这样做的目的 ...

  9. Java编程配置思路详解

    Java编程配置思路详解 SpringBoot虽然提供了很多优秀的starter帮助我们快速开发,可实际生产环境的特殊性,我们依然需要对默认整合配置做自定义操作,提高程序的可控性,虽然你配的不一定比官 ...

随机推荐

  1. Spring Boot 与 Spring MVC到底有什么区别

    前言 Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等.但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop ,解决了面向 ...

  2. Dubbo服务调用过程源码解析④

    目录 0.服务的调用 1.发送请求 2.请求编码 3.请求的解码 4.调用具体服务 5.返回调用结果 6.接收调用结果 Dubbo SPI源码解析① Dubbo服务暴露源码解析② Dubbo服务引用源 ...

  3. 时间盲注poc编写

    当测试注入漏洞时,页面没有返还结果,连报错都没有时,可以考虑延时. 比如这条语句 ?type=1 and if(length(database())=%d,sleep(5),1) 如果这条语句被服务器 ...

  4. eclipse在线安装svn插件subclipse

    eclipse在线安装svn插件subclipse 1 Help > Eclipse Marketplace,打开Eclipse Marketplace 2 切换到Search,输入subcli ...

  5. vue 导入.md文件(markdown转HTML)

    前言 刚接到这个需求的时候,觉得很简单(的确很简单)但是这玩意的坑真的也让人无奈. 网上找了很多的资料,都没有写出痛点(这就很难过了).通过实践并且在我们项目中平稳运行,想分享给后面的人 我的博客上也 ...

  6. 事务的概念,以及事务在JDBC编程中处理事务的步骤

    事务是作为单个逻辑工作单元执行的一系列操作,一个逻辑工作单元必须有四个属性,称为原子性.一致性.隔离性和持久性 (ACID) 属性,只有这样才能成为一个事务 .JDBC处理事务有如下操作: 1,con ...

  7. 一致性HASH算法在分布式应用场景使用

    其实不管redis还好,Mysql也好 这种数据存储介质,在分布式场景中都存在共同问题:即集群场景下服务路由.比如redis集群场景下,原本我们分3主3从部署.但万一有一天出现访问量暴增或其中一台机器 ...

  8. INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN 的使用和区别

    INNER JOIN:如果表中有至少一个匹配,则返回行 LEFT JOIN:即使右表中没有匹配,也从左表返回所有的行 RIGHT JOIN:即使左表中没有匹配,也从右表返回所有的行 FULL JOIN ...

  9. 图解SparkStreaming与Kafka的整合,这些细节大家要注意!

    前言 老刘是一名即将找工作的研二学生,写博客一方面是复习总结大数据开发的知识点,一方面是希望帮助更多自学的小伙伴.由于老刘是自学大数据开发,肯定会存在一些不足,还希望大家能够批评指正,让我们一起进步! ...

  10. 探讨EFCore如何优雅的实现读写分离

    前言     我们都知道当单库系统遇到性能瓶颈时,读写分离是首要优化手段之一.因为绝大多数系统读的比例远高于写的比例,并且大量耗时的读操作容易引起锁表导致无发写入数据,这时读写分离就更加重要了.   ...