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操作,比如网络并发场景

相关推荐
码爸几秒前
java 执行es中的sql
java·sql·elasticsearch
VaporGas14 分钟前
idea集成和使用Git指南
java·git·intellij-idea
阿乾之铭36 分钟前
spring MVC 拦截器
java·spring·mvc
码爸38 分钟前
flink 批量写clickhouse
java·clickhouse·flink
djgxfc41 分钟前
简单了解Maven与安装
java·maven
中文很快乐44 分钟前
springboot结合p6spy进行SQL监控
java·数据库·sql
丶白泽1 小时前
重修设计模式-概览
java·设计模式
小电玩1 小时前
谈谈你对Spring的理解
java·数据库·spring
五味香1 小时前
C++学习,动态内存
java·c语言·开发语言·jvm·c++·学习·算法
无名之逆1 小时前
计算机专业的就业方向
java·开发语言·c++·人工智能·git·考研·面试