文档章节

ByteBuffer详解

爱宝贝丶
 爱宝贝丶
发布于 02/18 10:12
字数 2173
阅读 297
收藏 0

       在Java nio中,主要有三大组件:Buffer,Channel和Selector。这三者之间的关系可以按照如下方式进行理解:

  • Buffer提供了一个字节缓冲区,其可以不断的从Channel中读取接收到的数据。Buffer的优点主要在于其提供了一系列的Api,能够让用户更方便的对数据进行读取和写入;
  • Channel简单来说就是一个信道,也就是客户端与服务器的一个连接,而且每个客户端都会对应一个Channel对象;
  • Selector是Java nio能够支持高并发数据处理一个关键,其核心理念就是IO多路复用的原理,简单的说就是当多个客户端(Channel)连接服务器时,可以通过Selector同时对这些客户端请求进行监听,当客户端发送数据到服务器之后由Selector对这些Channel进行分发处理。

       本文首先讲解ByteBuffer的实现原理,然后会介绍ByteBuffer中常用的Api,以及其在使用过程中需要注意的点。

1. 实现原理

       对于ByteBuffer,其主要有五个属性:mark,position,limit,capacity和array。这五个属性的作用如下:

  • mark:记录了当前所标记的索引下标;
  • position:对于写入模式,表示当前可写入数据的下标,对于读取模式,表示接下来可以读取的数据的下标;
  • limit:对于写入模式,表示当前可以写入的数组大小,默认为数组的最大长度,对于读取模式,表示当前最多可以读取的数据的位置下标;
  • capacity:表示当前数组的容量大小;
  • array:保存了当前写入的数据。

       这几个数据中,除了array是用于保存数据的以外,这里最终的主要是position,limit和capacity三个属性,因为对于写入和读取模式,这三个属性的表示的含义大不一样。

1.1 写入模式

       如下图所示为初始状态和写入3个字节之后position,limit和capacity三个属性的状态:

写入模式

       从图中可以看出,在写入模式下,limit指向的始终是当前可最多写入的数组索引下标,position指向的则是下一个可以写入的数据的索引位置,而capacity则始终不会变化,即为数组大小。

1.2 读取模式

       假设我们按照上述方式在初始长度为6的ByteBuffer中写入了三个字节的数据,此时我们将模式切换为读取模式,那么这里的position,limit和capacity则变为如下形式:

image.png

       可以看到,当切换为读取模式之后,limit则指向了最后一个可读取数据的下一个位置,表示最多可读取的数据;position则指向了数组的初始位置,表示下一个可读取的数据的位置;capacity还是表示数组的最大容量。这里当我们一个一个读取数据的时候,position就会依次往下切换,当期与limit重合时,就表示当前ByteBuffer中已没有可读取的数据了。

2. 使用示例

       对于ByteBuffer的基本使用方式,从上面的演示中就可以看出,其主要有初始化,写入,切换和读取几个基本操作。如下是ByteBuffer的一个基本使用示例:

public class ByteBufferApp {
  @Test
  public void testBuffer() {
    // 初始化一个大小为6的ByteBuffer
    ByteBuffer buffer = ByteBuffer.allocate(6);
    print(buffer);	// 初始状态:position: 0, limit: 6, capacity: 6

    // 往buffer中写入3个字节的数据
    buffer.put((byte) 1);
    buffer.put((byte) 2);
    buffer.put((byte) 3);
    print(buffer);	// 写入之后的状态:position: 3, limit: 6, capacity: 6

    System.out.println("************** after flip **************");
    buffer.flip();
    print(buffer);	// 切换为读取模式之后的状态:position: 0, limit: 3, capacity: 6

    buffer.get();
    buffer.get();
    print(buffer);	// 读取两个数据之后的状态:position: 2, limit: 3, capacity: 6
  }

