一、结论

  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 的使用与源码研究的更多相关文章

  1. 转:微信开发之使用java获取签名signature(贴源码,附工程)

    微信开发之使用java获取签名signature(贴源码,附工程) 标签: 微信signature获取签名 2015-12-29 22:15 6954人阅读 评论(3) 收藏 举报  分类: 微信开发 ...

  2. Java禁止浏览器有缓存的源码

    Java禁止浏览器有缓存的源码 import java.io.IOException; import javax.servlet.Filter; import javax.servlet.Filter ...

  3. 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)

    在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...

  4. java集合树状结构及源码

    java集合树状结构及源码 最近一直想看一下java集合的源码,毕竟平时用的比较多,但总是感觉是跟着习惯new出来一个对象,比如ArrayList,HashMap等等,所以就简单的看了一下,了解了一下 ...

  5. java.nio.ByteBuffer中的flip()、rewind()、compact()等方法的使用和区别

    java.nio.ByteBuffer 1. ByteBuffer中的参数position.limit.capacity.mark含义: position:表示当前指针的位置(下一个要操作的数据元素的 ...

  6. [Java并发] AQS抽象队列同步器源码解析--锁获取过程

    要深入了解java并发知识,AbstractQueuedSynchronizer(AQS)是必须要拿出来深入学习的,AQS可以说是贯穿了整个JUC并发包,例如ReentrantLock,CountDo ...

  7. [Java并发] AQS抽象队列同步器源码解析--独占锁释放过程

    [Java并发] AQS抽象队列同步器源码解析--独占锁获取过程 上一篇已经讲解了AQS独占锁的获取过程,接下来就是对AQS独占锁的释放过程进行详细的分析说明,废话不多说,直接进入正文... 锁释放入 ...

  8. 【转载】深度解读 java 线程池设计思想及源码实现

    总览 开篇来一些废话.下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) ...

  9. Java并发指南12:深度解读 java 线程池设计思想及源码实现

    ​深度解读 java 线程池设计思想及源码实现 转自 https://javadoop.com/2017/09/05/java-thread-pool/hmsr=toutiao.io&utm_ ...

随机推荐

  1. ES 20 - 查询Elasticsearch中的数据 (基于DSL查询, 包括查询校验match + bool + term)

    目录 1 什么是DSL 2 DSL校验 - 定位不合法的查询语句 3 match query的使用 3.1 简单功能示例 3.1.1 查询所有文档 3.1.2 查询满足一定条件的文档 3.1.3 分页 ...

  2. VUE单页面的应用优缺点

    1.优 分离前后端关注点,前端负责界面显示,后端负责数据存储和计算. 减轻服务器压力,服务器只用出数据就可以: 同一套后端程序代码,不用修改就可以用于多种设备客户端: 2019-06-19用户体验好. ...

  3. docker安装gitlab-runner

    docker安装gitlab-runner docker pull gitlab/gitlab-runner:latest安装gitlab-runner 打开自己搭建的GitLab网站,点击顶栏的Sn ...

  4. centos安装netcat TCP UDP测试工具 简称 nc,安全界叫它瑞士军刀

    centos安装netcat 今天安装swoole后,测试UDP服务需要用到netcat,然而百度了很多安装方法,并没有一个好用的.几经尝试,终于安装成功,现在就分享给大家,以供参考. 配置环境:ce ...

  5. php如何定义数组常量

    是这样吗?<?php define('BEST_PHPER',array('name'=>'巩文','address'=>'china')); My God,明确告诉你不可以:原因是 ...

  6. .NET中生成动态验证码

    .NET中生成动态验证码 验证码是图片上写上几个字,然后对这几个字做特殊处理,如扭曲.旋转.修改文字位置,然后加入一些线条,或加入一些特殊效果,使这些在人类能正常识别的同时,机器却很难识别出来,以达到 ...

  7. scrapy基础知识之 关于爬虫部分一些建议:

    1.尽量减少请求次数,能抓列表页就不抓详情页,减轻服务器压力,程序员都是混口饭吃不容易. 2.不要只看 Web 网站,还有手机 App 和 H5,这样的反爬虫措施一般比较少. 3.实际应用时候,一般防 ...

  8. 关于 https的SNI问题

    遇到的问题,服务器多站点配置HTTPS 后遇到的问题,服务器报警告错误. 随后网上搜索了下 SNI的意义. 这句话很经典: SNI(Server Name Indication)是为了解决一个服务器使 ...

  9. Docker笔记(三):Docker安装与配置

    原文地址:http://blog.jboost.cn/2019/07/14/docker-3.html Docker分为Docker CE社区免费版与Docker EE企业收费版.Docker EE主 ...

  10. Web前端——JavaScript扩展补充

    JS补充 document也是windows的一个子对象 a标签点击事件 要想设置点击a标签,执行某种方法,推荐在a标签的herf属性使用JavaScript伪协议,实现点击之后执行的js方法,而不是 ...