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有同样的作用和目的,但是使用方式完全不同 ...
随机推荐
- PyQt5操作SQLite数据库
1.操作SQLite数据库import sysfrom PyQt5.QtSql import QSqlDatabase,QSqlQueryfrom PyQt5.QtCore import * def ...
- Ubuntu各个版本的镜像下载地址
http://mirrors.melbourne.co.uk/ubuntu-releases/
- JavaScript 闭包&基于闭包实现柯里化和bind
闭包: 1 函数内声明了一个函数,并且将这个函数内部的函数返回到全局 2 将这个返回到全局的函数内中的函数存储到全局变量中 3 内部的函数调用了外部函数中的局部变量 闭包简述: 有权访问另一个函数局部 ...
- Oracle的TO_CHAR()格式化数字为百分数的字符串
TO_CHAR(-0.926903249,'FM999999990.00') || '%' 说明: 1,(点号) :'99.99' : 点号,不要念为"句号",句号是个圆圈,点号只 ...
- The Captain 题解
20200216题目题解 这是一篇题解祭题解记,但一共就一道题目.(ROS菜大了) 题目如下: The Captain 给定平面上的n个点,定义(x1,y1)到(x2,y2)的费用为min(|x1-x ...
- 我的B站主页:https://space.bilibili.com/611212 内有视频题解
我的B站主页:https://space.bilibili.com/611212 内有视频题解
- 简单bat脚本
hwf.bat: set GAP_HOME=%~dp0\.. ::copy "%JAVA_HOME%\bin\javaw.exe" "%JAVA_HOME%\bin\HW ...
- Spark操作MySQL,Hive并写入MySQL数据库
最近一个项目,需要操作近70亿数据进行统计分析.如果存入MySQL,很难读取如此大的数据,即使使用搜索引擎,也是非常慢.经过调研决定借助我们公司大数据平台结合Spark技术完成这么大数据量的统计分析. ...
- [转]:Ubuntu 下Apache安装和配置
[转]:Ubuntu 下Apache安装和配置_服务器应用_Linux公社-Linux系统门户网站 https://www.linuxidc.com/Linux/2013-06/85827.htm ...
- 名称空间using namespace std
写再最前面:摘录于柳神的笔记 虽然C++是一门面向对象语言,但是对于刷算法这件事而言,我们并不需要掌握它⾯向对象的部分-只 需要掌握刷算法的时候需要用到的部分(基本输⼊输出.STL标准模板库. str ...