NIO Buffer 内部机理使用姿势
关于NIO Buffer中4个重要状态属性
position、limit、capacity 与 mark
Buffer本身是一个容器,称作缓冲区,里面包装了特定的一种原生类型,其子类包括ByteBuffer、CharBuffer、LongBuffer、IntBuffer、DoubleBuffer、ShortBuffer、FloatBuffer。
注意:没有Boolean类型的Buffer
Buffer是一种特定原生类型线性的有限的元素序列,Buffer中比较重要的4个属性:position、limit、capacity、mark. 在使用 Buffer 时,我们实际操作的就是这四个属性的值。
具体介绍下 4 个属性:
capacity(容量):
一个buffer能够容纳数据元素的最大数量,capacity不会为负数,且永远不能被改变。
假设:IntBuffer.allocate(1024), 分配了大小为1024的元素个数,则capacity就等于1024。limit(上界):一个buffer的limit指的是第一个不能在读也不能在写的元素索引. limit不会为负数,并且一定是小于capacity的。
假设:IntBuffer.allocate(1024), 我们在程序中设置limit=512,说明Buffer的容量是1024,但是从512之后既不能读也不能写了,进一步说明该Buffer的实际可用大小是512。
position(位置):一个buffer的position指的是下一个将要读或者写的元素的索引.position不会为负数,并且一定是小于limit的. position的位置主要由get()和put()方法的调用来更新。
mark(标记):
一个备忘地址,作为临时标记位置使用,标记在设定前是未定义的。
mark的使用场景:
假设 IntBuffer.allocate(1024),现在position位置为10,现在只想发送512到1024之间的缓冲数据,此时我们可以buffer.mark(buffer.position())既将position记入mark位置,然后buffer.postion(512),此时发送的数据就是512到1024之间的数据。发送完成后,调用buffer.reset()将mark临时标记赋值给position使得position=mark。注意如果未设定mark,而调用了buffer.reset()方法则会抛出InvalidMarkException。
不变式:
0 <= mark <= position <= limit <= capacity
传输数据:
Buffer中的每个子类中都有get()和put()方法.
带参数的put和get方法称作绝对存入/取出,位置是通过参数指定的.
绝对操作不影响position位置,但是如果索引位置超出limit,则会抛出IndexOutOfBoundsException;
不带参数的put和get称作相对存入/取出,即position位置自动前进.
对于get相对操作,如果位置超过了limit,则会抛出BufferUnderflowException;
对于put相对操作,如果位置超过了limit,则会抛出BufferOverflowException;
线程安全性:
buffer在多线程并发下并不是安全的。如果一个buffer会在多个线程使用,那么需要使用恰当的同步操作来访问buffer。也就是buffer本身并不是线程安全的。
** NIO Buffer 常用方法介绍**
这部分我们以实际代码为例来说明:
clear() 方法:
清除,将buffer重置为空状态,它并不会更改缓冲区内的任何数据元素. 如果此时还没有读取的数据,则就无法读取到了。
public final Buffer clear() {
position = 0; // 位置重置为0
limit = capacity; // limit重置为capacity
mark = -1; // 丢弃标记
return this;
}
flip() 方法:
翻转,使buffer从写模式转换到读模式。
源码:
public final Buffer flip() {
limit = position; // 将limit设置为position
position = 0; // position重置为0
mark = -1; // 丢弃标记
return this;
}
rewind() 方法:
重绕,重置position为0,limit保持不变,此时调用rewind前buffer已处于读模式下了, 可以重新读取buffer中的数据。
源码:
public final Buffer rewind() {
position = 0; // 重置position为0
mark = -1; // 丢弃标记
return this;
}
compact() 方法:
将所有未读的数据拷贝到Buffer起始处. 然后将position设到最后一个未读元素正后面. limit属性依然像clear()方法一样,设置成capacity. 现在Buffer准备好写数据了,但是不会覆盖未读的数据。
注意compact方法的实现是由原生类型的子类实现,比如ByteBuffer则由HeapByteBuffer中实现。
源码:
public ByteBuffer compact() {
System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
position(remaining());
limit(capacity());
discardMark();
return this;
}
hasRemaining() 方法:
会在读取缓冲区时告诉你是否已经达到缓冲区的上界. 可以通过remaining()高效读取buffer数据。
int count = buffer.remaining();
for (int i = 0; i < count; i++) {
myByteArray[i] = buffer.get();
}
slice() 方法:
对原有数据的一个快照,共享相同的底层数据元素. 调用slice方法后,会得到大于等于position且小于limit之间的数据,对于改变slice方法获得大buffer数据,也能够反映到原buffer上。
示例代码:
public class NioTest_slice {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
int capacity = byteBuffer.capacity();
for (int i = 0 ; i < capacity; i++) {
byteBuffer.put((byte)i);
}
// 设置position、limit
byteBuffer.position(2);
byteBuffer.limit(6);
//slice方法是前闭后开的 大于等于position,小于limit
ByteBuffer subByteBuffer = byteBuffer.slice();
// 改变subByteBuffer内容,也能反映到byteBuffer上
for (int j = 0; j < subByteBuffer.capacity(); j++) {
// 2到5位置的数*2
subByteBuffer.put((byte)(2 * subByteBuffer.get(j)));
}
// 设置回原来的值,打印输出看下byteBuffer数据变化
byteBuffer.position(0);
byteBuffer.limit(capacity);
while (byteBuffer.hasRemaining()) {
System.out.println(byteBuffer.get());
}
}
}
输出结果中看到第3个位置到第5个位置的数据都✖️2了,返回4,6,8.
0
1
4
6
8
10
6
7
8
9
duplicate() 方法:
创建了一个与原始缓冲区相似的新缓冲区,与调用slice方法一样也是共享相同的底层数据元素, 拥有同样的容量, 但每个缓冲区拥有各自的 position、limit 和 mark 属性。
示例代码:
public class NioTest_duplicate {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
int capacity = byteBuffer.capacity();
for (int i = 0 ; i < capacity; i++) {
byteBuffer.put((byte)i);
}
ByteBuffer subByteBuffer = byteBuffer.duplicate();
subByteBuffer.position(0); // 单独给duplicate出来的buffer设置position
System.out.println("byteBuffer position:" + byteBuffer.position() + "--subByteBuffer position:" + subByteBuffer.position());
// 改变subByteBuffer内容,也能反映到byteBuffer上
for (int j = 0; j < subByteBuffer.capacity(); j++) {
// 2到5位置的数*2
subByteBuffer.put((byte)(2 * subByteBuffer.get(j)));
}
// 切换到读模式
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
System.out.println(byteBuffer.get());
}
}
}
输出结果能看到原buffer数据元素变化
byteBuffer position:10--subByteBuffer position:0
0
2
4
6
8
10
12
14
16
18
asReadOnlyBuffer() 方法:
我们可以随时将一个普通的Buffer调用asReadOnlyBuffer方法返回一个只读Buffer. 但是,不能将一个只读Buffer转换为读写Buffer。
**关于 Buffer 的 Scattering 和 Gathering **
scatter / gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体。
Scattering:
Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定). 换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。
Gattering:
buffer数组是write()方法的入参,write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。
因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。
示例代码:
public class NioTest_scatteringandgathering {
public static void main(String[] args) throws Exception {
try(ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {
InetSocketAddress inetSocketAddress = new InetSocketAddress(8899);
serverSocketChannel.socket().bind(inetSocketAddress); // 绑定到8899端口
int messageLength = 2 + 3 + 4;
ByteBuffer[] byteBuffers = new ByteBuffer[3];
byteBuffers[0] = ByteBuffer.allocate(2);
byteBuffers[1] = ByteBuffer.allocate(3);
byteBuffers[2] = ByteBuffer.allocate(4);
SocketChannel socketChannel = serverSocketChannel.accept();
do {
int byteRead = 0;
while (byteRead < messageLength) {
long r = socketChannel.read(byteBuffers);
System.out.println("--------------r:" + r);
byteRead += r;
System.out.println("byteRead:" + byteRead);
Arrays.asList(byteBuffers).stream().map(buffer -> "position:" + buffer.position() + ", limit:" + buffer.limit())
.forEach(System.out::println);
}
Arrays.asList(byteBuffers).forEach(Buffer::flip);
// 实际写的个数
int byteWrites = 0;
while (byteWrites < messageLength) {
long r = socketChannel.write(byteBuffers);
byteWrites += r;
}
Arrays.asList(byteBuffers).forEach(Buffer::clear);
System.out.println("byteRead:" + byteRead + ", byteWrite:" + byteWrites + ", messageLength:" + messageLength);
} while (true);
}
}
}
以上服务启动后,我们通过nc localhost 8899或者telecom localhost 8899,然后输入Hello world字符串。
控制台上能看到输出:
--------------r:9
byteRead:9
position:2, limit:2
position:3, limit:3
position:4, limit:4
byteRead:9, byteWrite:9, messageLength:9
欢迎关注我的公众号,扫二维码关注,文章首发到公众号,与你一同成长~
NIO Buffer 内部机理使用姿势的更多相关文章
- Java NIO —— Buffer(缓冲区)
Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区.注意:Buffer是非线程安全类. 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO Buffer ...
- Java NIO Buffer(netty源码死磕1.2)
[基础篇]netty源码死磕1.2: NIO Buffer 1. Java NIO Buffer Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区.Buffer缓冲区本质上是一块可 ...
- (二:NIO系列) Java NIO Buffer
出处:Java NIO Buffer Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区.Buffer缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO ...
- Java NIO Buffer缓冲区
原文链接:http://tutorials.jenkov.com/java-nio/buffers.html Java NIO Buffers用于和NIO Channel交互.正如你已经知道的,我们从 ...
- NumPy 超详细教程(3):ndarray 的内部机理及高级迭代
系列文章地址 NumPy 最详细教程(1):NumPy 数组 NumPy 超详细教程(2):数据类型 NumPy 超详细教程(3):ndarray 的内部机理及高级迭代 ndarray 对象的内部机理 ...
- 大数据Hadoop核心架构HDFS+MapReduce+Hbase+Hive内部机理详解
微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...
- java NIO Buffer 详解(1)
1.java.io 最为核心的概念是流(stream),面向流的编程,要么输入流要么输出流,二者不可兼具: 2.java.nio 中拥有3个核心概念: Selector Channel, Buffe ...
- Hadoop核心架构HDFS+MapReduce+Hbase+Hive内部机理详解
转自:http://blog.csdn.net/iamdll/article/details/20998035 分类: 分布式 2014-03-11 10:31 156人阅读 评论(0) 收藏 举报 ...
- NIO - Buffer
NIO —— Buffer源码分析 Buffer的类结构 底层的基础类是抽象类-Buffer,其中定义了四个变量:capacity(容量),limit(限制),position(位置),mark(标记 ...
随机推荐
- vue中动态加载img
想实现动态加载图片,当点击“首页”时,图片变色 代码如下: <mt-tabbar v-model="selected" fixed class="border-1p ...
- 使用TensorRT对caffe和pytorch onnx版本的mnist模型进行fp32和fp16 推理 | tensorrt fp32 fp16 tutorial with caffe pytorch minist model
本文首发于个人博客https://kezunlin.me/post/bcdfb73c/,欢迎阅读最新内容! tensorrt fp32 fp16 tutorial with caffe pytorch ...
- PHP创建对象的6种方式
创建对象实例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ...
- Beta阶段贡献分配
此作业要求参见:http://edu.cnblogs.com/campus/nenu/2019fall/homework/10006 队名:扛把子 组长:孙晓宇 组员:宋晓丽 梁梦瑶 韩昊 刘信鹏 要 ...
- python爬取中国知网部分论文信息
爬取指定主题的论文,并以相关度排序. #!/usr/bin/python3 # -*- coding: utf-8 -*- import requests import linecache impor ...
- 【2018寒假集训 Day2】【动态规划】又上锁妖塔
又上锁妖塔 (tower.in/tower.out) [题目描述] 小D在X星买完了想要的东西,在飞往下一个目的地的途中,正无聊的他转头看了看身边的小A,发现小A正在玩<仙剑>,可是小A很 ...
- Java多线程编程(5)--线程间通信
一.等待与通知 某些情况下,程序要执行的操作需要满足一定的条件(下文统一将其称之为保护条件)才能执行.在单线程编程中,我们可以使用轮询的方式来实现,即频繁地判断是否满足保护条件,若不满足则继续判断 ...
- 记一次将本地工程上传到github的过程
记一次将本地工程上传到github的过程 1.首先,进入本地工程所在文件夹,运行git init将工程初始化为git仓库: XH@DESKTOP-82MT9LU MINGW64 ~/Desktop/t ...
- Zookeeper 应用实现-配置中心
一.目标 一个乞丐版自更新配置中心,更新配置后,能在各个服务器实现更新 二.架构 三.角色 config-web: 配置后台,主要用于管理配置,增改配置 config-agent: 监听配置,遇到变动 ...
- mysql 插入string类型变量时候,需要注意的问题,妈的,害我想了好几个小时!!
很多人在用php+MySQL做网站往数据库插入数据时发现如下错误: 注册失败!Unknown column '1a' in 'field list' 结果发现用数字提交是没有问题的,其他如char型就 ...