NIO之Buffer解读

news2024/11/26 2:36:31

目录

Buffer 简介

 Buffer 的基本用法

使用步骤

使用 Buffer 的例子

 使用 IntBuffer 的例子

Buffer 的 capacity、position 和 limit

capacity

position

limit

Buffer 的类型

Buffer 分配和读写数据 

Buffer 分配

向 Buffer 中写数据

flip()方法 

从 Buffer 中读取数据

 Buffer 几个方法

rewind()方法

clear()与 compact()方法

mark()与 reset()方法

缓冲区操作

缓冲区分片

只读缓冲区 

直接缓冲区 

内存映射文件 I/O

ByteBuffer的大小分配


Buffer 简介

Java NIO 中的 Buffer 用于和 NIO 通道进行交互。数据是从通道读入缓冲区,从缓冲 区写入到通道中的。

 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装 成 NIO Buffer 对象,并提供了一组方法,用来方便的访问该块内存。缓冲区实际上是 一个容器对象,更直接的说,其实就是一个数组,在 NIO 库中,所有数据都是用缓冲 区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到 缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流 I/O 系统中,所有数据都是直接写入或者直接将数据读取到 Stream 对象中。

在 NIO 中,所有的缓冲区类型都继承于抽象类 Buffer,最常用的就是 ByteBuffer, 对于 Java 中的基本类型,基本都有一个具体 Buffer 类型与之相对应,它们之间的继 承关系如下图所示:

 Buffer 的基本用法

使用步骤

1、使用 Buffer 读写数据,一般遵循以下四个步骤:

(1)写入数据到 Buffer

(2)调用 flip()方法

(3)从 Buffer 中读取数据

(4)调用 clear()方法或者 compact()方法

当向 buffer 写入数据时,buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip()方法将 Buffer 从写模式切换到读模式。在读模式下,可以读取之前写入到 buffer 的所有数据。一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有 两种方式能清空缓冲区:调用 clear()或 compact()方法。clear()方法会清空整个缓冲 区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起 始处,新写入的数据将放到缓冲区未读数据的后面。

使用 Buffer 的例子

  @Test
    public void testConect2() throws IOException {
        RandomAccessFile aFile = new RandomAccessFile("d:\\atguigu/01.txt", "rw");
        FileChannel inChannel = aFile.getChannel();

        //create buffer with capacity of 48 bytes
        ByteBuffer buf = ByteBuffer.allocate(48);
        int bytesRead = inChannel.read(buf); //read into buffer. 
        while (bytesRead != -1) {
            buf.flip(); //make buffer ready for read
            while(buf.hasRemaining()){
                System.out.print((char) buf.get()); // read 1 byte at a time
            }buf.clear(); //make buffer ready for writing
            bytesRead = inChannel.read(buf);
        }
        aFile.close();
    }

 使用 IntBuffer 的例子

     @Test
    public void testConect3() throws IOException {
        // 分配新的 int 缓冲区,参数为缓冲区容量
        // 新缓冲区的当前位置将为零,其界限(限制位置)将为其容量。
        // 它将具有一个底层实现数组,其数组偏移量将为零。
        IntBuffer buffer = IntBuffer.allocate(8);
        for (int i = 0; i < buffer.capacity(); ++i) {
            int j = 2 * (i + 1);
        // 将给定整数写入此缓冲区的当前位置,当前位置递增
            buffer.put(j);
        }
        // 重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为 0
        buffer.flip();
        // 查看在当前位置和限制位置之间是否有元素
        while (buffer.hasRemaining()) {
        // 读取此缓冲区当前位置的整数,然后当前位置递增
            int j = buffer.get();
            System.out.print(j + " ");
        }
    }

Buffer 的 capacity、position 和 limit

为了理解 Buffer 的工作原理,需要熟悉它的三个属性:

- Capacity

- Position

- limit

position 和 limit 的含义取决于 Buffer 处在读模式还是写模式。不管 Buffer 处在什么 模式,capacity 的含义总是一样的。

这里有一个关于 capacity,position 和 limit 在读写模式中的说明:

capacity

作为一个内存块,Buffer 有一个固定的大小值,也叫“capacity”.你只能往里写 capacity 个 byte、long,char 等类型。一旦 Buffer 满了,需要将其清空(通过读数 据或者清除数据)才能继续写数据往里写数据。

position

1)写数据到 Buffer 中时,position 表示写入数据的当前位置,position 的初始值为 0。当一个 byte、long 等数据写到 Buffer 后, position 会向下移动到下一个可插入 数据的 Buffer 单元。position 最大可为 capacity – 1(因为 position 的初始值为 0).