  private void print(ByteBuffer buffer) {
    System.out.printf("position: %d, limit: %d, capacity: %d\n",
      buffer.position(), buffer.limit(), buffer.capacity());
  }
}

       在上面的示例中,我们首先创建一个最大容量为6的ByteBuffer,此时position为0,即初始状态,而limit为6,与最大容量一致。当我们往buffer中写入三个字节数据之后,此时的position为3,limit还是6,表示还可以继续往buffer中写入三个数据。当我们切换为读取模式之后,需要注意,此时buffer中写入了三个字节的数据,也就是说只有三个字节的数据可供读取,因而切换为读取模式之后,position指向了0,表示下一个可供读取的数据位置,而limit为3,即为之前写入的数据数量,而capacity始终为6,表示buffer的最大大小。

2.1 相关api

  • mark()

       在前面我们讲过,ByteBuffer中海油一个mark属性,这个属性是一个标识的作用,即记录当前position的位置,在后续如果调用reset()或者flip()方法时,ByteBuffer的position就会被重置到mark所记录的位置。因而对于写入模式,在mark()并reset()后,将会回到mark记录的可以写入数据的位置;对于读取模式,在mark()并reset()后,将会回到mark记录的可以读取的数据的位置。如下是mark()方法分别演示写入和读取数据的示例:

public class ByteBufferApp {
  @Test
  public void testMark() {
    ByteBuffer buffer = ByteBuffer.allocate(6);
    // position: 0, limit: 6, capacity: 6

    buffer.put((byte) 1);
    buffer.put((byte) 2);
    buffer.put((byte) 3);
    // position: 3, limit: 6, capacity: 6

    buffer.mark();  // 写入三个字节数据后进行标记
    // position: 3, limit: 6, capacity: 6
    
    buffer.put((byte) 4); // 再次写入一个字节数据
    // position: 4, limit: 6, capacity: 6

    buffer.reset(); // 对buffer进行重置,此时将恢复到Mark时的状态
    // position: 3, limit: 6, capacity: 6

    buffer.flip();  // 切换为读取模式,此时有三个数据可供读取
    // position: 0, limit: 3, capacity: 6

    buffer.get(); // 读取一个字节数据之后进行标记
    buffer.mark();
    // position: 1, limit: 3, capacity: 6

    buffer.get(); // 继续读取一个字节数据
    // position: 2, limit: 3, capacity: 6

    buffer.reset(); // 进行重置之后,将会恢复到mark的状态
    // position: 1, limit: 3, capacity: 6
  }
}
  • rewind()

       对于rewind()方法,它的主要作用在于将当前的position重置为0,并且mark重置为-1,而且无论mark是否进行过标记。很明显,rewind()和reset()方法都是进行重置的,但是reset()方法则是会优先重置到mark标记的位置。同理,对于写入模式,rewind()方法会重置为初始写入状态,对于读取模式,rewind()则会重置为初始读取模式,其不会对limit属性有任何影响。如下是rewind()方法的一个使用示例:

public class ByteBufferApp {
  @Test
  public void testRewind() {
    ByteBuffer buffer = ByteBuffer.allocate(6);
    // position: 0, limit: 6, capacity: 6

    buffer.put((byte) 1);
    buffer.put((byte) 2);
    buffer.put((byte) 3);
    // position: 3, limit: 6, capacity: 6

    buffer.rewind();  // 调用rewind()方法之后,buffer状态将会重置
    // position: 0, limit: 6, capacity: 6
  }
}
  • compact()

       对于compact()方法,其主要作用在于在读取模式下进行数据压缩,并且方便下一步继续写入数据。比如在一个长度为6的ByteBuffer中写满了数据,然后在读取模式下读取了三个数据之后,我们想继续往buffer中写入数据,此时由于只有前三个字节是可用的,而后三个字节是有效的数据,此时如果写入的话是会把后面三个有效字节给覆盖掉的。因而需要将后面三个有效字节往前移动,以空出三个字节,并且将position指向下一个可供写入的位置,而不是迁移之后的索引0处。compact()方法的作用即在于此,如下是该方法的一个使用示例:

