【Java进阶】NIO(二)<Buffer>

一、Buffer的属性

1. 介绍

  • Buffer缓冲区实际上就是一个数组,把数组的内容和信息包装成一个Buffer对象,它提供了一组访问这些信息的方法

2. 重要属性

在这里插入图片描述

  • capacity(容量)
    ① 指缓冲区可以存储多少个数据,容量在创建Buffer缓冲区时指定大小,创建之后不能再修改
    ② 如果缓冲区满了,需要清空后才能继续写数据
  • position(当前位置)
    ① 指缓冲区写入/读取的位置,刚刚创建Buffer对象后,position初始化为0,写入一个数据后,position就向后移动一个单元,它的最大值是capacity-1
    ② 当Buffer从写模式切换到读模式,position会被重置为0,从Buffer的开始位置读取数据,每读取一个数据,position就向后移动一个单元
  • limit(上限)
    ① 指第一个不能被读出或写入的位置,limit上限后面的单元既不能读也不能写
    ② 在Buffer缓冲区的写模式下,limit表示能够写入多少个数据;在读取模式下,limit表示最多可以读取多少个数据
  • mark(标记)
    ① 设置一个标记,可以调用mark()方法,把标记设置在position位置,当调用reset()方法时,就把position设置为mark标记的位置
  • 总之:0 <= mark <= position <= limit <= capacity

二、Buffer的常用API

1. 介绍

  • 在NIO中关键的Buffer
    ① ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,这些Buffer覆盖了能够通过I/O发送的所有基本类型byte、char、double、float、int、long、short等
    ② 实际上使用较多的是ByteBuffer、CharBuffer

2. 重要方法

  • allocate()
    ① 每个缓冲区类都有一个静态方法allocate(capacity),可以创建一个指定容量的缓冲区
  • put()
    ① 用于向缓冲区中存储数据
  • get()
    ① 用于从缓冲区中读取数据
  • compact()
    ① 当缓冲区中还有未读完的数据时,可以调用compact()方法进行压缩,将所有未读取的数据复制到Buffer的起始位置,把position设置到最后一个未读元素的后面,limit属性设置为capacity
  • capacity()
    ① 返回缓冲区的大小
  • hasRemaining()
    ① 判断当前position后面是否还有可处理的数据,即判断position与limit之间是否还有数据可处理
  • limit()
    ① 返回limit上限的位置
  • mark()
    ① 设置缓冲区的标志位置,这个值只能在0~position之间,以后可以通过reset()方法返回到这个位置
  • position()
    ① 返回position当前位置
  • remaining()
    ① 返回当前position位置与limit之间的数量
  • reset()
    ① 可以将position设置为mark标志位
  • rewind()
    ① 将position设置为0,取消mark标志位
  • clear()
    ① 清空缓冲区,仅仅是修改position标志为0,设置limit为capacity,缓冲区中数据还是存在的
  • flip()
    ① 可以把缓冲区由写模式切换到读模式,先把limit设置为position位置,再把position设置为0

三、Buffer的常用API代码演示

1. Buffer的常用API操作

package buffer;

import java.nio.CharBuffer;

/**
 * Buffer常用API操作
 *
 * @author swaggyhang
 * @create 2023-06-11 9:12
 */
public class BufferApiDemo {
    public static void main(String[] args) {
        // 1 调用静态allocate()方法,创建缓冲区对象
        CharBuffer buf = CharBuffer.allocate(12);

        // 2 调用capacity()、limit()、position()方法,获取缓冲区的capacity、limit、position
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:12,limit:12,position:0

        // 3 调用put()方法,向缓冲区中存储数据
        buf.put('我');
        buf.put('有');
        buf.put('矿');
        buf.put('你');
        buf.put('爱');
        buf.put('我');
        buf.put('吗');
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:12,limit:12,position:7

        // 4 调用flip()方法,把缓冲区切换读模式
        buf.flip();
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:12,limit:7,position:0

        // 5 调用get()方法,读取缓冲区中的数据
        System.out.println(buf.get()); // 我
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:12,limit:7,position:1

        // 6 调用put()方法,再次存储数据,把数据保存在position位置
        buf.put('X');
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:12,limit:7,position:2

        // 7 调用mark()方法,把当前position设置为标志位
        buf.mark();

        // 8 调用get()方法,再读取一个字符
        System.out.println(buf.get());  // 矿
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:12,limit:7,position:3

        // 9 调用reset()方法,把position设置为mark()方法标志的位置
        buf.reset();
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:12,limit:7,position:2

        // 10 调用compact()方法,把缓冲区中未读取的数据复制到position为0的位置
        // position设置为复制的未读取的数据长度、limit设置为capacity
        buf.compact();
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:12,limit:12,position:5

        // 11 调用clear()方法,清空缓冲区,仅仅是修改position/limit的值
        buf.clear();
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:12,limit:12,position:0

        // 12 调用clear()方法,清空缓冲区后,缓冲区中的数据依然存在,非物理删除缓冲区中数据
        System.out.println(buf);    // 矿你爱我吗我吗
        // 通过循环把position与limit之间的内容逐个打印出来
        while (buf.hasRemaining()) {
            System.out.print(buf.get());
        }
    }
}

