Java NIO ———— Buffer 缓冲区详解 入门
引言
缓冲区是一个用于特定基本类型的容器。由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 缓冲区详解 入门的更多相关文章
- Java NIO 缓冲技术详解
缓冲区(buffer)是从即将写入通道(channel)或刚刚从通道中读出的一段数据.它是一个持有数据,并扮演NIO通道端点的对象.缓冲区为数据访问和读写过程提供正式机制. 它是NIO和老版Java ...
- Java NIO Buffer缓冲区
原文链接:http://tutorials.jenkov.com/java-nio/buffers.html Java NIO Buffers用于和NIO Channel交互.正如你已经知道的,我们从 ...
- Protocol Buffer技术详解(Java实例)
Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发 ...
- java之StringBuffer类详解
StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...
- java之AbstractStringBuilder类详解
目录 AbstractStringBuilder类 字段 构造器 方法 public abstract String toString() 扩充容量 void expandCapacity(in ...
- java之StringBuilder类详解
StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...
- Protocol Buffer技术详解(数据编码)
Protocol Buffer技术详解(数据编码) 之前已经发了三篇有关Protocol Buffer的技术博客,其中第一篇介绍了Protocol Buffer的语言规范,而后两篇则分别基于C++和J ...
- Protocol Buffer技术详解(语言规范)
Protocol Buffer技术详解(语言规范) 该系列Blog的内容主体主要源自于Protocol Buffer的官方文档,而代码示例则抽取于当前正在开发的一个公司内部项目的Demo.这样做的目的 ...
- Java编程配置思路详解
Java编程配置思路详解 SpringBoot虽然提供了很多优秀的starter帮助我们快速开发,可实际生产环境的特殊性,我们依然需要对默认整合配置做自定义操作,提高程序的可控性,虽然你配的不一定比官 ...
随机推荐
- 报错 ncclCommInitRank failed.
环境 4 GeForce GTX 1080 GPUS docker image nnabla/nnabla-ext-cuda-multi-gpu:py36-cuda102-mpi3.1.6-v1.14 ...
- Code Review 最佳实践
ref: Code review Best Practices 文章将了以下内容: 3w:why.what.when 进行 code review code review 之前的准备 执行 code ...
- c#——ToString()的各种用法
ToString()的各种用法 string str = ""; str = 123456.ToString("N"); //生成 12,3456.00 str ...
- 简单session实现
简单的session校验实现 利用拦截器实现 package com.ryh.blog.intecepter; import org.springframework.core.Ordered; imp ...
- Js HTML DOM动画
基础页面 为了演示如何通过 JavaScript 来创建 html 动画,我们将使用一张简单的网页: 实例 我的第一部 JavaScript 动画 我的动画在这里. 创建动画容器 所有动画都应该与容器 ...
- esx.problem.hyperthreading.unmitigated
是因为VMware新发布的一个漏洞补丁导致的具体解释可参阅VMware官方kb,有详细解释和版本说明. 可选择屏蔽该问题告警 选中主机>配置>高级系统设置>编辑>修改" ...
- 容器编排系统K8s之flannel网络模型
前文我们聊到了k8s上webui的安装和相关用户授权,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14222930.html:今天我们来聊一聊k8s上的网络 ...
- spark-streaming获取kafka数据的两种方式
简单理解为:Receiver方式是通过zookeeper来连接kafka队列,Direct方式是直接连接到kafka的节点上获取数据 一.Receiver方式: 使用kafka的高层次Consumer ...
- 项目实战--@Transactional 的使用
@Transactional 介绍 Spring 事务管理分为编码式和声明式的两种方式,编程式事务指的是通过编码方式实现事务:声明式事务基于 AOP,将具体业务逻辑与事务处理解耦.@Transacti ...
- 【转载】一种git commit前自动格式化的方式
查看原文 简介 这个系列为了解决一个问题:自动化的去管理代码风格和格式 前提:Linux,C语言,Clang 如何在每次commit的时候,将代码风格自动格式化后再提交commit,且格式化的内容必须 ...