NIO核心一:缓冲区 Buffer

一、简单介绍

Buffer,顾名思义就是缓冲区的意思,它是NIO中数据交换的载体,实质上是一种承载数据的容器。在上一篇BIO文章中我们提到BIO的工作模式是使用流来进行数据交换,并且根据操作的不同,分为输入流和输出流两种,而在NIO中则是以Buffer来进行数据交换的,例如后面将要提到的Channel中,都是通过Buffer来进行读写操作的,Buffer可以同时进行输入和输出操作。

二、Buffer类及其子类

Buffer类在jdk中是一个抽象类,它实现了Buffer的一些共性的设计。

上述缓冲区除了ByteBuffer的功能稍微多点外,因为ByteBuffer是通用的,所以功能会比较多。其他6种的使用方式几乎是一致的。都是通过如下方法获取一个 Buffer对象:

java 复制代码
static XxxBuffer allocate(int capacity) : 创建一个容量为capacity 的 XxxBuffer 对象

三、Buffer的参数说明以及常用方法

Buffer中的重要概念

  • 容量(capacity): 缓冲区支持的最大容量。缓冲区不能为负,且创建后不能更改
  • 界限(limit): 表示缓冲区可以操作数据的大小(limit)后边是不能写数据的。缓冲区的限制不能为负,并且不能大于缓冲区的容量。写入模式,限制buffer的容量。在读取模式下,limit等于写入的数据量。
  • 位置(postion): 下一个要读取或写入数据的索引。缓冲区位置不能为负,并且不能大于其限制
  • 标记(mark)与重置(reset): 标记是一个索引,通过Buffer中的mark()方法指向Buffer中的一个特定的position,之后可以通过调用reset()方法恢复到这个位置的position.
    标记、位置、限制、容量遵循以下不等式: 0 <= mark <= position <= limit <= capacity
    图片理解Buffer读写数据的流程变化

Buffer的常用方法

java 复制代码
Buffer clear() 清空缓冲区并返回对缓冲区的引用
Buffer flip()   将缓冲区的界限设置为当前位置,并将当前位置重置为 0
int capacity() 返回 Buffer的capacity 大小
boolean hasRemaining() 判断缓冲区中是否还有元素
int limit() 返回 Buffer 的界限(limit) 的位置
Buffer limit(int n) 将设置缓冲区界限为 n,并返回一个具有新 limit 的缓冲区对象
Buffer mark() 对缓冲区设置标记
int position() 返回缓冲区的当前位置 position
Buffer position(int n) 将设置缓冲区的当前位置为 n,并返回修改后的 Buffer 对象
int remaining() 返回 position 和 limit 之间的元素个数
Buffer reset() 将位置 position 转到以前设置的mark所在的位置
Buffer rewind() 将位置设为为 0, 取消设置的 mark

方式测试