public class ByteBufferApp {
  @Test
  public void testCompact() {
    ByteBuffer buffer = ByteBuffer.allocate(6);
    buffer.put((byte) 1);
    buffer.put((byte) 2);
    buffer.put((byte) 3);
    buffer.put((byte) 4);
    buffer.put((byte) 5);
    buffer.put((byte) 6); // 初始化一个写满的buffer

    buffer.flip();
    // position: 0, limit: 6, capacity: 6  -- 切换为读取模式

    buffer.get();
    buffer.get();
    // position: 2, limit: 6, capacity: 6  -- 读取两个字节后,还剩余四个字节

    buffer.compact();
    // position: 4, limit: 6, capacity: 6  -- 进行压缩之后将从第五个字节开始

    buffer.put((byte) 7);
    // position: 5, limit: 6, capacity: 6  -- 写入一个字节数据的状态
  }
}

3. 小结

       本文首先展示了ByteBuffer在写入模式和读取模式下内部的一个状态,然后简单讲解了ByteBuffer的使用方式,并且展示了ByteBuffer各个常用Api的作用和用法。

© 著作权归作者所有

爱宝贝丶

爱宝贝丶

粉丝 340
博文 136
码字总数 456051
作品 0
武汉
程序员
私信 提问
Java-NIO-Buffer详解

Buffer 类是 java.nio 的构造基础。一个 Buffer 对象是固定数量的数据的容器,其作用是一个存储器,或者分段运输区,在这里,数据可被存储并在之后用于检索。缓冲区可以被写满或释放。对于每...

tantexian
2016/12/05
46
0
NIO API 详解大全

NIO API主要集中在java.nio和它的subpackages中: java.nio 定义了Buffer及其数据类型相关的子类。其中被java.nio.channels中的类用来进行IO操作的ByteBuffer的作用非常重要。 java.nio.cha...

唐玄奘
2017/12/04
0
0
Netty ByteBuf详解

本文主要介绍Netty5 ByteBuf原理及具体使用。 基本结构:与NIO ByteBuffer类似,使用ByteBuffer往往需要在读写之间通过flip切换。ByteBuf里维护两个index,一个readerIndex,一个writerIndex;r...

robin-yao
2015/04/28
1K
0
在Android中使用OpenGL ES进行开发第(二)课:定义图形

一、前期基础知识储备 笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGLES绘制2D/3D图形的第一...

weixin_41101173
2018/04/22
0
0
java I/O 模型简述

同步与异步&阻塞与非阻塞 五大I/O模型详解 java I/O模型简述 概述 从同步与异步&阻塞与非阻塞的概念,到具体的I/O模型,再到具体的Java语言实现,都是层层递进,本篇就从Java语言来看I/O模型...

haoran_10
2016/07/14
618
5

没有更多内容

加载失败,请刷新页面

加载更多

同名依赖,多次引入导致的程序错误

表现: 本地测试正常,打包上线后报错找不到某个方法(缺少依赖),检测依赖发现,同名依赖有两个版本。 解决:删除一个,程序正常

避难所
28分钟前
3
0
在HTML中的下拉框中实现超连接

<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <link rel="canonical" href="https://blog.csdn.net/weixin_34228617/article/details/86130280"/> ......

mickelfeng
33分钟前
3
0
Content7关闭防火墙命令

在外部访问CentOS中部署应用时,需要关闭防火墙。 关闭防火墙命令:systemctl stop firewalld.service 开启防火墙:systemctl start firewalld.service 关闭开机自启动:systemctl disable f...

无名氏的程序员
34分钟前
3
0
分布式存储原理:TiDB

浮躁的码农
46分钟前
7
0
CSS实现圆角边框的完美解决方案

css实现图片圆角,兼容所有浏览器: 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 <style type= "text/css" > /*通用样式--容器宽度值*/ .s......

前端老手
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部