2)读数据到 Buffer 中时,position 表示读入数据的当前位置,如 position=2 时表 示已开始读入了 3 个 byte,或从第 3 个 byte 开始读取。通过 ByteBuffer.flip()切换到 读模式时 position 会被重置为 0,当 Buffer 从 position 读入数据后,position 会下 移到下一个可读入的数据 Buffer 单元。

limit

1)写数据时,limit 表示可对 Buffer 最多写入多少个数据。写模式下,limit 等于 Buffer 的 capacity。

2)读数据时,limit 表示 Buffer 里有多少可读数据(not null 的数据),因此能读到 之前写入的所有数据(limit 被设置成已写数据的数量,这个值在写模式下就是 position)。

Buffer 的类型

- ByteBuffer

- MappedByteBuffer

- CharBuffer

- DoubleBuffer

- FloatBuffer

- IntBuffer

- LongBuffer

- ShortBuffer

这些 Buffer 类型代表了不同的数据类型。换句话说,就是可以通过 char,short,int, long,float 或 double 类型来操作缓冲区中的字节。

Buffer 分配和读写数据 

Buffer 分配

要想获得一个 Buffer 对象首先要进行分配。 每一个 Buffer 类都有一个 allocate 方法。 下面是一个分配 48 字节 capacity 的 ByteBuffer 的例子。

ByteBuffer buf = ByteBuffer.allocate(48);

 这是分配一个可存储 1024 个字符的 CharBuffer:

CharBuffer buf = CharBuffer.allocate(1024);

向 Buffer 中写数据

写数据到 Buffer 有两种方式:

(1)从 Channel 写到 Buffer。

(2)通过 Buffer 的 put()方法写到 Buffer 里。

从 Channel 写到 Buffer 的例子:

int bytesRead = inChannel.read(buf); //read into buffer.

通过 put 方法写 Buffer 的例子:

buf.put(127);

put 方法有很多版本,允许你以不同的方式把数据写入到 Buffer 中。例如, 写到一个 指定的位置,或者把一个字节数组写入到 Buffer

flip()方法 

flip 方法将 Buffer 从写模式切换到读模式。调用 flip()方法会将 position 设回 0,并 将 limit 设置成之前 position 的值。换句话说,position 现在用于标记读的位置, limit 表示之前写进了多少个 byte、char 等 (现在能读取多少个 byte、char 等)。

从 Buffer 中读取数据

从 Buffer 中读取数据有两种方式:

(1)从 Buffer 读取数据到 Channel。

(2)使用 get()方法从 Buffer 中读取数据。

从 Buffer 读取数据到 Channel 的例子:

//read from buffer into channel. 
int bytesWritten = inChannel.write(buf);

 使用 get()方法从 Buffer 中读取数据的例子

byte aByte = buf.get();

get 方法有很多版本,允许你以不同的方式从 Buffer 中读取数据。例如,从指定
position 读取,或者从 Buffer 中读取数据到字节数组。 

 Buffer 几个方法

rewind()方法

Buffer.rewind()将 position 设回 0,所以你可以重读 Buffer 中的所有数据。limit 保持不变,仍然表示能从 Buffer 中读取多少个元素(byte、char 等)。

clear()与 compact()方法

一旦读完 Buffer 中的数据,需要让 Buffer 准备好再次被写入。可以通过 clear()或 compact()方法来完成。

如果调用的是 clear()方法,position 将被设回 0,limit 被设置成 capacity 的值。换 句话说,Buffer 被清空了。Buffer 中的数据并未清除,只是这些标记告诉我们可以从 哪里开始往 Buffer 里写数据。

如果 Buffer 中有一些未读的数据,调用 clear()方法,数据将“被遗忘”,意味着不再 有任何标记会告诉你哪些数据被读过,哪些还没有。

如果 Buffer 中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据, 那么使用 compact()方法。

compact()方法将所有未读的数据拷贝到 Buffer 起始处。然后将 position 设到最后一 个未读元素正后面。limit 属性依然像 clear()方法一样,设置成 capacity。现在 Buffer 准备好写数据了,但是不会覆盖未读的数据。

mark()与 reset()方法

通过调用 Buffer.mark()方法,可以标记 Buffer 中的一个特定 position。之后可以通 过调用 Buffer.reset()方法恢复到这个 position。例如:

buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing. 
buffer.reset(); //set position back to mark.

缓冲区操作

缓冲区分片

