java.nio.buffer详解

本文深入解析Java NIO中的Buffer概念,介绍其作为特定基本类型数据容器的作用,以及通过mark、position、limit等状态变量如何控制数据读写。探讨Buffer在面向缓冲区编程中的重要性,并详细解释了Buffer的各种操作方法。

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

Buffer简介

在Java NIO中,主要有三大基本的组件:Buffer、Channel和Selector,上一篇文章我们具体介绍了Selector,现在让我们深入理解下在实际编程中使用的最多的Buffer。

定义

首先先让我们来认识一下Buffer:

A container for data of a specific primitive type.
A buffer is a linear, finite sequence of elements of a specific primitive type

在Buffer类的描述中,Buffer被定义为用于特定基本类型数据的容器,且是特定基本类型的线性优先元素序列。

Buffer提供了一个字节缓冲区,它可以从channels中读取数据到Buffer,也可以将Buffer中的数据写入到channels,所以NIO被定义为面向缓冲区编程,而IO则是被定义为面向流的编程。

底层实现

通过阅读源码让我们慢慢揭开Buffer的神秘面纱

public abstract class Buffer {

    /************ 状态变量 ***********/
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
    
    /************ 访问方法 ***********/
    public final Buffer limit(int newLimit);
    public final Buffer mark();
    public final Buffer reset();
    public final Buffer clear();
    public final Buffer flip();
    public final Buffer rewind();
    // 剩余可读元素,limit - position
    public final int remaining();
    // 是否是只可读缓冲区
    public abstract boolean isReadOnly();
    // 是否是堆外内存
    public abstract boolean isDirect();

Buffer类一共有四个变量,被称之为状态变量

  • capacity:容量,必须初始化的值(因为底层是数组)
  • limit:上界,缓冲区的临界区,即最多可读到哪个位置
  • position:下标,当前读取到的位置(例如当前读出第5个元素,则读完后,position为6)
  • mark:标记,备忘位置

四个状态大小关系

0 <= mark <= position <= limit <= capacity

通过访问方法我们可以控制变量的指向,来达到读取到我们想要的数据的目的,下面让我们结合源码和图来学习下各方法对缓冲区的操作

ByteBuffer buffer = ByteBuffer.allocate(n);  (1)

buffer.limit(5);    (2)

// 执行get操作会将position加1
buffer.get();
buffer.get();

buffer.mark();      (3)

buffer.get();
buffer.get();

buffer.reset();     (4)

buffer.flip();      (5)

buffer.rewind();    (6)

buffer.reset();     (7)

(1)初始化Buffer
一个初始化的Buffer各变量位置指向如图(这里capacity大小为n):

 

 

(2)limit

    public final Buffer limit(int newLimit) {
        if ((newLimit > capacity) || (newLimit < 0))
            throw new IllegalArgumentException();
        // 设置limit的新位置
        limit = newLimit;
        // 如果当前读取到位置大于新设置的可读上限,则将position重置为limit,意思是缓存区无法再往下读取了
        if (position > limit) position = limit;
        // 如果mark的位置大于新设置的可读上限,则需要重置mark为-1;代表mark的位置失效了
        if (mark > limit) mark = -1;
        return this;
    }

所以如果我们执行limit(5)操作,则上图变化为

 

 

(3)mark

    public final Buffer mark() {
        // 记录当前读到的位置
        mark = position;
        return this;
    }

mark操作之前Buffer已经被读取到第二个位置(此时position=2),准备要读第三个元素之前,我们执行mark操作后,则图变化为

 

 

(4)reset

    // mark位置,将postion置为mark
    // 恢复到上次备忘的位置
    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

reset操作之前,我们执行了两次get操作,所以此时的position=4,状态变化为下图

 

 

执行reset后我们的状态又变为(3)操作

 

 

(5)flip
如果我们想重新读取上一次读取的内容,则可以执行flip操作

    // 将缓冲区的内容切换为重新读取状态
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

状态变化为

 

 

(6)rewind
重读缓冲区操作

    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

此时的操作结果与执行flip操作后一致;
但如果我们执行rewind之前不执行flip操作,则在(4)操作后,我们直接执行rewind,则状态变化为

 

 

(7)clear
恢复缓冲区至初始状态

    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

 

 

通过上面的实例及图讲解相信大家一定对Buffer加深了了解,所以我们再次简单的定义一下Buffer;Buffer缓冲区其实就是一个线性数组,通过mark、position、limit来控制读取和写入数组,补充一下Buffer的特性:

  • Buffer底层是线性数组,是有限的基本类型元素的组合
  • Buffer可以提供一个固定大小的容器来读取和写入数据
  • 每个Buffer都是可读的,但只有选中的buffer才可写
  • 默认情况下,Buffer不是线程安全的,所以在多线程环境下操作同一个Buffer,一定要使用锁机制保证同步



作者:locoder
链接:https://www.jianshu.com/p/60e925972b43
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值