Java NIO ByteBuffer 的使用与源码研究
一、结论
ByteBuffer 是Java NIO体系中的基础类,所有与Channel进行数据交互操作的都是以ByteBuffer作为数据的载体(即缓冲区)。ByteBuffer的底层是byte数组,通过四个重要的成员变量(mark、limit、position、capacity)来实现对缓冲区的读写数据以及复用缓冲区等操作。ByteBuffer 申请缓冲区内存(数组)的方式有两种,即堆内存与堆外内存,其中堆外内存有着较强的性能,但需要小心处理,堆内存则可以放心的交给JVM管理。此外还需要注意一点的是ByteBuffer是非线程安全的。
二、API研究
学习新知识总归要回到其本质上,首先思考以下问题:
如果使用一个数组作为缓冲区,想要复用这个缓冲区需要作什么?
在这里我们以阻塞IO读写文件来说明(代码如下所示)
我们新建了一个数组作为缓冲区,读文件的时候不断的往该缓冲区写入数据,并记录实际读入的字节数,并将实际读入的字节数写入byteOutputStream。
可以看出要想使用一个数组作为缓冲区首先我们至少需要以下数据
1.缓冲区的大小(buffer.length)
2.缓冲区内可用的字节数(即readLength,因为不可能每次读入数据都填满整个缓冲区)
此外,由于InputStream可以将数据读到缓冲区的指定分段,因此缓冲区内可用的字节数实际上是由一个数组下标值(默认为0)加上实际读入的字节数长度组成的。
public static void main() throws IOException {
byte[] buffer = new byte[1024];
ByteOutputStream byteOutputStream = new ByteOutputStream();
File file = new File("D:/tmp/test");
FileInputStream inputStream = new FileInputStream(file);
int readLength = 0;
while ((readLength = inputStream.read(buffer)) != 0){
// do something
byteOutputStream.write(buffer,0,readLength);
}
inputStream.close();
System.out.println(new String(byteOutputStream.getBytes()));
}
因此,ByteBuffer 作为可以复用的缓冲区,其底层也是使用数组作为缓冲区,其核心主要有以下四个成员变量
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
mark 标记
position 位置,当前数组指针所在的位置,即下一个读出\写入数组元素的指针所在位置
limit 缓冲区的限制,第一个不应该向此缓冲区写入\读出数据的位置(对应readLength)(默认等于capacity)
capacity 缓冲区的实际大小(对应buffer.length)
因此ByteBuffer 的API主要是围绕围绕这四个成员变量开展的。
1.以remaining举例说明(该方法用于获取剩余元素的大小)
public final int remaining() {
return limit - position;
}

