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_ ...
随机推荐
- SYN6107型 GPS北斗双模子钟
SYN6107型 GPS北斗双模子钟 产品概述 SYN6107型GPS北斗双模子钟是由西安同步电子科技有限公司精心设计.自行研发生产的一套以接收北斗卫星信号的子钟,从北斗地球同步卫星上获取标准时钟信号 ...
- Django之forms组件进阶
Django Form表单组件 Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要 ...
- 浅析为何使用融合CDN是大趋势?
使用传统CDN的用户遇到的新问题 随着云计算时代的快速发展,尤其是流媒体大视频时代的到来,用户在是使用过往CDN节点资源调配将面临很多问题: 问题1: 流媒体时代不局限于静态内容分发,直播点播等视频服 ...
- 【转载】JDK自带的log工具
版权声明:本文为Jaiky_杰哥原创,转载请注明出处.This blog is written by Jaiky, reproduced please indicate. https://blog.c ...
- 浅谈Linq查询
一.Var关键字 在学习Linq查询之前,我们先来学习var关键字的用法,看看微软官方的定义:从Visual C#3.0开始,在方法范围声明的变量可以具有隐式“类型” var.隐式类型的局部变量是强类 ...
- 第四届蓝桥杯省赛 (JavaB组)
第二题:马虎的算式 小明是个急性子,上小学的时候经常把老师写在黑板上的题目抄错了. 有一次,老师出的题目是:36 x 495 = ? 他却给抄成了:396 x 45 = ? 但结果却很戏剧性,他的答案 ...
- 关于自己"手机瘾"的切身体验
其实之前也喜欢看手机,只是从去年7月份开始,因为自控能力.新项目等原因,导致"手机瘾"这个病更严重了,控制不住,一看就是几小时.去年过年的时候,自己定的第一个目标就是戒掉手机瘾,到 ...
- Codeforces Gym101201B:Buggy Robot(BFS + DP)
题目链接 题意 给出一个n*m的地图,还有一个操作序列,你原本是要按照序列执行操作的,但是你可以修改操作:删除某些操作或者增加某些操作,问从'R'到'E'最少需要多少次修改操作. 思路 和上次比赛做的 ...
- concat的应用
今天遇到一个问题,有一张车辆信息表,一张车辆品牌表,他们之间的品牌进行关联, 但是车辆信息表中品牌的名称较长,而品牌表名称较短.例如:车辆表:东风标致:品牌表:标致. 为了达到两种表的“模糊关联”. ...
- django基础知识之csrf:
csrf 全称Cross Site Request Forgery,跨站请求伪造 某些恶意网站上包含链接.表单按钮或者JavaScript,它们会利用登录过的用户在浏览器中的认证信息试图在你的网站上完 ...