2. 缓冲区的批量传输

  • 可以进行批量的操作,借助于数组,把缓冲区中的一块数据读到数组中,也可以把数组中的部分内容保存到缓冲区中
  • 批量传输时大小总是固定的,如果没有指定传输的大小,意味着把数组填满
  • 当缓冲区的数据量不足以填满整个数组时,会抛出异常;在批量读取数据时,可以查询缓冲区中的剩余量
  • 将小缓冲区中的数据填充到大的数组时,要指定缓冲区剩余量的长度
package buffer;


import java.nio.CharBuffer;
import java.util.Arrays;

/**
 * Buffer批量传输操作
 *
 * @author swaggyhang
 * @create 2023-06-11 9:52
 */
public class BufferBatchDemo {
    public static void main(String[] args) {
        // 1 创建CharBuffer
        CharBuffer buf = CharBuffer.allocate(16);

        // 2 调用put()方法,将字符串保存到缓冲区中
        buf.put("hello动力节点的蛙课网很牛B");
        buf.flip(); // 翻转,切换为读模式
        System.out.println(buf);    // hello动力节点的蛙课网很牛B

        // 3 定义字符数组
        char[] dst = new char[12];
        // 调用get()方法,将缓冲区中的数据读取到字符数组中
        // 注意:批量传输时,大小总是固定的。如果没有指定传输大小,意味着把数组填满
        CharBuffer remainingCharBuffer = buf.get(dst);
        System.out.println(Arrays.toString(dst));   // [h, e, l, l, o, 动, 力, 节, 点, 的, 蛙, 课]
        System.out.println(remainingCharBuffer);    // 网很牛B

        // 4 继续把buf缓冲区中的内容读取到字符数组中
        // 当缓冲区中数据量不足以填满整个数组时,会抛出异常
        //buf.get(dst);   // java.nio.BufferUnderflowException

        // 5 在批量读取缓冲区数据时,需要查询缓冲区中的剩余量
        // 把小缓冲区的数据填充到大的数组时,要指定缓冲区剩余量的长度
        buf.get(dst, 0, buf.remaining());
        System.out.println(Arrays.toString(dst));   // [网, 很, 牛, B, o, 动, 力, 节, 点, 的, 蛙, 课]
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());

        // 6 循环读取缓冲区中的数据
        buf.clear();
        while (buf.hasRemaining()) {
            int len = Math.min(dst.length, buf.remaining());
            buf.get(dst, 0, len);
            System.out.print(new String(dst, 0, len));
        }
        System.out.println();   // hello动力节点的蛙课网很牛B

        // 7 批量写入
        char[] contents = {'a', 'b', 'c', 'd'};
        // 把字符数组中的数据保存到缓冲区中
        // 如果缓冲区中没有足够的空间,会抛出异常
        //buf.put(contents);  // java.nio.BufferOverflowException

        buf.position(14);
        buf.put(contents, 0, buf.remaining());
        buf.flip(); // 翻转,切换为读模式
        System.out.println(buf);    // hello动力节点的蛙课网很ab
    }
}

3. 缓冲区创建的两种方式

  • 分配操作创建缓冲区
    ① allocate()方法分配一个私有的、指定容量大小的数组来存储元素
    ② CharBuffer buf1 = CharBuffer.allocate(16);
  • 包装操作创建缓冲区
    ① 使用提供的数组作为存储空间来存储缓冲区的数据,不再分配其他空间
    ② char[] myarray = new char[16];CharBuffer buf2 = CharBuffer.wrap(myarray);
    ③ 对数组myarray的任何修改,也会影响缓冲区对象
  • 总之
    ① 不管是allocate()方法还是通过wrap()方法创建的缓冲区都是间接的,间接缓冲区会使用备份数组
    ② hasArray()方法可以判断是否有一个可存取的备份数组,如果hasArray()返回true,可以通过array()方法返回缓冲区对象使用的备份数组的引用
