【NIO】4. ByteBuffer的几个重要方法

ByteBuffer的几个重要方法

  • [1. 分配内存(allocate vs allocateDirect)](#1. 分配内存(allocate vs allocateDirect))
  • [2. 向Buffer写入数据](#2. 向Buffer写入数据)
    • [2.1 从Channel中读取数据并写入Buffer](#2.1 从Channel中读取数据并写入Buffer)
    • [2.2 调用Buffer的put方法](#2.2 调用Buffer的put方法)
  • [3. 从Buffer中读取数据](#3. 从Buffer中读取数据)
    • [3.1 从Buffer中读取数据,写入Channel](#3.1 从Buffer中读取数据,写入Channel)
    • [3.2 调用Buffer的get(index)方法,获取指定索引位置的数据](#3.2 调用Buffer的get(index)方法,获取指定索引位置的数据)
    • [3.3 调用Buffer的get()方法,获取当前position位置的数据](#3.3 调用Buffer的get()方法,获取当前position位置的数据)
  • [4. mark & reset](#4. mark & reset)
  • [5. 将字符串转为buffer](#5. 将字符串转为buffer)
    • [5.1 将字符串转为字节数组,写入ByteBuffer](#5.1 将字符串转为字节数组,写入ByteBuffer)
    • [5.2 使用Charset的encode方法,将字符串包装成ByteBuffer](#5.2 使用Charset的encode方法,将字符串包装成ByteBuffer)
    • [5.3 使用ByteBuffer的wrap方法,将字符串包装成ByteBuffer](#5.3 使用ByteBuffer的wrap方法,将字符串包装成ByteBuffer)
  • [6. 分散读 & 集中写(了解)](#6. 分散读 & 集中写(了解))
    • [6.1 分散读](#6.1 分散读)
    • [6.2 集中写](#6.2 集中写)

1. 分配内存(allocate vs allocateDirect)

从内存中划分一块区域给ByteBuffer有两种方式

  • 方式一:从【JVM堆内存】中分配;
  • 方式二:从【直接内存】中分配(操作系统内存);

JVM堆内存的读写效率比直接内存低;因为

  • JVM堆内存多一次数据拷贝;
  • 堆内存受到JVM垃圾回收的影响;
java 复制代码
public static void main(String[] args) {

        // 从【JVM堆内存】中分配
        ByteBuffer allocate = ByteBuffer.allocate(16);

        // 从【直接内存】中分配(物理内存)
        ByteBuffer allocateDirect = ByteBuffer.allocateDirect(16);

        System.out.println(allocate.getClass()); // class java.nio.HeapByteBuffer
        System.out.println(allocateDirect.getClass()); // class java.nio.DirectByteBuffer

    }
对比项 allocate(堆) allocateDirect(直接)
内存位置 JVM 堆 堆外(OS 内存)
分配/回收速度 快 慢
I/O 性能 较低(需复制) 高(零拷贝)
适用场景 普通场景 高频 I/O、大文件

2. 向Buffer写入数据

2.1 从Channel中读取数据并写入Buffer

java 复制代码
public static void main(String[] args) {
        try(FileInputStream fileInputStream = new FileInputStream("src/main/resources/Buffer01_HelloWorld.txt")) {
            // 获取文件channel
            FileChannel channel = fileInputStream.getChannel();

            // 从内存中分配一个buffer,大小为10个字节
            ByteBuffer byteBuffer = ByteBuffer.allocate(10);

            // 从channel中读取数据,并写到buffer中
            channel.read(byteBuffer);


        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

2.2 调用Buffer的put方法

java 复制代码
public static void main(String[] args) {
        // 从内存中分配一个buffer,大小为10个字节
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);

        // 写入一个byte
        byteBuffer.put((byte) 1);

        // 写入byte数组
        byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));
    }

3. 从Buffer中读取数据

注意:在从buffer中读取数据之前,必须调用flip方法切换到读模式!

3.1 从Buffer中读取数据,写入Channel

java 复制代码
public static void main(String[] args) {
        try (FileOutputStream fileOutputStream = new FileOutputStream("src/main/resources/test.txt")) {
            // 获取文件channel
            FileChannel channel = fileOutputStream.getChannel();

            // 从内存中分配一个buffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(10);

            // 向buffer中写入数据
            byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));

            // 切换到buffer的读模式
            byteBuffer.flip();

            // 从buffer中读取数据,写入channel
            int write = channel.write(byteBuffer);

            // 打印写入的字节数:3
            System.out.println(write);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

3.2 调用Buffer的get(index)方法,获取指定索引位置的数据

该方法不会移动position的位置,就像操作数组一样,可以重复读取;

java 复制代码
public static void main(String[] args) {
        // 从内存中分配一个buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);

        // 向buffer中写入数据
        byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));

        // 切换到buffer的读模式
        byteBuffer.flip();

        // get(index),不会移动position的位置,就像操作数组一样,可以重复读取;
        System.out.println(byteBuffer.get(0)); // 97
        System.out.println(byteBuffer.get(1)); // 98
        System.out.println(byteBuffer.get(2)); // 99
        System.out.println(byteBuffer.get(0)); // 97
    }

3.3 调用Buffer的get()方法,获取当前position位置的数据

该方法会让position移动,当读取完Buffer中的数据后,position也移动到Buffer的末尾,此时再读取就会出异常:java.nio.BufferUnderflowException

java 复制代码
public static void main(String[] args) {
        // 从内存中分配一个buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);

        // 向buffer中写入数据
        byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));

        // 切换到buffer的读模式
        byteBuffer.flip();

        // get(),会让position移动
        System.out.println(byteBuffer.get());
        System.out.println(byteBuffer.get());
        System.out.println(byteBuffer.get());

        System.out.println(byteBuffer.get());
        
    }

输出结果

text 复制代码
"C:\Program Files\Java\jdk-1.8\bin\java.exe" ...
97
98
99
Exception in thread "main" java.nio.BufferUnderflowException
	at java.nio.Buffer.nextGetIndex(Buffer.java:524)
	at java.nio.HeapByteBuffer.get(HeapByteBuffer.java:138)
	at com.xxx.xxx.main(xxx.java:26)

Process finished with exit code 1

如果想重复读取数据,可以调用rewind方法,将position重新置为0;

java 复制代码
public static void main(String[] args) {
        // 从内存中分配一个buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);

        // 向buffer中写入数据
        byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));

        // 切换到buffer的读模式
        byteBuffer.flip();

        // get(),会让position移动
        System.out.println(byteBuffer.get()); //97
        System.out.println(byteBuffer.get()); //98
        System.out.println(byteBuffer.get()); //99

        // 调用rewind方法,将position重新置为0
        byteBuffer.rewind();

        System.out.println(byteBuffer.get()); //97

    }

4. mark & reset

  • mark():使用变量mark,记住当前position的值;
  • reset():将position的值重新设为mark;
java 复制代码
public static void main(String[] args) {
        // 从内存中分配一个buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);

        // 向buffer中写入数据
        byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));

        // 切换到buffer的读模式
        byteBuffer.flip();

        // 使用变量mark,记住当前position的值,当前position 即0;
        System.out.println(byteBuffer.mark());

        // get(),会让position移动
        System.out.println(byteBuffer.get());
        System.out.println(byteBuffer.get());
        System.out.println(byteBuffer.get());

        // 将position的值重新设为mark,即0;
        System.out.println(byteBuffer.reset());

        // 此时可以再次从头读取
        System.out.println(byteBuffer.get());

    }

输出结果

text 复制代码
"C:\Program Files\Java\jdk-1.8\bin\java.exe" ...
java.nio.HeapByteBuffer[pos=0 lim=3 cap=10]
97
98
99
java.nio.HeapByteBuffer[pos=0 lim=3 cap=10]
97

Process finished with exit code 0

5. 将字符串转为buffer

5.1 将字符串转为字节数组,写入ByteBuffer

java 复制代码
public static void main(String[] args) {

        // 从内存中分配一个buffer
        ByteBuffer buffer = ByteBuffer.allocate(16);

        // 将字符串转为字节数组,写入buffer
        buffer.put("hello".getBytes(StandardCharsets.UTF_8));

        // 切换到读模式
        buffer.flip();

        System.out.println(buffer.get()); //104
        System.out.println(buffer.get()); //101
        System.out.println(buffer.get()); //108
        System.out.println(buffer.get()); //108
        System.out.println(buffer.get()); //111

    }

5.2 使用Charset的encode方法,将字符串包装成ByteBuffer

注意:在从buffer中读取数据之前,必须调用flip方法切换到读模式,

通过StandardCharsets.UTF_8.encode方法,返回ByteBuffer对象之前,已经调用过flip方法了;但是为了保证已经切换到读模式,最好再读取数据之前,显式调用flip方法;

java 复制代码
public static void main(String[] args) {

        // 使用Charset的encode方法,将字符串包装成ByteBuffer
        // 返回ByteBuffer对象之前,已经调用过flip方法
        ByteBuffer buffer = StandardCharsets.UTF_8.encode("hello");

        // 显式调用flip方法,确保切换到读模式
        buffer.flip();

        System.out.println(buffer.get()); //104
        System.out.println(buffer.get()); //101
        System.out.println(buffer.get()); //108
        System.out.println(buffer.get()); //108
        System.out.println(buffer.get()); //111

    }

5.3 使用ByteBuffer的wrap方法,将字符串包装成ByteBuffer

java 复制代码
public static void main(String[] args) {

        // 使用ByteBuffer的wrap方法,将字符串包装成ByteBuffer
        ByteBuffer buffer = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));

        // 切换到读模式
        buffer.flip();

        System.out.println(buffer.get()); //104
        System.out.println(buffer.get()); //101
        System.out.println(buffer.get()); //108
        System.out.println(buffer.get()); //108
        System.out.println(buffer.get()); //111

    }

6. 分散读 & 集中写(了解)

6.1 分散读

读取文件中的内容,写入多个Buffer中。

文件内容:

text 复制代码
owntwothree
java 复制代码
public static void main(String[] args) {
        try (FileInputStream fileInputStream = new FileInputStream("test.txt")) {

            FileChannel channel = fileInputStream.getChannel();
            
            ByteBuffer buffer1 = ByteBuffer.allocate(3);
            ByteBuffer buffer2 = ByteBuffer.allocate(3);
            ByteBuffer buffer3 = ByteBuffer.allocate(5);
            
            long read = channel.read(new ByteBuffer[]{buffer1, buffer2, buffer3});
            System.out.println("read bytes: " + read); // 11
            // 第一个buffer写入【one】
            // 第二个buffer写入【two】
            // 第三个buffer写入【three】

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

6.2 集中写

将多个Buffer中的数据,写入一个文件

java 复制代码
public static void main(String[] args) {
        ByteBuffer buffer1 = StandardCharsets.UTF_8.encode("hello"); //5 bytes
        ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("world"); //5 bytes
        ByteBuffer buffer3 = StandardCharsets.UTF_8.encode("你好"); //6 bytes

        try (FileOutputStream fileOutputStream = new FileOutputStream("text.txt")) {
            FileChannel channel = fileOutputStream.getChannel();

            long write = channel.write(new ByteBuffer[]{buffer1, buffer2, buffer3});
            System.out.println("write bytes: " + write); //16
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
相关推荐
那个松鼠很眼熟w2 小时前
【NIO】3.1 ByteBuffer的原理
nio
那个松鼠很眼熟w2 小时前
【NIO】3.2 ByteBuffer的原理-AI参考
nio
那个松鼠很眼熟w3 小时前
【NIO】5. FileChannel的基本用法
nio
那个松鼠很眼熟w5 小时前
【NIO】2. ByteBuffer的基本使用
nio
今天的砖头有点烫手啊5 天前
Java IO/NIO/AIO 演进:从 BIO 到 Netty 的底层逻辑
java·jvm·nio
名字还没想好☜23 天前
Java NIO ByteBuffer 实战:flip/clear/compact 三个绕晕人的方法与 position/limit 心智模型
java·开发语言·后端·spring·nio
滕州市燕猫虎计算机科技工作室个体工商户23 天前
java.nio.ByteBuffer的几个常用方法
nio
CodeStats1 个月前
【Java文件IO】Java 文件 IO 体系深度拆解(中):NIO API 详解、Files 与 FileChannel 巅峰对决
java·io·文件·nio·零拷贝·mmap
evans在进步1 个月前
Tomcat NIO 工作原理详解:从 Acceptor、Poller 到容器处理链
java·tomcat·nio