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

相关推荐
久绊A1 小时前
网络信息系统的整个生命周期
网络
m0_571957581 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
_PowerShell1 小时前
[ DOS 命令基础 3 ] DOS 命令详解-文件操作相关命令
网络·dos命令入门到精通·dos命令基础·dos命令之文件操作命令详解·文件复制命令详解·文件对比命令详解·文件删除命令详解·文件查找命令详解
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
_.Switch4 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
qq_254674414 小时前
工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置
网络
JokerSZ.4 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全