Java-NIO 之 Buffer 与 Channel

NIO:一种同步非阻塞的 I/O 模型,也是 I/O 多路复用的基础。

同步与异步

  • 同步:发起一个调用后,被调用者未处理完请求之前,调用不返回。
  • 异步:发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。

同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。

阻塞和非阻塞

  • 阻塞:发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
  • 非阻塞:发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。

举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在那里傻等着水开(同步阻塞)。

等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(同步非阻塞)。

后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(异步非阻塞)。

一、Buffer(缓冲区)

在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据。

/*
 * 根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:
 * ByteBuffer
 * CharBuffer
 * ShortBuffer
 * IntBuffer
 * LongBuffer
 * FloatBuffer
 * DoubleBuffer
 *
 * 上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区
 */

1.基本属性

  • 容量(capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
  • 界限(limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
  • 位置(position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于 limit。
  • 标记(mark)与重置(reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这个 position。
  • 标记、位置、限制、容量遵守以下不变式:0 <= mark <= position <= limit <= capacity

2.常用方法

Buffer 所有子类提供了两个用于数据操作的方法:get() 与 put() 
获取 Buffer 中的数据

  • get() :读取单个字节
  • get(byte[] dst):批量读取多个字节到 dst 中
  • get(int index):读取指定索引位置的字节(不会移动 position)

放入数据到 Buffer 中

  • put(byte b):将给定单个字节写入缓冲区的当前位置
  • put(byte[] src):将 src 中的字节写入缓冲区的当前位置
  • put(int index, byte b):将指定字节写入缓冲区的索引位置(不会移动 position)

其它方法

  • Buffer clear():清空缓冲区并返回对缓冲区的引用,不会真正的删除掉 buffer 中的数据,只是把 position 移动到 0,同时把 limit 调整为 capacity,marks 置为 -1。
  • Buffer flip():将缓冲区的 limit 设置为 position,并将 position 置为 0,marks 置为 -1。

3.直接缓冲区与非直接缓冲区

// 分配缓冲区:JVM 内存中
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
// 分配直接缓冲区:本地内存中
ByteBuffer bufDirect = ByteBuffer.allocateDirect(1024);
// 是否为直接缓冲区
System.out.println(buf.isDirect());

// 直接字节缓冲区还可以通过 FileChannel 的 map() 方法将文件区域直接映射到内存中来创建。该方法返回 MappedByteBuffer。

非直接缓冲区

直接缓冲区

4.简单使用

import org.junit.Test;

import java.nio.ByteBuffer;

public class TestBuffer {
    @Test
    public void markAndReset() {
        String str = "abcde14693090";
        // 分配直接缓冲区
        ByteBuffer buf = ByteBuffer.allocateDirect(1024);

        // 存入数据
        buf.put(str.getBytes());
        // 切换到读取模式
        buf.flip();

        byte[] dst = new byte[buf.limit()];
        buf.get(dst, 0, 2);
        System.out.println(new String(dst, 0, 2));
        System.out.println(buf.position());

        // mark() : 标记
        buf.mark();

        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());
        }
    }

    @Test
    public void getAndPut() {
        String str = "abcde";

        //1. 分配一个指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);

        System.out.println("allocate():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //2. 利用 put() 存入数据到缓冲区中
        buf.put(str.getBytes());

        System.out.println("put():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //3. 切换读取数据模式
        buf.flip();

        System.out.println("flip():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //4. 利用 get() 读取缓冲区中的数据
        byte[] dst = new byte[buf.limit()];
        buf.get(dst);
        System.out.println(new String(dst, 0, dst.length));

        System.out.println("get():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //5. rewind() : 可重复读
        buf.rewind();

        System.out.println("rewind():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        //6. clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,只是处于“被遗忘”状态
        buf.clear();

        System.out.println("clear():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity());

        // 获取单个字符
        System.out.println((char) buf.get());
    }
}

二、通道(Channel)

表示 IO 源与目标节点打开的连接,在 Java NIO 中负责缓冲区中数据的传输,类似于传统的“流”。只不过 Channel 本身不能直接访问数据,只能与 Buffer 进行交互。

/*
 * java.nio.channels.Channel 接口的主要实现类:
 *         |--FileChannel:用于读取、写入、映射和操作文件的通道。
 *         |--SocketChannel:通过 TCP 读写网络中的数据。
 *         |--DatagramChannel:通过 UDP 读写网络中的数据通道。
 *         |--ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
 */

1.获取通道

/*
 * 获取通道的一种方式是对支持通道的对象调用 getChannel() 方法。
 * 支持通道的类如下:
 * 本地 IO:
 * |--FileInputStream
 * |--FileOutputStream
 * |--RandomAccessFile
 * 网络 IO:
 * |--Socket
 * |--ServerSocket
 * |--DatagramSocket
 *
 * 在 JDK 1.7 中, NIO.2 针对各个通道的实现类提供了静态方法 open() 来获取通道。
 * 在 JDK 1.7 中, NIO.2 的 Files 工具类的静态方法 newByteChannel() 也可以获取通道。
 */

2.通道数据传输

/*
 * 将 Buffer 中数据写入 Channel
 * int bytesWritten = inChannel,write(buf)
 * 从 Channel 读取数据到 Buffer
 * int bytesRead = inChannel.read(buf)
 *
 * Channel 之间的数据传输(将数据从源通道传输到其他 Channel 中)
 * transferFrom()
 * transferTo()
 */

复制文件的几种方式

// 通道之间的数据传输(直接缓冲区)
@Test
public void channelCopy() throws IOException {
    FileChannel inChannel = FileChannel.open(Paths.get("D:/123.txt"), StandardOpenOption.READ);
    FileChannel outChannel = FileChannel.open(Paths.get("D:/456.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

    // inChannel.transferTo(0, inChannel.size(), outChannel);
    outChannel.transferFrom(inChannel, 0, inChannel.size());

    inChannel.close();
    outChannel.close();
}

// 使用直接缓冲区完成文件的复制(内存映射文件)
@Test
public void byteBuffCopy() {
    long start = System.currentTimeMillis();

    try (FileChannel inChannel = FileChannel.open(Paths.get("D:/123.txt"), StandardOpenOption.READ);
         FileChannel outChannel = FileChannel.open(Paths.get("D:/456.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE)) {

        // 内存映射文件
        MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());

        // 直接对缓冲区进行数据的读写操作
        byte[] dst = new byte[inMappedBuf.limit()];
        inMappedBuf.get(dst);
        outMappedBuf.put(dst);
    } catch (IOException e) {
        e.printStackTrace();
    }

    long end = System.currentTimeMillis();
    System.out.println("耗费时间为:" + (end - start));
}

// 利用通道完成文件的复制(非直接缓冲区)
@Test
public void fileCopy() {
    long start = System.currentTimeMillis();

    try (FileInputStream fis = new FileInputStream("D:/123.txt");
         FileOutputStream fos = new FileOutputStream("D:/456.txt");
         // 获取通道
         FileChannel inChannel = fis.getChannel();
         FileChannel outChannel = fos.getChannel()) {

        // 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);

        // 将通道中的数据存入缓冲区中
        while (inChannel.read(buf) != -1) {
            // 切换读取数据的模式
            buf.flip();
            // 将缓冲区中的数据写入通道中
            outChannel.write(buf);
            // 清空缓冲区
            buf.clear();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    long end = System.currentTimeMillis();
    System.out.println("Time:" + (end - start));
}

3.分散(Scatter)与聚集(Gather)

分散读取(Scattering Reads):从 Channel 中读取的数据“分散”到多个 Buffer 中。(按照缓冲区的顺序,从 Channel 中读取的数据依次将 Buffer 填满。)

聚集写入(Gathering Writes):将多个 Buffer 中的数据“聚集”到 Channel。(按照缓冲区的顺序,写入 position 和 limit 之间的数据到 Channel 。)

使用分散和聚集来复制文件部分内容

// 分散和聚集
@Test
public void scatterAndGather() throws IOException {
    RandomAccessFile raf1 = new RandomAccessFile("D:/123.txt", "rw");

    // 获取通道
    FileChannel channel1 = raf1.getChannel();
    // 分配指定大小的缓冲区
    ByteBuffer buf1 = ByteBuffer.allocate(100);
    ByteBuffer buf2 = ByteBuffer.allocate(1024);
    // 分散读取
    ByteBuffer[] bufs = {buf1, buf2};
    channel1.read(bufs);

    // 转换模式
    for (ByteBuffer byteBuffer : bufs) {
        byteBuffer.flip();
    }

    System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
    System.out.println("-----------------");
    System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));

    // 聚集写入
    RandomAccessFile raf2 = new RandomAccessFile("D:/456.txt", "rw");
    FileChannel channel2 = raf2.getChannel();
    channel2.write(bufs);
}

4.字符集

/*
 * 字符集:Charset
 * 编码:字符串 -> 字节数组
 * 解码:字节数组  -> 字符串
 */
@Test
public void testCharset() throws IOException {
    // 指定字符集
    Charset cs1 = Charset.forName("GBK");

    CharBuffer cBuf = CharBuffer.allocate(1024);
    cBuf.put("字符集");

    cBuf.flip();
    //获取编码器
    CharsetEncoder ce = cs1.newEncoder();
    //编码
    ByteBuffer bBuf = ce.encode(cBuf);
    System.out.println(Arrays.toString(bBuf.array()));

    bBuf.flip();
    //获取解码器
    CharsetDecoder cd = cs1.newDecoder();
    //解码
    cBuf = cd.decode(bBuf);
    System.out.println(cBuf.toString());
}

// Java 支持的字符集
@Test
public void getCharset() {
    Map<String, Charset> map = Charset.availableCharsets();
    for (Entry<String, Charset> entry : map.entrySet()) {
        System.out.println(entry.getKey() + "=" + entry.getValue());
    }
}


https://snailclimb.gitee.io/javaguide/#/java/BIO-NIO-AIO

https://snailclimb.gitee.io/javaguide/#/java/Java%20IO%E4%B8%8ENIO

https://cyc2018.github.io/CS-Notes/#/notes/Java%20IO?id=%e4%b8%83%e3%80%81nio

https://www.cnblogs.com/dolphin0520/p/3919162.html

https://ifeve.com/java-nio-all/

原文地址:https://www.cnblogs.com/jhxxb/p/11272727.html

时间: 2024-10-24 06:30:18

Java-NIO 之 Buffer 与 Channel的相关文章

Java NIO:Buffer、Channel 和 Selector

Buffer 一个 Buffer 本质上是内存中的一块,我们可以将数据写入这块内存,之后从这块内存获取数据. java.nio 定义了以下几个 Buffer 的实现,这个图读者应该也在不少地方见过了吧. 其实核心是最后的 ByteBuffer,前面的一大串类只是包装了一下它而已,我们使用最多的通常也是 ByteBuffer. 我们应该将 Buffer 理解为一个数组,IntBuffer.CharBuffer.DoubleBuffer 等分别对应 int[].char[].double[] 等.

《Java源码分析》:Java NIO 之 Buffer

<Java源码分析>:Java NIO 之 Buffer 在上篇博文中,我们介绍了Java NIO 中Channel 和Buffer的基本使用方法,这篇博文将从源码的角度来看下Buffer的内部实现. 在Java API文档中,对Buffer的说明摘入如下: Buffer:是一个用于特定基本数据类型的容器.这里的特定基本数据类型指的是:除boolean类型的其他基本上数据类型. 缓冲区是特定基本数据类型元素的线性有限序列.除内容外,缓冲区饿基本属性还包括三个重要的属性,如下: 1.capaci

Java NIO中的通道Channel(二)分散/聚集 Scatter/Gather

什么是Scatter/Gather scatter/gather指的在多个缓冲区上实现一个简单的I/O操作,比如从通道中读取数据到多个缓冲区,或从多个缓冲区中写入数据到通道: scatter(分散):指的是从通道中读取数据分散到多个缓冲区Buffer的过程,该过程会将每个缓存区填满,直至通道中无数据或缓冲区没有空间: gather(聚集):指的是将多个缓冲区Buffer聚集起来写入到通道的过程,该过程类似于将多个缓冲区的内容连接起来写入通道: scatter/gather接口 如下是Scatte

Java NIO (3) Buffer

Java NIO Buffer Java NIO Buffers are used when interacting with NIO Channels. As you know, data is read from channels into buffers, and written from buffers into channels. A buffer is essentially a block of memory into which you can write data, which

【Java nio】buffer

1 package com.slp.nio; 2 3 import org.junit.Test; 4 5 import java.nio.ByteBuffer; 6 7 /** 8 * Created by sanglp on 2017/3/1. 9 * 一.缓冲区:在Java nio中负责数据的存储.缓冲区就是数据,用于村塾不同数据类型的数据 10 * 根据数据类型的不同(boolean类型除外),提供了相应类型的缓冲区 11 * ByteBuffer 12 * CharBuffer 13

Java NIO -- 缓冲区(Buffer)的数据存取

缓冲区(Buffer): 一个用于特定基本数据类型的容器.由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类.Java NIO 中的 Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的. Buffer 就像一个数组,可以保存多个相同类型的数据.根据数据类型不同(boolean 除外) ,有以下 Buffer 常用子类:ByteBufferCharBuffer ShortBuffer IntBuffer LongBuffer Flo

Java网络编程和NIO详解4:浅析NIO包中的Buffer、Channel 和 Selector

Java网络编程与NIO详解4:浅析NIO包中的Buffer.Channel 和 Selector 转自https://www.javadoop.com/post/nio-and-aio 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO https://blog.csdn.net/column/details/21963.html 部分代码会放在我的的Github:https://github.com/h2pl/ J

【JAVA】【NIO】4、Java NIO Buffer

Java NIO的Buffer用于和channel进行交互. buffer本质上是一个内存块,你可以写数据,然后读取出来. 这个内存块是通过NIO的Buffer对象进行包装的,该对象提供了一系列的方法,使得对内存块的访问更加容易了. 基本的Buffer使用 使用Buffer读写数据一般有如下4步: 1.将数据写入 Buffer 2.调用buffer.flip()方法 3.从Buffer中读出数据 4.调用buffer.clear()方法或buffer.compact()方法 当你将数据写入buf

5. 彤哥说netty系列之Java NIO核心组件之Channel

你好,我是彤哥,本篇是netty系列的第五篇. 简介 上一章我们一起学习了如何使用Java原生NIO实现群聊系统,这章我们一起来看看Java NIO的核心组件之一--Channel. 思维转变 首先,我想说的最重要的一个点是,学习NIO思维一定要从BIO那种一个连接一个线程的模式转变成多个连接(Channel)共用一个线程来处理的这种思维. 1个Connection = 1个Socket = 1个Channel,这几个概念可以看作是等价的,都表示一个连接,只不过是用在不同的场景中. 如果单从阻塞

6. 彤哥说netty系列之Java NIO核心组件之Buffer

--日拱一卒,不期而至! 你好,我是彤哥,本篇是netty系列的第六篇. 简介 上一章我们一起学习了Java NIO的核心组件Channel,它可以看作是实体与实体之间的连接,而且需要与Buffer交互,这一章我们就来学习一下Buffer的特性. 概念 Buffer用于与Channel交互时使用,通过上一章的学习我们知道,数据从Channel读取到Buffer,或者从Buffer写入Channel. Buffer本质上是一个内存块,可以向里面写入数据,或者从里面读取数据,在Java中它被包装成了