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有同样的作用和目的,但是使用方式完全不同 ...
随机推荐
- ES-9200端口与9300端口
(1)Elasticsearch是基于lucene的全文检索服务器 (1)9300:ES节点之间的通讯使用 (2)9200:ES节点和外部通讯使用
- mysql之指定为definer的用户不存在
问题描述: java.sql.SQLException: The user specified as a definer ('tsingsoft'@'%') does not exist 解决: 1. ...
- MySQL忘记密码如何重置
一]进入服务器下,我用的是centos版本 vim /etc/my.cnf 1 vim[二]找到mysqld的部分然后在下面添加上一句代码,意思是跳过密码直接进入,然后保存退出 skip-grant- ...
- 【Javaweb】Servlet的xml和注解配置
1.xml <%@ page language="java" contentType="text/html;" %> <!DOCTYPE ht ...
- 《Interest Rate Risk Modeling》阅读笔记——第八章:基于 LIBOR 模型用互换和利率期权进行对冲
目录 第八章:基于 LIBOR 模型用互换和利率期权进行对冲 思维导图 推导浮息债在重置日(reset date)的价格 第八章:基于 LIBOR 模型用互换和利率期权进行对冲 思维导图 推导浮息债在 ...
- Win32 开发记录
Typedefs LPCSTR = char* LPCWSTR = wchar_t* Webpages Control reference: https://docs.microsoft.com/en ...
- Node Sass does not yet support your current environment: Linux 64-bit with Unsupported runtime
环境: ubuntu18 webstorm vue项目 报错原因: 缺少相关依赖 解决方法: npm rebuild node-sass 还未解决: npm uninstall --save node ...
- Django 中的select_related函数优化查询
参考链接: https://blog.csdn.net/secretx/article/details/43964607 在数据库有外键的时候,使用select_related()和prefech_r ...
- P1029最大公约数和最小公倍数
P1029最大公约数和最小公倍数 #include <iostream> #include <cmath> #include <algorithm> #define ...
- luogu P1044 火车进出栈问题(Catalan数)
Catalan数就是魔法 火车进出栈问题即: 一个栈(无穷大)的进栈序列为 1,2,3,4,...,n 求有多少个不同的出栈序列? 将问题进行抽象, 假设'+'代表进栈, 则有'-'代表出栈 那么如果 ...