JavaNIO第一话-Buffer
Buffer是入门Java NIO的基础,本文希望通过一些形象的比喻来解释一下缓冲区的概念,帮助读者快速理解和记忆。
本文灵感来自于Bilibili博主v若水若水分享的尚硅谷Java视频_NIO视频教程,有需要看视频学习的朋友可以在Bilibili上搜索Java NIO找到相关视频学习。
本文中的英文摘自java.nio中相关类的注释。
Buffer是什么?
A container for data of a specific primitive type.
Buffer是特定基本类型数据的容器。
- 我愿意把Buffer比作 火车。
Buffer能装什么?

- Buffer能装int,float,char,double,short,long,byte
- Buffer不能装boolean
| 整数 | 浮点数 | 字符 | 字节数 |
|---|---|---|---|
| byte(8位) | 1字节 | ||
| short(16位) | char(16位) | 2字节 | |
| int(32位) | float(32位) | 3字节 | |
| long(64位) | double(64位) | 4字节 |
Buffer的重要属性及对应的获取方法
the essential properties of a buffer are its capacity, limit, and position
- capacity,容量。火车有 n 节车厢
- position,位置。这里需要再假想一辆小车,行驶在火车旁边的站台上。position表示当前小车停留在第 n 节车厢前。
- limit,限制。火车的前 n 节车厢。
| 属性 | 属性获取方法 |
|---|---|
| capacity | public final int capacity() |
| position | public final int position() |
| limit | public final int limit() |
接下来的讨论都会紧紧围绕这三个属性
安排一列火车-allocate
以最常用的ByteBuffer为例:
/**
* 分配一个新的字节缓冲区。
* <p>新缓冲区的位置position将为零,其限制limit等于
* 容量capacity,其标记mark将是不确定的,并且其每个元素将是
* 初始化为零。 它将有一个{@link #array backing array},
*,其{@link #arrayOffset array offset}将为零。
*/
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
- 可以看到allocate方法会使用HeapXXXBuffer类来实例化。
- allocate将分配一个指定容量的缓冲区。存储哪类基本数据类型,这取决于你使用哪类Buffer。
测试代码:
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println(buffer.capacity());
System.out.println(buffer.limit());
System.out.println(buffer.position());
}
控制台输出:
1024
1024
0
通俗解释:
安排一辆1024节车厢的火车。容量为1024节车厢,允许装货1024节车厢,现在小车停在第0节车厢上。
学过数组的都知道,计算机中,一个长度为1024的数组下标是从0到1023
装货-put
装单个"货"
测试用例1
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put((byte) 'a');
System.out.println(buffer.capacity());
System.out.println(buffer.limit());
System.out.println(buffer.position());
}
控制台输出
1024
1024
1
通俗解释
把 a 装到火车的第 0 号车厢,成功装上之后,小车向前移了一个节车厢,停在了第 1 号车厢。
这里实际上装进ByteBuffer的是由 a 转化得到的 8 位二进制数(01100001)。
8 位二进制正好就是1个字节。
a 是怎么得到二进制数的?这个问题可以查看ASCII码表
测试用例2
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(11, (byte) 'a');
System.out.println(buffer.capacity());
System.out.println(buffer.limit());
System.out.println(buffer.position());
}
控制台输出
1024
1024
0
结论
put(byte)会使得position发生改变,但是put(index, byte)不会使position发生改变
装一组"货"
测试代码
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("abcd".getBytes());
System.out.println(buffer.capacity());
System.out.println(buffer.limit());
System.out.println(buffer.position());
}
控制台输出
1024
1024
4
通俗解释
把[a,b,c,d]依次装到火车的第 0, 1, 2, 3 号车厢,现在车厢旁边的小车停在 4 号车厢前。
这里实际上装进ByteBuffer的是由 a,b,c,d 转化得到的二进制数。
结论
无论是put一个字节(byte)还是一个字节数组(byte[]),put成功后position都会向后移动。
装货时超载-BufferOverflowException
测试代码
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.limit(2);
System.out.println(buffer.capacity());
System.out.println(buffer.limit());
System.out.println(buffer.position());
buffer.put("abc".getBytes());
}
控制台输出
1024
2
0
Exception in thread "main" java.nio.BufferOverflowException
at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:189)
at java.nio.ByteBuffer.put(ByteBuffer.java:859)
at nio.Main.main(Main.java:14)
通俗解释
虽然这列火车有1024节车厢,但是规定 只能装在前2节车厢内 。因此当你同时派来了 3 辆小车,分别装着 a,b,c,从 0 号车厢排列到 2 号车厢,当你想同时装货到火车上的时候,你被列车管理员拒绝了,理由是你超载了!因此,现在*** a,b,c全都没装上火车 ***。
代码
public ByteBuffer put(byte[] src, int offset, int length) {
checkBounds(offset, length, src.length);
if (length > remaining())
throw new BufferOverflowException();
int end = offset + length;
for (int i = offset; i < end; i++)
this.put(src[i]);
return this;
}
public final ByteBuffer put(byte[] src) {
return put(src, 0, src.length);
}
结论:全装or全部不给装
这里是先校验,后写入数据。要么全都正常写入,要么一个也不会写入。
可重复读-get
读单个
测试用例1
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put((byte) 'a');
System.out.println(buffer.get(0));
System.out.println(buffer.position());
System.out.println(buffer.get(0));
System.out.println(buffer.position());
}
控制台输出
97
1
97
1
结论
get(index)不会改变position的位置,且可以重复读,读取结果总是一样的。
测试用例2
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("abcd".getBytes());
System.out.println(buffer.position());
byte[] bytes = new byte[2];
buffer.get(bytes);
System.out.println(Arrays.toString(bytes));
System.out.println(buffer.position());
}
控制台输出
4
[0, 0]
6
通俗解释
在装货 [a, b, c, d] 之后,小车的位置已经移动到4号车厢前(第5节车厢),此时是不能从当前车厢读取到有效的货物信息的。另外,小车因为成功读取了2个车厢的货物信息(虽然结果为0),小车的位置已经发生了改变,目前小车移动到了6号车厢前(第7节车厢)。
如何让小车回到起始位置呢?
flip
/**
* 在执行一系列<i> put </ i>操作之后,调用此方法以准备一系列相对
* 的<i> get </ i>操作。
*/
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
rewind
/**
* 假设已正确设置了限制,请在执行一系列<i> get </ i>操作之前调用此方法。
*/
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
clear
/**
* 在使用一系列<i> put </ i>操作填充此缓冲区之前,请调用此方法。
*/
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
结论
flip()和rewind()相同点,都是在准备调用get之前调用。
flip()和rewind()不同点,rewind需要用户事先确保limit正确。
clear()用于在准备重新调用put之前调用。
再探get
测试用例
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("abcd".getBytes());
System.out.println(buffer.position());
byte[] bytes = new byte[2];
buffer.flip();
buffer.get(bytes);
System.out.println(Arrays.toString(bytes));
System.out.println(buffer.position());
buffer.rewind();
buffer.get(bytes);
System.out.println(Arrays.toString(bytes));
System.out.println(buffer.position());
}
控制台输出
4
[97, 98]
2
[97, 98]
2
结论
在调用get(byte[])之前事先调用flip()则此次可以正常读取数据到数组中。调用rewind()则可以“倒带”(我愿意理解成把小车开回原点),又可以再次读取。
小车越界-BufferUnderflowException
测试代码
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("abcd".getBytes());
System.out.println(buffer.position());
byte[] bytes = new byte[6];
buffer.flip();
buffer.get(bytes);
}
控制台输出
Exception in thread "main" java.nio.BufferUnderflowException
at java.nio.HeapByteBuffer.get(HeapByteBuffer.java:151)
at java.nio.ByteBuffer.get(ByteBuffer.java:715)
at nio.Main.main(Main.java:15)
通俗解释
在调用flip()之后,列车管理员规定小车最多只能移到到4号车厢前面,因为只有0号到3号车厢装有货物可以读到货物信息,即只有4节车厢中是含有货物信息的,而小车却想获取6节车厢中货物信息,结果被列车管理员告知你越界了!
小车越界可不同于数组越界-IndexOutOfBoundsException
测试代码
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("abcd".getBytes());
byte[] bytes = new byte[6];
buffer.flip();
buffer.get(bytes, 0, 7);
}
控制台输出
Exception in thread "main" java.lang.IndexOutOfBoundsException
at java.nio.Buffer.checkBounds(Buffer.java:567)
at java.nio.HeapByteBuffer.get(HeapByteBuffer.java:149)
at nio.Main.main(Main.java:14)
结论
数组越界是表示对于数组的操作就有问题,与Buffer中的属性关系不大。比如数组长度只有6,却想读取7个字节到数组中去,直接导致了数组越界。
标记和重置-mark&reset
Marking and resetting
A buffer's mark is the index to which its position will be reset when the {@link #reset reset} method is invoked. The mark is not always defined, but when it is defined it is never negative and is never greater than the position. If the mark is defined then it is discarded when the position or the limit is adjusted to a value smaller than the mark. If the mark is not defined then invoking the {@link #reset reset} method causes an {@link InvalidMarkException} to be thrown.
测试代码
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.mark();
buffer.put("ab".getBytes());
System.out.println(buffer.position());
buffer.reset();
System.out.println(buffer.position());
byte[] bytes = new byte[2];
buffer.get(bytes);
System.out.println(Arrays.toString(bytes));
}
控制台输出
2
0
[97, 98]
通俗解释
在一开始,小车就在0号车厢做了个标记,然后在完成对0号车厢和1号车厢的装货后,一个重置,小车位置由2号车厢回到了事先标记好的0号车厢,然后就可以开始读了。!!注意:这不是一种安全的读方式,这里仅仅是示例作用。
拓展知识1:Buffer支持链式调用
测试代码
public static void main(String[] args) {
byte[] bytes = new byte[4];
byte result = ((ByteBuffer)ByteBuffer.allocate(1024)
.put("abcd".getBytes())
.flip())
.get();
System.out.println(result);
}
控制台输出
97
拓展知识2:clear不会清除buffer中原来存储的内容
测试代码
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("abcd".getBytes());
System.out.println(buffer.position());
buffer.clear();
System.out.println(buffer.position());
System.out.println(buffer.get(0));
System.out.println(buffer.get(1));
System.out.println(buffer.get(2));
System.out.println(buffer.get(3));
}
控制台输出
4
0
97
98
99
100
拓展知识3:putChar('a')和put((byte)'a')有别
测试代码
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.putChar('a');
System.out.println(buffer.position());
System.out.println(buffer.get(0));
System.out.println(buffer.get(1));
buffer.put((byte)'a');
System.out.println(buffer.position());
System.out.println(buffer.get(2));
System.out.println(buffer.get(3));
}
控制台输出
2
0
97
3
97
0
结论
putChar('a')会放入2个字节到缓冲区,如果实际编码只有一个字节,那就会用0来表示前一个字节。而put((byte)'a')只会放入1个字节到缓冲区。你可以通过buffer.position()观察这一点。
JavaNIO第一话-Buffer的更多相关文章
- Android简易实战教程--第一话《最简单的计算器》
转载请注明出处:http://blog.csdn.net/qq_32059827/article/details/51707931 从今天开始,本专栏持续更新Android简易实战类博客文章.和以往专 ...
- 漫话JavaScript与异步·第一话——异步:何处惹尘埃
自JavaScript诞生之日起,频繁与异步打交道便是这门语言的使命,并为此衍生出了许多设计和理念.因此,深入理解异步的概念对于前端工程师来说极为重要. 什么是异步? 程序是分"块" ...
- 漫话JavaScript与异步·第二话——Promise:一诺千金
一.难以掌控的回调 我在第一话中介绍了异步的概念.事件循环.以及JS编程中可能的3种异步情况(用户交互.I/O.定时器).在编写异步操作代码时,最直接.也是每个JSer最先接触的写法一定是回调函数(c ...
- 漫话JavaScript与异步·第三话——Generator:化异步为同步
一.Promise并非完美 我在上一话中介绍了Promise,这种模式增强了事件订阅机制,很好地解决了控制反转带来的信任问题.硬编码回调执行顺序造成的"回调金字塔"问题,无疑大大提 ...
- java jvm学习笔记四(安全管理器)
欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一 ...
- 【转】漫谈ANN(2):BP神经网络
上一次我们讲了M-P模型,它实际上就是对单个神经元的一种建模,还不足以模拟人脑神经系统的功能.由这些人工神经元构建出来的网络,才能够具有学习.联想.记忆和模式识别的能力.BP网络就是一种简单的人工神经 ...
- java之jvm学习笔记四(安全管理器)
java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...
- 简明CSS属性:定位
简明CSS属性:定位 第一话 定位 (Positioning) 关键词:position/z-index/top/bottom/right/left/clip POSITION 该属性用来决定元素在页 ...
- NIO的初步入门
NIO java NIO简介 Java NIO 简介 是从java1.4版本开始引入的一个新的IO AP可以替代标准java IO API NIO与原来的IO有同样的作用和目的,但是使用方式完全不同 ...
随机推荐
- Codeforce 977E Cyclic Components
dfs判断图的连通块数量~ #include<cstdio> #include<algorithm> #include<vector> #include<cs ...
- Hibernate学习(六)
Hibernate的三种查询方式 1.Criteria 查询 ,Query By Criteria ( QBC )JPA 规范中定义的一种查询方法,但是不推荐使用 2.HQL : Hibernate ...
- Spring学习(七)
注解产生原因 1.传统spring的做法:使用xml来对bean进行注入和或者是配置aop.事物配置文件体积庞大,造成了配置文件的可读性和可维护性很低Java文件和xml不断切换,造成思维不连贯开发效 ...
- java面试(一)
一.java基础 1.JDK和JRE的区别? JDK是java的开发环境,JRE是java的运行环境,即编写java程序就一定需要JDK,只是运行java程序只要JRE就足够了. 2.java中==和 ...
- Educational Codeforces Round 79 (Rated for Div. 2) - D. Santa's Bot(数论)
题意:有$n$个孩子,第$i$个孩子有$k[i]$件想要的礼物,第$j$个礼物为$a[i][j]$,现在随机挑一个孩子,从他想要的礼物里面随机挑一个,然后送给另一个孩子$($这个孩子可以和第一个孩子是 ...
- 【Fine学习笔记】python 文件l操作方法整理
python脚本可以对excel进行创建.读.写.保存成指定文件名,保存到指定路径的操作.整理了以下处理方法: 首先区别几个操作方式: "r" 以读方式打开,只能读文件 , 如 ...
- kafka中常用API的简单JAVA代码
通过之前<kafka分布式消息队列介绍以及集群安装>的介绍,对kafka有了初步的了解.本文主要讲述java代码中常用的操作. 准备:增加kafka依赖 <dependency> ...
- 实验一  GIT 代码版本管理
实验一 GIT 代码版本管理 实验目的: 1)了解分布式分布式版本控制系统的核心机理: 2) 熟练掌握git的基本指令和分支管理指令: 实验内容: 1)安装git 2)初始配置git ,git ...
- 【降维】主成分分析PCA推导
本博客根据 百面机器学习,算法工程师带你去面试 一书总结归纳,公式都是出自该书. 本博客仅为个人总结学习,非商业用途,侵删. 网址 http://www.ptpress.com.cn 目录: PCA最 ...
- Java实现大批量数据导入导出(100W以上) -(三)超过25列Excel导出
前面一篇文章介绍大数据量导出实现: Java实现大批量数据导入导出(100W以上) -(二)导出 这篇文章在Excel列较少时,按以上实际验证能很快实现生成.但如果列较多时用StringTemplat ...