在 NIO 中,除了可以分配或者包装一个缓冲区对象外,还可以根据现有的缓冲区对象 来创建一个子缓冲区,即在现有缓冲区上切出一片来作为一个新的缓冲区,但现有的 缓冲区与创建的子缓冲区在底层数组层面上是数据共享的,也就是说,子缓冲区相当 于是现有缓冲区的一个视图窗口。调用 slice()方法可以创建一个子缓冲区。

    @Test
    public void testConect3() throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        // 缓冲区中的数据 0-9
        for (int i = 0; i < buffer.capacity(); ++i) {
            buffer.put((byte) i);
        }
        // 创建子缓冲区
        buffer.position(3);
        buffer.limit(7);
        ByteBuffer slice = buffer.slice();
        // 改变子缓冲区的内容
        for (int i = 0; i < slice.capacity(); ++i) {
            byte b = slice.get(i);
            b *= 10;
            slice.put(i, b);
        }
        buffer.position(0);
        buffer.limit(buffer.capacity());
        while (buffer.remaining() > 0) {
            System.out.print(buffer.get()+" ");
        }
    }

只读缓冲区 

只读缓冲区非常简单,可以读取它们,但是不能向它们写入数据。可以通过调用缓冲 区的 asReadOnlyBuffer()方法,将任何常规缓冲区转 换为只读缓冲区,这个方法返回 一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化:

    @Test
    public void testConect4() throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        // 缓冲区中的数据 0-9
        for (int i = 0; i < buffer.capacity(); ++i) {
            buffer.put((byte) i);
        }

        // 创建只读缓冲区
        ByteBuffer readonly = buffer.asReadOnlyBuffer();
        // 改变原缓冲区的内容
        for (int i = 0; i < buffer.capacity(); ++i) {

            byte b = buffer.get(i);
            b *= 10;
            buffer.put(i, b);
        }
        readonly.position(0);
        readonly.limit(buffer.capacity());
        // 只读缓冲区的内容也随之改变
        while (readonly.remaining() > 0) {
            System.out.println(readonly.get());
        }
    }

如果尝试修改只读缓冲区的内容,则会报 ReadOnlyBufferException 异常。只读缓冲 区对于保护数据很有用。在将缓冲区传递给某个 对象的方法时,无法知道这个方法是 否会修改缓冲区中的数据。创建一个只读的缓冲区可以保证该缓冲区不会被修改。只 可以把常规缓冲区转换为只读缓冲区,而不能将只读的缓冲区转换为可写的缓冲区。

直接缓冲区 

直接缓冲区是为加快 I/O 速度,使用一种特殊方式为其分配内存的缓冲区,JDK 文档 中的描述为:给定一个直接字节缓冲区,Java 虚拟机将尽最大努力直接对它执行本机 I/O 操作。也就是说,它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后), 尝试避免将缓冲区的内容拷贝到一个中间缓冲区中 或者从一个中间缓冲区中拷贝数据。 要分配直接缓冲区,需要调用 allocateDirect()方法,而不是 allocate()方法,使用方 式与普通缓冲区并无区别。

拷贝文件示例:

    @Test
    public void testConect5() throws IOException {
        String infile = "d:\\atguigu\\01.txt";
        FileInputStream fin = new FileInputStream(infile);
        FileChannel fcin = fin.getChannel();
        String outfile = String.format("d:\\atguigu\\02.txt");
        FileOutputStream fout = new FileOutputStream(outfile);
        FileChannel fcout = fout.getChannel();
        // 使用 allocateDirect,而不是 allocate
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        while (true) {
            buffer.clear();
            int r = fcin.read(buffer);
            if (r == -1) {
                break;
            }
            buffer.flip();
            fcout.write(buffer);
        }
    }

内存映射文件 I/O

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快的多。内存映射文件 I/O 是通过使文件中的数据出现为 内存数组的内容来 完成的,这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。 一般来说,只有文件中实际读取或者写入的部分才会映射到内存中。

static private final int start = 0;
static private final int size = 1024;
static public void main(String args[]) throws Exception {
    RandomAccessFile raf = new RandomAccessFile("d:\\atguigu\\01.txt", "rw");
    FileChannel fc = raf.getChannel();
    MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 
    start, size);
    mbb.put(0, (byte) 97);
    mbb.put(1023, (byte) 122);raf.close();
}

