引言
缓冲区是一个用于特定基本类型的容器。由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. 有了它(powermock)再也不担心单元测试不达标了

    为什么要写单元测试 优点:单元测试可以减少bug率,提升代码的质量.还可以通过单元测试来熟悉业务. 公司硬性要求:有些公司可能还会强制要求,每次新增代码.或者变更代码单测覆盖率要达到多少比例才能申请代 ...

  2. mysql海量数据优化

    一般我们数据量大的时候,然后就需要进行分页,一般分页语句就是limit offset,rows.这种分页数据量小的时候是没啥影响的,一旦数据量越来越大随着offset的变大,性能就会越来越差.下面我们 ...

  3. ROS代码经验系列-- tf进行位置查询变换

    include文件: #include "tf/transform_broadcaster.h" #include "tf/transform_listener.h&qu ...

  4. 简单session实现

    简单的session校验实现 利用拦截器实现 package com.ryh.blog.intecepter; import org.springframework.core.Ordered; imp ...

  5. MyBatis-Plus 多表联查+分页

    在写东西的过程中,多表联查和分页功能必不可少.当然,crud也很重要 但是又不想写代码和xml. 通过苦苦的查找.发现MyBatis-Plus一款国产的框架.优化了许多操作 本次主要记录一下,多表联查 ...

  6. 负载均衡各个算法JAVA诠释版

    00 前言 首先给大家介绍下什么是负载均衡(来自百科) 负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展 网络设备和 服务器的带宽.增加 吞吐量.加强网络数据处理能力.提高网络的灵活 ...

  7. 一行 CSS 代码的魅力

    之前在知乎看到一个很有意思的讨论 一行代码可以做什么? 那么,一行 CSS 代码又能不能搞点事情呢? CSS Battle 首先,这让我想到了,年初的时候沉迷的一个网站 CSS Battle .这个网 ...

  8. 使用postman添加cookie失败和cookie消失问题

    例如 groupId=2; path=/; domain=.www.baidu.com; HttpOnly; Expires=Tue, 16 Jul 2019 03:42:12 GMT; 添加失败和c ...

  9. kafka的概念

    1.生产者: 生产者发送消息到broker,有三种确认方式(request.required.acks)acks = 0: producer不会等待broker(leader)发送ack .因为发送消 ...

  10. JDK1.7-HashMap原理

    JDK1.7 HashMap 如何在源码上添加自己的注释 打开jdk下载位置 解压src文件夹,打开idea,ctrl+shift+alt+s打开项目配置 选择jdk版本1.7,然后点击Sourcep ...