【NIO】Buffer(缓冲区)

本文详细介绍了Java NIO中Buffer的使用方法,包括Buffer的基本概念、读写操作、常用方法等,并通过具体示例展示了如何利用Buffer进行文件复制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

Github:https://github.com/yihonglei/jdk-source-code-reading(java-nio)

一 Buffer 概述

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

写到通道。缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存数组。

这块内存被包装成 NIO Buffer 对象,并提供了一组方法,用来方便的访问该块内存。

二 Buffer 基础

1、Buffer 基本用法

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

1)写入数据到 Buffer,一般有可以从 Channel 中读取到缓冲区,也可以调用 put 方法写入;

2)调用 flip() 方法,切换数据读写模式;

3)从 Buffer 中读取数据到 Channel;

4)调用 clear() 方法或者 compact() 方法清理缓冲区;

当向 buffer 写入数据时,buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip()

方法将 Buffer 从写模式切换到读模式。在读模式下,可以读取之前写入到 buffer 的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。

有两种方式能清空缓冲区:

1)clear() 方法会清空整个缓冲区。

2)compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,

新写入的数据将放到缓冲区未读数据的后面。

package com.jpeony.nio.buffer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 使用Buffer读写数据一般遵循以下四个步骤:
 * 1)写入数据到 Buffer,一般有可以从 Channel 中读取到缓冲区,也可以调用 put 方法写入;
 * 2)调用 flip() 方法,切换数据读写模式;
 * 3)从 Buffer 中读取数据到 Channel;
 * 4)调用 clear() 方法或者 compact() 方法清理缓冲区;
 * 当向 buffer 写入数据时,buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip() 方法将 Buffer 从写模式切换到读模式。
 * 在读模式下,可以读取之前写入到 buffer 的所有数据。一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
 * 有两种方式能清空缓冲区:
 * 1)clear() 方法会清空整个缓冲区。
 * 2)compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
 *
 * @author yihonglei
 */
