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_ ...
随机推荐
- 使用豆瓣的pip源安装python模块
1.指定豆瓣pip源安装Django pip install -i https://pypi.doubanio.com/simple/ --trusted-host pypi.doubanio.com ...
- ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程
在前面随笔介绍的<ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理>里面,介绍了如何改进和完善审计日志和登录日志的应用服务端和Winform客户端,由于篇幅限制,没有进 ...
- spring boot 2.x 系列 —— spring boot 整合 kafka
文章目录 一.kafka的相关概念: 1.主题和分区 2.分区复制 3. 生产者 4. 消费者 5.broker和集群 二.项目说明 1.1 项目结构说明 1.2 主要依赖 二. 整合 kafka 2 ...
- 【数据结构】红黑树-Java实现
WIKI:https://en.wikipedia.org/wiki/Red%E2%80%93black_tree 转:红黑树(五)之 Java的实现 总结的比较精炼的: http://www.cnb ...
- 一路编程 -- Gruntfile.js
<一路编程> Steven Foote 第四章构建工具 中的 Gruntfile.js 文件的 JSHint 部分,如果按照书中所写,run grunt 的命令的时候会出错. 此处附上完 ...
- JAVA Stirng.format 使用理解
JAVA Stirng.format 使用理解前言:项目中需要对一些字符串处理发现format方法的神奇之处一.api才是王道第一种二参使用①public static String format(S ...
- VS2008 专业版试用到期破解 【转】
对于在win7内核下的vs2008破解,和在xp内核系统下的破解是不同的.传统(XP)的破解方式: 一.先安装试用版,然后在“添加或删除程序”里找到VS2008,点“更改/删除”就会看到一个输入序列号 ...
- redis 基础数据结构实现
参考文献 redis数据结构分析 Skip List(跳跃表)原理详解 redis 源码分析之内存布局 Redis 基础数据结构与对象 Redis设计与实现-第7章-压缩列表 在redis中构建了自己 ...
- django基础知识之认识MVT MVC:
MVT Django是一款python的web开发框架 与MVC有所不同,属于MVT框架 m表示model,负责与数据库交互 v表示view,是核心,负责接收请求.获取数据.返回结果 t表示templ ...
- 数字IC前后端设计中的时序收敛(六)--Max Fanout违反
本文转自:自己的微信公众号<数字集成电路设计及EDA教程>(二维码见博文底部) 里面主要讲解数字IC前端.后端.DFT.低功耗设计以及验证等相关知识,并且讲解了其中用到的各种EDA工具的教 ...