(图片来自《NIO与Socket编程指南》)
如上图所示此时 capacity = 8,limit = 6,position = 2 ,以读数据为例则说明此时还有4个元素可供读取。
2.ByteBuffer 复用的实现
参考阻塞读写文件例子可以确定要复用ByteBuffer必然要重置相关的变量,重置不同的变量有着不同的效果。
比如,在向ByteBuffer中写入数据之后,要再读出数据时必然需要知道实际写入数据的长度(limit并不会随着写入数据而改变,limit代表了缓冲区的限制),并且将数组指针移动到0的位置。
filp方法就是干这活的,其实现如下:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
要还原缓冲区的状态,直接调用clear即可,但该方法并不会清除缓冲区中的数据
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
如果在读取数据的过程(此时postion已经改变)想要再次从头读取数据只需重置postion即可,使用rewind方法即可,该方法会重置mark为-1。
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
3.Mark 一下
从设计上来说ByteBuffer并不是一个支持随机访问(RandomAccess)的缓冲区,写入或读出数据的时候数组的指针只能向前移动,但在某些场景下我们可能需要对某个数据段的内容进行重复读取,此时只需要对指定的位置执行标记操作以便需要的时候将指针移动到标记的位置。
mark方法如下所示,只是暂存position,并不会改变指针的位置。
public final Buffer mark() {
mark = position;
return this;
}
因此如果想要回到该位置就需要执行reset方法。
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
由于mark的默认值为-1,因此如果未执行过mark方法会抛出异常。
Java NIO ByteBuffer 的使用与源码研究的更多相关文章
- 转:微信开发之使用java获取签名signature(贴源码,附工程)
微信开发之使用java获取签名signature(贴源码,附工程) 标签: 微信signature获取签名 2015-12-29 22:15 6954人阅读 评论(3) 收藏 举报 分类: 微信开发 ...
- Java禁止浏览器有缓存的源码
Java禁止浏览器有缓存的源码 import java.io.IOException; import javax.servlet.Filter; import javax.servlet.Filter ...
- 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)
在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...
- java集合树状结构及源码
java集合树状结构及源码 最近一直想看一下java集合的源码,毕竟平时用的比较多,但总是感觉是跟着习惯new出来一个对象,比如ArrayList,HashMap等等,所以就简单的看了一下,了解了一下 ...
- java.nio.ByteBuffer中的flip()、rewind()、compact()等方法的使用和区别
java.nio.ByteBuffer 1. ByteBuffer中的参数position.limit.capacity.mark含义: position:表示当前指针的位置(下一个要操作的数据元素的 ...
- [Java并发] AQS抽象队列同步器源码解析--锁获取过程
要深入了解java并发知识,AbstractQueuedSynchronizer(AQS)是必须要拿出来深入学习的,AQS可以说是贯穿了整个JUC并发包,例如ReentrantLock,CountDo ...
- [Java并发] AQS抽象队列同步器源码解析--独占锁释放过程
[Java并发] AQS抽象队列同步器源码解析--独占锁获取过程 上一篇已经讲解了AQS独占锁的获取过程,接下来就是对AQS独占锁的释放过程进行详细的分析说明,废话不多说,直接进入正文... 锁释放入 ...
- 【转载】深度解读 java 线程池设计思想及源码实现
总览 开篇来一些废话.下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) ...
- Java并发指南12:深度解读 java 线程池设计思想及源码实现
深度解读 java 线程池设计思想及源码实现 转自 https://javadoop.com/2017/09/05/java-thread-pool/hmsr=toutiao.io&utm_ ...
随机推荐
- mpvue 开发小程序接口数据统一管理
mpvue项目里做API与数据分离统一管理 小程序里请求数据接口使用wx:request,因为考虑项目比较大,最好把wx:request封装起来,统一使用管理 utils.js 配置开发环境和线上环境 ...
- Laravel --- 【转】安装调试利器 Laravel Debugbar
[转]http://www.tuicool.com/articles/qYfmmur 1.简介 Laravel Debugbar 在 Laravel 5 中集成了 PHP Debug Bar ,用于显 ...
- scikit-learn杂记
1.数据预处理 二值化 import numpy as np from sklearn import preprocessing X = np.array([[1., -1., 2.], [2., 0 ...
- Python初学者的经历
刚开始安装了个python3.6的版本,自己写了个hello world ,发现可以运行,后面又网上找到了下载酷狗音乐的代码,结果报各种包没有,使用pip安装也各种安装不起来 又从网上找了python ...
- 给 Windows 的终端配置代理
初衷 由于项目开发使用go,所以经常要用到go get,但是吧,terminal下根本没办法下载啊,经常下载三个小时包,写代码一个小时 迫于无奈,只好找个方式可以在terminal下使用ss cmd下 ...
- java8 异步api、循环、日期
java8 异步api.循环.日期 转载请注明出处:https://www.cnblogs.com/funnyzpc/p/10801470.html 异步api 对于多任务耗时的业务场景,一般我们会用 ...
- Java接口中的成员变量默认为(public、static、final)、方法为(public、abstract)
interface”(接口)可将其想象为一个“纯”抽象类.它允许创建者规定一个类的基本形式:方法名.自变量列表以及返回类型,但不实现方法主体.接口也可包含基本数据类型的数据成员,但它们都默认为publ ...
- shell脚本开发基本规范
当你的才华还撑不起你的野心的时候,你就应该静下心来学习.当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练.问问自己,想要怎样的人生. 欢迎加入 基础架构自动化运维:598432640,大数据S ...
- JIRA7.10迁移
1.准备环境 系统环境:Centos7.3 防火墙和Selinux管闭 [root@localhost ~]# useradd jira [root@localhost ~]# yum instal ...
- python查询elasticsearch(Query DSL) 实例
import datetime import sys import getopt import hashlib from elasticsearch import Elasticsearch &quo ...