public class BufferRWTest {
    /**
     * 文件复制实例
     */
    public static void main(String[] args) {
        // 源文件
        File fromFile = new File("/Users/yihonglei/tmp/hello.txt");
        // 目标文件
        File toFile = new File("/Users/yihonglei/tmp/hello-copy.txt");

        try (
                // 根据源文件创建文件输入流
                FileInputStream fis = new FileInputStream(fromFile);
                // 根据目标文件创建文件输出流,如果文件不存在,自动创建
                FileOutputStream fos = new FileOutputStream(toFile);

                // 1、获取通道
                FileChannel inChannel = fis.getChannel();
                FileChannel outChannel = fos.getChannel();
        ) {
            // 2、分配指定大小的缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(48);

            // 3、将通道中的数据读取到缓冲区
            while (inChannel.read(buffer) != -1) {
                // 切换成读数据模式
                buffer.flip();

                // 4、从缓冲区读取数据写入到通道中
                outChannel.write(buffer);

                // 5、清空缓冲区
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2、Buffer 的 capacity、position 和 limit

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成 Buffer

对象,并提供了一组方法,用来方便的访问该块内存。为了理解 Buffer 的工作原理,需要熟悉

它的三个属性:

  • capacity
  • position
  • limit

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

capacity 的含义总是一样的。这里有一个关于 capacity,position 和 limit 在读写模式中的说明:

capacity

作为一个内存块,Buffer 有一个固定的大小值,也叫 "capacity"。你只能往里写 capacity 个

byte、long,char 等类型。一旦 Buffer 满了,需要将其清空(通过读数据或者清除数据)

才能继续写数据往里写数据。

position

在写模式下,position 指向下一个可写位置。初始 position 值为 0。position 最大可为

capacity –1 当读取数据时,也是从某个特定位置读。

在读模式下,position 会被重置为 0,指向下一个可读位置。

limit

在写模式下,Buffer 的 limit 表示你最多能往 Buffer 里写多少数据。写模式下,

limit 等于 Buffer 的 capacity。

当读模式下,limit 表示你最多能读到多少数据。因此,当切换 Buffer 到读模式时,

limit 会被设置成写模式下的 position 值。

换句话说,你能读到之前写入的所有数据,即 limit 被设置成已写数据的数量,这个值在写模式

下就是 position,因为 position 从 0 开始,就像上图的写入 ABCD 四个值,position 指向第 5 个

元素位置,即 position 是4,切换到读模式下,将 position 赋值给 limit,即 limit 是4,表示最多

能读取多少个元素。

3、Buffer 的类型

如下是 Java NIO 中最重要的缓冲区的实现:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
  • MappedByteBuffer(堆内)
  • DirectByteBuffer(直接内存)

这些 Buffer 覆盖了你能通过 IO 发送的基本数据类型:byte、short、int、long、float、double

和 char。

4、Buffer 的分配

要想获得一个 Buffer 对象首先要进行分配。每一个 Buffer 类都有一个 allocate 方法。

分配 48 字节 capacity 的 ByteBuffer 的例子:

ByteBuffer buf = ByteBuffer.allocate(48);

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

CharBuffer buf = CharBuffer.allocate(1024);

5、向 Buffer 中写入数据

写数据到 Buffer 有两种方式:

1)从 Channel 写到 Buffer。

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

从 Channel 写到 Buffer 的例子:

int bytesRead = inChannel.read(buf); // 从 Channel(通道)读取到 Buffer(缓冲区)中

通过 put 方法写 Buffer 的例子:

buf.put(127);

put 方法有很多版本,允许你以不同的方式把数据写入到 Buffer 中。

例如,写到一个指定的位置,或者把一个字节数组写入到 Buffer。

6、flip() 方法使用

flip 方法将 Buffer 从写模式切换到读模式。调用 flip() 方法会将 position 设回 0,并将 limit

设置成之前 position 的值。换句话说,position 现在用于标记读的位置,limit 表示之前写进

了多少个 byte、char 等现在能读取多少个 byte、char 等。

7、从 Buffer 中读取数据

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

1)从 Buffer 读取数据到 Channel。

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

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

// 从 Buffer 中读取数据写入到通道中

int bytesWritten = inChannel.write(buf);

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

byte aByte = buf.get();

get 方法有很多版本,允许你以不同的方式从 Buffer 中读取数据。

例如,从指定 position 读取,或者从 Buffer 中读取数据到字节数组。

8、rewind() 方法

Buffer.rewind() 将 position 设回 0,所以你可以重读 Buffer 中的所有数据。

limit 保持不变,仍然表示能从 Buffer 中读取多少个元素(byte、char等)。

9、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 准备好写数据了,但是不会覆盖未读的数据。

10、mark() 和 reset() 方法

通过调用 Buffer.mark()方法,可以标记 Buffer 中的一个特定 position。

之后可以通过调用 Buffer.reset()方法恢复到这个 position。例如:

buffer.mark();

// 调用 buffer 读写方法,下面通过调用 reset 恢复到调用前的 position 位置。

buffer.reset(); // set position back to mark

11、equals() 和 compareTo() 方法

可以使用 equals() 和 compareTo() 方法两个 Buffer。

equals()

当满足下列条件时,表示两个Buffer相等。

1)有相同的类型(byte、char、int等)。

2)Buffer 中剩余的 byte、char 等的个数相等。

3)Buffer 中所有剩余的 byte、char 等都相同。

如你所见,equals 只是比较 Buffer 的一部分,不是每一个在它里面的元素都比较。

实际上,它只比较 Buffer 中的剩余元素。

compareTo()

compareTo() 方法比较两个 Buffer 的剩余元素(byte、char等),如果满足下列条件,

则认为一个 Buffer "小于"另一个 Buffer。

1)第一个不相等的元素小于另一个 Buffer 中对应的元素。

2)所有元素都相等,但第一个 Buffer 比另一个先耗尽(第一个 Buffer 的元素个数比另一个少)。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值