java 复制代码
public class NIOTest {
    public static void main(String[] args) {
        bufferTest();
    }
    public static void bufferTest(){
        //1. 分配一个指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        System.out.println("-----------------allocate()----------------");
        System.out.println(buf.position());  //返回缓冲区的当前位置 0
        System.out.println(buf.limit());     //界限(limit) 的位置 1024
        System.out.println(buf.capacity()); // Buffer的capacity 大小 1024

        // 2.put向缓冲区中添加数据,利用 put() 存入数据到缓冲区中
        String str = "qcby";
        buf.put(str.getBytes());
        System.out.println("-----------------put()----------------");
        System.out.println(buf.position()); //返回缓冲区的当前位置 4
        System.out.println(buf.limit());  // 界限(limit) 的位置 1024
        System.out.println(buf.capacity()); // Buffer的capacity 大小 1024

        //3. 切换读取数据模式
        buf.flip();
        System.out.println("-----------------flip()----------------");
        System.out.println(buf.position()); //返回缓冲区的当前位置 0
        System.out.println(buf.limit());  // 界限(limit) 的位置 4
        System.out.println(buf.capacity());  // Buffer的capacity 大小 1024

        //这种读取不可重复读
        System.out.println("-----------------get()----------------");
        //4. 利用 get() 读取缓冲区中的数据 ---- 前提是必须切换为读取模式
        byte[] dst = new byte[buf.limit()];
        buf.get(dst);
        System.out.println(new String(dst, 0, dst.length));
        System.out.println(buf.position()); //返回缓冲区的当前位置 4
        System.out.println(buf.limit()); // 界限(limit) 的位置 4
        System.out.println(buf.capacity()); // Buffer的capacity 大小 1024

        System.out.println("-----------------rewind()----------------");
        //5. rewind() : 可重复读
        buf.rewind();
        byte[] dst1 = new byte[buf.limit()];
        buf.get(dst1);
        System.out.println(new String(dst1, 0, dst.length));
        System.out.println(buf.position()); //返回缓冲区的当前位置 4
        System.out.println(buf.limit()); // 界限(limit) 的位置 4
        System.out.println(buf.capacity()); // Buffer的capacity 大小 1024

        //6. clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于"被遗忘"状态
        buf.clear();
        System.out.println("-----------------clear()----------------");
        System.out.println(buf.position()); //返回缓冲区的当前位置 0
        System.out.println(buf.limit());  // 界限(limit) 的位置 1024
        System.out.println(buf.capacity());  // 界限(limit) 的位置 1024
        System.out.println((char)buf.get()); // q
    }
}
java 复制代码
public class NIOTest {
    public static void main(String[] args) {
        bufferTest();
    }
    public static void bufferTest(){
        String str = "hello world";
        ByteBuffer buf = ByteBuffer.allocate(1024); //创建一个缓冲区为1024的buffer数组
        buf.put(str.getBytes()); // 将数据放入
        buf.flip();  //调用flip()方法,转换为读取模式
        byte[] dst = new byte[buf.limit()]; //创建一个buffer界限位置的数组
        buf.get(dst, 0, 2);//将buffer数组当中前前两个数据放入dst数组当中
        System.out.println(new String(dst, 0, 2));//输出数据
        System.out.println(buf.position()); //位置 (position):下一个要读取或写入的数据的索引。
        //mark() : 标记
        buf.mark(); //标记是一个索引,通过Buffer中的mark()方法指向Buffer中的一个特定的position
        buf.get(dst, 2, 2);
        System.out.println(new String(dst, 2, 2));
        System.out.println(buf.position());
        //reset() : 恢复到 mark 的位置
        buf.reset();
        System.out.println(buf.position());
        //判断缓冲区中是否还有剩余数据
        if(buf.hasRemaining()){
            //获取缓冲区中可以操作的数量
            System.out.println(buf.remaining());
        }
    }
}

四、Buffer的基本用法

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

①:写入数据到Buffer

②:调用flip()方法,将Buffer从写模式切换到读模式

③:从Buffer中读取数据

④:调用clear()方法或者compact()方法

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

五、选择直接内存还是非直接内存

直接缓冲区与非直接缓冲区区别图形示意:

很明显,在做IO处理时,比如网络发送大量数据时,直接内存会具有更高的效率。直接内存使用allocateDirect创建,但是它比申请普通的堆内存需要耗费更高的性能。不过,这部分的数据是在JVM之外的,因此它不会占用应用的内存。所以呢,当你有很大的数据要缓存,并且它的生命周期又很长,那么就比较适合使用直接内存。只是一般来说,如果不是能带来很明显的性能提升,还是推荐直接使用堆内存。字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。

java 复制代码
public static void bufferTest(){
        //分配直接缓冲区
        ByteBuffer buf = ByteBuffer.allocateDirect(1024);
        System.out.println(buf.isDirect());
}

使用场景

1 有很大的数据需要存储,它的生命周期又很长

2 适合频繁的IO操作,比如网络并发场景

相关推荐
云飞云共享云桌面3 分钟前
传统工作站 vs 云飞云共享云桌面:制造业设计云桌面选型深度对比
运维·服务器·前端·网络·3d·架构·制造
huangdong_5 分钟前
电商平台图片URL原图转换技术深度解析:从缩略图到高清原图的完整方案
java·后端·spring
記億揺晃着的那天20 分钟前
Java 调用外部 Go 程序的实践:ProcessBuilder 在生产环境中的应用
java·golang·processbuilder
JAVA面经实录91730 分钟前
Java 数据结构与算法 (终极完整学习文档)
java·数据结构·算法
JAVA面经实录9171 小时前
操作系统面试题
java·服务器·数据库·计算机网络·面试
一杯奶茶¥2 小时前
基于springboot的失物招领管理系统带万字文档 校园失物招领管理系统 失物认领管理系统java springboot vue
java·vue.js·spring boot·java项目
不能只会打代码2 小时前
边缘视频分析平台的架构设计与性能优化——从750ms到190ms的调优之路
java·spring boot·redis·性能优化·边缘计算·物联网竞赛
小刘|2 小时前
Spring AI Alibaba 集成和风天气 API 实战
java·服务器·前端
KANGBboy2 小时前
java知识五(继承)
java·开发语言
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?
java·开发语言·面试