ByteBuffer的大小分配

  • 每个 channel 都需要记录可能被切分的消息,因为 ByteBuffer 不能被多个 channel 共同使用,因此需要为每个 channel 维护一个独立的 ByteBuffer
  • ByteBuffer 不能太大,比如一个 ByteBuffer 1Mb 的话,要支持百万连接就要 1Tb 内存,因此需要设计大小可变的 ByteBuffer
    • 一种思路是首先分配一个较小的 buffer,例如 4k,如果发现数据不够,再分配 8k 的 buffer,将 4k buffer 内容拷贝至 8k buffer,优点是消息连续容易处理,缺点是数据拷贝耗费性能,参考实现 Java Resizable ArraySometimes you want to keep data in a single, consecutive array for fast and easy access, but need the array to be resizable, or at least expandable. This tutorial shows you how to implement a resizable array in Java.http://tutorials.jenkov.com/java-performance/resizable-array.html
    • 另一种思路是用多个数组组成 buffer,一个数组不够,把多出来的内容写入新的数组,与前面的区别是消息存储不连续解析复杂,优点是避免了拷贝引起的性能损耗

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/596078.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ Lambda 表达式:深入理解与应用

C Lambda 表达式是 C11 标准引入的一项强大功能&#xff0c;它允许开发者以简洁、优雅的方式创建匿名函数对象。 本文将深入探讨 C Lambda 表达式的原理、语法和应用场景&#xff0c;帮助读者更好地理解和使用这一功能。 1. Lambda 表达式简介 Lambda 表达式是一种创建匿名函数…

18. Vue-element-template白天黑夜模式动态切换

两套主题动态切换 1. 去官网生成两套主题拷贝到 resources/src/assets/theme https://element.eleme.cn/#/zh-CN/theme 2. 也可以本地修改 element-variables.scss 然后运行et生成 安装 &#xff08;注意Node版本&#xff09; ➜ Genes-Admin git:(ogenes) sudo n 10.16.…

【车载开发系列】Autosar DEM基本概念

【车载开发系列】Autosar DEM基本概念 Autosar DEM基本概念 【车载开发系列】Autosar DEM基本概念一. 诊断事件管理(DEM)概念二. DEM的主要作用1、汽车检修提供数据2、汽车错误状态处理提供依据 三. DEM模块及关联模块关系1. 功能禁止模块FIM2. SWC和BSW3. NvM非易失性存储4. 诊…

分布式存储Ceph介绍及搭建

一&#xff1a;存储的类型 1.单机存储设备 ●DAS&#xff08;直接附加存储&#xff0c;是直接接到计算机的主板总线上去的存储&#xff09; IDE、SATA、SCSI、SAS、USB 接口的磁盘 所谓接口就是一种存储设备驱动下的磁盘设备&#xff0c;提供块级别的存储 ●NAS&#xff08;…

一、docker-compose方式安装运行Jenkins

docker-compose方式安装运行Jenkins 服务器系统&#xff1a;centos 7.6 以docker-compose 编排容器方式安装&#xff0c;当然需提前安装docker-compose环境&#xff08;见百度->docker-compose环境安装&#xff09; docker-compose.yml version: 3.1 services:jenkins:i…

率先领跑!人大金仓布局“数字医疗”护航健康中国

近日&#xff0c;中国医院信息网络大会(CHIMA 2023)在福州圆满结束。作为数据库领域唯一参展企业&#xff0c;人大金仓携一系列“数字医疗”国产数据库解决方案亮相&#xff0c;在激发数据价值&#xff0c;促进数据资源整合利用&#xff0c;确保数据安全使用等方面的突出表现和…

死神来了 | 高铁出轨:“德国的泰坦尼克号事件”

点击文末“阅读原文”即可收听本期节目 剪辑、音频 / 伊姐 运营 / SandLiu 卷圈 封面 / 姝琦Midjourney 监制 / 姝琦 产品统筹 / bobo 场地支持 / 声湃轩天津录音间 德国高铁出轨事故是1998年6月3日发生于德国下萨克森州策勒县艾雪德镇附近的严重铁路事故&#xff0c;造成…

华为OD机试真题B卷 Java 实现【合法IP】,附详细解题思路

一、题目描述 IPV4地址可以用一个32位无符号整数来表示&#xff0c;一般用点分方式来显示&#xff0c;点将IP地址分成4个部分&#xff0c;每个部分为8位&#xff0c;表示成一个无符号整数&#xff08;因此正号不需要出现&#xff09;&#xff0c;如10.137.17.1&#xff0c;是我…

在中文LLaMA模型上进行精调

最近在开源项目ymcui/Chinese-LLaMA-Alpaca的基础上完成了自己的中文模型精调工作&#xff0c;形成了两个工具共享给大家。ymcui/Chinese-LLaMA-Alpaca 构建指令形式的精调文件 如果用于精调&#xff0c;首先要准备精调数据&#xff0c;目标用途如果是问答&#xff0c;需要按…

【下篇】我们邀请了4位专家来探讨消费市场的新增量:W型机会、单客经济、日本市场、DTC......