package buffer;

import java.nio.CharBuffer;
import java.util.Arrays;

/**
 * Buffer的两种创建方式
 *
 * @author swaggyhang
 * @create 2023-06-11 10:53
 */
public class BufferCreateDemo {
    public static void main(String[] args) {
        // 1) 调用allocate()方法,分配操作创建缓冲区
        CharBuffer buf1 = CharBuffer.allocate(16);

        // 2) 调用wrap()方法,使用包装操作创建缓冲区
        char[] myarry = new char[16];
        CharBuffer buf2 = CharBuffer.wrap(myarry);

        // 调用put()方法,向buf2中保存数据,会直接影响到数组myarray
        buf2.put("hello");
        buf2.flip();    // 翻转,切换到读模式
        System.out.println(buf2);   // hello
        System.out.println(Arrays.toString(myarry));    // [h, e, l, l, o,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ]

        // 对数组的任何修改,也会影响buf2缓冲区对象
        myarry[0] = 'X';
        buf2.position(0);   // 或者 buf2.clear();
        System.out.println(buf2);   // Xello
        System.out.println(Arrays.toString(myarry));    // [X, e, l, l, o,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ]

        /**
         * 不管是allocate(),还是wrap()创建的缓冲区都是间接的,间接缓冲区会使用备份数组
         * hasArray()方法,可以判断是否有一个可存取的备份数组
         * 如果hasArray()返回true,可以通过array()方法获取缓冲区对象使用的备份数组的引用
         */
        if (buf2.hasArray()) {
            char[] arr2 = buf2.array();
            System.out.println(Arrays.toString(arr2));  // [X, e, l, l, o,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ]
        }
    }
}

4. 缓冲区的复制与分隔

package buffer;

import java.nio.CharBuffer;

/**
 * Buffer的复制与分隔
 *
 * @author swaggyhang
 * @create 2023-06-11 11:07
 */
public class BufferDuplicateDemo {
    public static void main(String[] args) {
        // 1 创建缓冲区
        CharBuffer buf = CharBuffer.allocate(16);

        // 2 存储数据
        buf.put("hello");
        System.out.println("capacity:" + buf.capacity() + ",limit:" + buf.limit() + ",position:" + buf.position());
        // capacity:16,limit:16,position:5

        // 3 缓冲区的复制
        CharBuffer buf2 = buf.duplicate();
        System.out.println("capacity:" + buf2.capacity() + ",limit:" + buf2.limit() + ",position:" + buf2.position());
        // capacity:16,limit:16,position:5
        buf2.flip();    // 翻转,切换为读模式
        System.out.println(buf2);

        // 4 buf和buf2实际上引用的是同一个数组
        buf2.clear();
        buf2.put("NIOworld");
        buf.flip(); // 翻转,将buf切换为读模式,把buf的limit设置为position,position设置为0
        System.out.println(buf);    // NIOwo

        // 5 分隔缓冲区,slice()方法根据[position, limit)区间创建一个新的缓冲区
        buf2.position(3);
        CharBuffer buf3 = buf2.slice();
        System.out.println("capacity:" + buf3.capacity() + ",limit:" + buf3.limit() + ",position:" + buf3.position());
        // capacity:13,limit:13,position:0
    }
}

四、直接字节缓冲区

  • 在硬盘中和操作系统中处理的数据都是01二进制,缓冲区中只有ByteBuffer字节缓冲区有资格参与I/O操作
  • Channel通道只能使用ByteBuffer作为它的参数
  • 直接字节缓冲区通常是I/O操作是最好的选择,如果使用非直接字节缓冲区可能会导致性能损耗,如果向通道传递一个非直接字节缓冲区,通道可能会先创建一个临时的直接字节缓冲区,将非直接字节缓冲区的内容复制到这个临时的直接字节缓冲区中,使用临时直接字节缓冲区执行底层的I/O操作
  • 创建直接字节缓冲区的成本要高于非直接字节缓冲区,因为它使用的内存是通过调用本地操作系统的代码分配的,绕过了JVM的堆栈
  • ByteBuffer.allocateDirect()方法创建直接字节缓冲区
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值