在4月底的时候&#xff0c;我们举办了一场线上直播活动&#xff0c;有幸邀请到了4位消费零售行业的专家&#xff0c;我本人与他们一起探讨如何寻找市场的新增量&#xff0c;思考品牌如何找到新机会。本篇内容就是将专家们的观点进行了梳理和总结。 接上篇内容&#xff1a; 本篇…

【六·一】就做个纯粹的小小游戏吧

写在前面&#xff1a;博主是一只经过实战开发历练后投身培训事业的“小山猪”&#xff0c;昵称取自动画片《狮子王》中的“彭彭”&#xff0c;总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域&#xff0c;如今终有小成…

Java学习(maven)——maven新建项目 常用IO工具 Durid数据库工具 案例

引出 如何用maven新建项目&#xff0c;用maven建项目的优势&#xff0c;常用的io工具和durid工具 用Maven建项目 0.Maven配置方式 参考博客 &#xff1a; 【配置】Maven的配置 & Tomcat的配置 & 在IDEA中新建web项目 中的maven配置 1.io流的工具IOUtils/FileUtils…

ROS:一些基本命令行

目录 一、打开小海龟1.1终端&#xff0c;启动ROS Master&#xff1a;1.2终端2&#xff0c;启动小海龟仿真器&#xff1a;1.3终端3&#xff0c;启动海龟控制节点&#xff1a; 二、查看系统中的计算图三、节点命令3.1查看节点下的命令rosnode3.2显示节点列表rosnode list3.3查看节…

【致敬未来的攻城狮计划】打卡1:rasc+keil环境搭建

前言 这回参加的是csdn李肯老师的攻城狮计划&#xff0c;简单说就是我白嫖板子&#xff0c;输出学习笔记。 板子是瑞萨的CPK_RA2E1&#xff0c;还有触摸元件&#xff0c;看起来很有意思hh。 环境搭建 一开始决定采取vscode搭建的方式。后期进行到最后一步——cmake build的时…

多链路传输技术在火山引擎 RTC 的探索和实践

动手点关注 干货不迷路 传统的数据传输方式大多是利用一个链路、选择设备的默认网卡进行传输&#xff0c;使用这种方式实现实时音视频通话时&#xff0c;如果默认网络出现问题&#xff08;如断网、弱网等&#xff09;&#xff0c;用户的通信就会发生中断或者卡顿&#xff0c;影…

chatgpt赋能python:Python倒序函数:让你的列表逆转!

Python倒序函数&#xff1a;让你的列表逆转&#xff01; Python作为一种高级编程语言&#xff0c;代码简洁易学&#xff0c;因此被广泛使用。对于开发者而言&#xff0c;逆转列表&#xff08;list&#xff09;是很常见的需求。Python提供了一个内置函数reverse()来解决这个问题…

前端技术搭建扫雷小游戏(内含源码)

The sand accumulates to form a pagoda ✨ 写在前面✨ 功能介绍✨ 页面搭建✨ 样式设置✨ 逻辑部分 ✨ 写在前面 上周我们实通过前端基础实现了贪吃蛇游戏&#xff0c;今天还是继续按照我们原定的节奏来带领大家完成一个游戏&#xff0c;功能也比较简单简单&#xff0c;也是想…

钉钉斜杠“/”开启邀请测试;用ChatGPT写一个数据采集程序

&#x1f989; AI新闻 &#x1f680; 钉钉斜杠“/”开启邀请测试&#xff0c;AI全面智能化助力企业生产 摘要&#xff1a;钉钉斜杠“/”邀请测试开启&#xff0c;应用于文档、群聊、视频会议、应用开发等场景&#xff0c;为企业用户提供多项AI智能能力&#xff0c;如创作、汇…

【AUTOSAR】Com通讯栈配置说明(五)---- ComM模块

ComM模块 ComMConfigSet ComMChannels ComMBusType&#xff1a;定义总线类型 ComMChannelId&#xff1a;通道号 ComMFullCommRequestNotificationEnabled&#xff1a;未使用 ComMComMGlobalNvmBlockDescriptor&#xff1a;未使用 ComMMainFunctionPeriod&#xff1a;ComM的…

【云原生•监控】mtail轻量日志监控系统

【云原生•监控】mtail轻量日志监控系统 前言 「笔者已经在公有云上搭建了一套临时环境&#xff0c;可以先登录体验下&#xff1a;」 http://124.222.45.207:17000/login 账号&#xff1a;root/root.2020 简介 「可观测性平台三大支柱&#xff1a;日志监控、调用链监控和度量指…