buffer-channel.md 17.4 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
1
---
沉默王二's avatar
nio  
沉默王二 已提交
2
title: 详解Java NIO的Buffer缓冲区和Channel通道 - 沉默王二 - java进阶之路
沉默王二's avatar
NIO  
沉默王二 已提交
3
shortTitle: Buffer和Channel
沉默王二's avatar
沉默王二 已提交
4 5 6 7
category:
  - Java核心
tag:
  - Java NIO
沉默王二's avatar
nio  
沉默王二 已提交
8 9
description: Java NIO 中的 Buffer 和 Channel 是 NIO 系统的核心组件。Buffer 负责存储数据,提供了对数据的读写操作。Channel 代表了与 I/O 设备(如文件或套接字)之间的连接。它提供了从源设备到 Buffer 的数据读取能力和从 Buffer 到目标设备的数据写入能力。
author: 沉默王二
沉默王二's avatar
沉默王二 已提交
10 11 12
head:
  - - meta
    - name: keywords
沉默王二's avatar
nio  
沉默王二 已提交
13
      content: java,Channel,Buffer,nio
沉默王二's avatar
沉默王二 已提交
14 15
---

沉默王二's avatar
nio  
沉默王二 已提交
16
# 12.3 Buffer和Channel
沉默王二's avatar
沉默王二 已提交
17

沉默王二's avatar
nio  
沉默王二 已提交
18
首先我们再来回顾一下 [IO 和 NIO 的区别](https://tobebetterjavaer.com/nio/nio-better-io.html)
沉默王二's avatar
沉默王二 已提交
19

沉默王二's avatar
NIO  
沉默王二 已提交
20 21 22
- 可简单认为:**IO 是面向流的处理,NIO 是面向块(缓冲区)的处理**
- 面向流的 I/O 系统**一次一个字节地处理数据**
- 一个面向块(缓冲区)的 I/O 系统**以块的形式处理数据**
沉默王二's avatar
沉默王二 已提交
23

沉默王二's avatar
nio  
沉默王二 已提交
24
NIO 主要有**两个核心部分组成**
沉默王二's avatar
沉默王二 已提交
25

沉默王二's avatar
nio  
沉默王二 已提交
26 27
- **Buffer 缓冲区**
- **Channel 通道**
沉默王二's avatar
沉默王二 已提交
28

沉默王二's avatar
nio  
沉默王二 已提交
29
在 NIO 中,并不是以流的方式来处理数据的,而是以 buffer 缓冲区和 Channel 通道**配合使用**来处理数据的。
沉默王二's avatar
沉默王二 已提交
30 31 32

简单理解一下:

沉默王二's avatar
nio  
沉默王二 已提交
33
可以把 Channel 通道比作铁路,buffer 缓冲区比作成火车(运载着货物)
沉默王二's avatar
沉默王二 已提交
34

沉默王二's avatar
nio  
沉默王二 已提交
35
而我们的 NIO 就是**通过 Channel 通道运输着存储数据的 Buffer 缓冲区的来实现数据的处理**
沉默王二's avatar
沉默王二 已提交
36

沉默王二's avatar
NIO  
沉默王二 已提交
37
要时刻记住:Channel 不与数据打交道,它只负责运输数据。与数据打交道的是 Buffer 缓冲区
沉默王二's avatar
沉默王二 已提交
38

沉默王二's avatar
NIO  
沉默王二 已提交
39 40
- **Channel-->运输**
- **Buffer-->数据**
沉默王二's avatar
沉默王二 已提交
41

沉默王二's avatar
nio  
沉默王二 已提交
42
相对于传统 IO 而言,**流是单向的**。对于 NIO 而言,有了 Channel 通道这个概念,我们的**读写都是双向**的(铁路上的火车能从广州去北京、自然就能从北京返还到广州)!
沉默王二's avatar
沉默王二 已提交
43

沉默王二's avatar
nio  
沉默王二 已提交
44
### Buffer 缓冲区
沉默王二's avatar
沉默王二 已提交
45

沉默王二's avatar
NIO  
沉默王二 已提交
46
我们来看看 Buffer 缓冲区有什么值得我们注意的地方。
沉默王二's avatar
沉默王二 已提交
47

沉默王二's avatar
NIO  
沉默王二 已提交
48
Buffer 是缓冲区的抽象类:
沉默王二's avatar
沉默王二 已提交
49

沉默王二's avatar
NIO  
沉默王二 已提交
50
![](https://cdn.tobebetterjavaer.com/stutymore/rumen-20230404151539.png)
沉默王二's avatar
沉默王二 已提交
51

沉默王二's avatar
nio  
沉默王二 已提交
52
其中 ByteBuffer 是**用得最多的实现类**(在通道中读写字节数据)。
沉默王二's avatar
沉默王二 已提交
53

沉默王二's avatar
NIO  
沉默王二 已提交
54
![](https://cdn.tobebetterjavaer.com/stutymore/rumen-20230404152253.png)
沉默王二's avatar
沉默王二 已提交
55

沉默王二's avatar
NIO  
沉默王二 已提交
56
拿到一个缓冲区我们往往会做什么?很简单,就是**读取缓冲区的数据/写数据到缓冲区中**。所以,缓冲区的核心方法就是 put 和 get:
沉默王二's avatar
沉默王二 已提交
57

沉默王二's avatar
NIO  
沉默王二 已提交
58
![](https://cdn.tobebetterjavaer.com/stutymore/rumen-20230404152445.png)
沉默王二's avatar
沉默王二 已提交
59

沉默王二's avatar
NIO  
沉默王二 已提交
60
Buffer 类维护了 4 个核心变量来提供**关于其所包含的数组信息**。它们是:
沉默王二's avatar
沉默王二 已提交
61

沉默王二's avatar
NIO  
沉默王二 已提交
62 63 64 65
- 容量 Capacity **缓冲区能够容纳的数据元素的最大数量**。容量在缓冲区创建时被设定,并且永远不能被改变。(不能被改变的原因也很简单,底层是数组嘛)
- 上界 Limit **缓冲区里的数据的总数**,代表了当前缓冲区中一共有多少数据。
- 位置 Position **下一个要被读或写的元素的位置**。Position 会自动由相应的 `get()``put()`函数更新。
- 标记 Mark 一个备忘位置。**用于记录上一次读写的位置**
沉默王二's avatar
沉默王二 已提交
66 67 68 69

首先展示一下**是如何创建缓冲区的,核心变量的值是怎么变化的**

```java
沉默王二's avatar
NIO  
沉默王二 已提交
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
// 创建一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

// 看一下初始时4个核心变量的值
System.out.println("初始时-->limit--->"+byteBuffer.limit());
System.out.println("初始时-->position--->"+byteBuffer.position());
System.out.println("初始时-->capacity--->"+byteBuffer.capacity());
System.out.println("初始时-->mark--->" + byteBuffer.mark());

System.out.println("--------------------------------------");

// 添加一些数据到缓冲区中
String s = "沉默王二";
byteBuffer.put(s.getBytes());

// 看一下初始时4个核心变量的值
System.out.println("put完之后-->limit--->"+byteBuffer.limit());
System.out.println("put完之后-->position--->"+byteBuffer.position());
System.out.println("put完之后-->capacity--->"+byteBuffer.capacity());
System.out.println("put完之后-->mark--->" + byteBuffer.mark());
沉默王二's avatar
沉默王二 已提交
90 91 92 93
```

运行结果:

沉默王二's avatar
NIO  
沉默王二 已提交
94 95 96 97 98 99 100 101 102 103 104
```
初始时-->limit--->1024
初始时-->position--->0
初始时-->capacity--->1024
初始时-->mark--->java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024]
--------------------------------------
put完之后-->limit--->1024
put完之后-->position--->12
put完之后-->capacity--->1024
put完之后-->mark--->java.nio.HeapByteBuffer[pos=12 lim=1024 cap=1024]
```
沉默王二's avatar
沉默王二 已提交
105

沉默王二's avatar
NIO  
沉默王二 已提交
106
现在**我想要从缓存区拿数据**,怎么拿呀??NIO 给了我们一个`flip()`方法。这个方法可以**改动 position 和 limit 的位置**
沉默王二's avatar
沉默王二 已提交
107

沉默王二's avatar
NIO  
沉默王二 已提交
108
在之前代码的基础上,我们`flip()`一下。
沉默王二's avatar
沉默王二 已提交
109

沉默王二's avatar
NIO  
沉默王二 已提交
110 111 112 113 114 115 116 117
```java
// flip()方法
byteBuffer.flip();
System.out.println("flip()方法之后-->limit--->"+byteBuffer.limit());
System.out.println("flip()方法之后-->position--->"+byteBuffer.position());
System.out.println("flip()方法之后-->capacity--->"+byteBuffer.capacity());
System.out.println("flip()方法之后-->mark--->" + byteBuffer.mark());
```
沉默王二's avatar
沉默王二 已提交
118

沉默王二's avatar
NIO  
沉默王二 已提交
119
再看看 4 个核心属性的值会发生什么变化:
沉默王二's avatar
沉默王二 已提交
120

沉默王二's avatar
NIO  
沉默王二 已提交
121
![](https://cdn.tobebetterjavaer.com/stutymore/rumen-20230404153844.png)
沉默王二's avatar
沉默王二 已提交
122

沉默王二's avatar
NIO  
沉默王二 已提交
123
在调用 `flip()` 之后,limit 变为当前 position 的值(12),position 重置为 0。这意味着你可以从缓冲区的开始位置读取刚刚写入的数据,直到 limit 指定的位置。capacity 保持不变(1024)。
沉默王二's avatar
沉默王二 已提交
124

沉默王二's avatar
NIO  
沉默王二 已提交
125
假设我们有一个初始容量为 1024 的 ByteBuffer。
沉默王二's avatar
沉默王二 已提交
126

沉默王二's avatar
NIO  
沉默王二 已提交
127
**初始状态**:
沉默王二's avatar
沉默王二 已提交
128

沉默王二's avatar
NIO  
沉默王二 已提交
129 130 131 132 133
```
position = 0
limit = 1024
capacity = 1024
```
沉默王二's avatar
沉默王二 已提交
134

沉默王二's avatar
NIO  
沉默王二 已提交
135
**添加数据 "沉默王二" 后**:
沉默王二's avatar
沉默王二 已提交
136

沉默王二's avatar
NIO  
沉默王二 已提交
137
由于 "沉默王二" 为 UTF-8 编码,一个汉字占 3 个字节,共有 4 个汉字,所以占用 12 个字节。
沉默王二's avatar
沉默王二 已提交
138

沉默王二's avatar
NIO  
沉默王二 已提交
139 140 141 142 143
```
position = 12
limit = 1024
capacity = 1024
```
沉默王二's avatar
沉默王二 已提交
144

沉默王二's avatar
NIO  
沉默王二 已提交
145
**调用 `flip()` 方法后**:
沉默王二's avatar
沉默王二 已提交
146

沉默王二's avatar
NIO  
沉默王二 已提交
147 148 149 150 151
```
position = 0
limit = 12
capacity = 1024
```
沉默王二's avatar
沉默王二 已提交
152

沉默王二's avatar
NIO  
沉默王二 已提交
153
用一幅图来表示就是。
沉默王二's avatar
沉默王二 已提交
154

沉默王二's avatar
NIO  
沉默王二 已提交
155
![](https://cdn.tobebetterjavaer.com/stutymore/rumen-20230404155658.png)
沉默王二's avatar
沉默王二 已提交
156

沉默王二's avatar
NIO  
沉默王二 已提交
157
当切换成读模式之后,我们就可以读取缓冲区的数据了:
沉默王二's avatar
沉默王二 已提交
158 159

```java
沉默王二's avatar
NIO  
沉默王二 已提交
160
// 创建一个limit()大小的字节数组(因为就只有limit这么多个数据可读)
沉默王二's avatar
沉默王二 已提交
161
byte[] bytes = new byte[byteBuffer.limit()];
沉默王二's avatar
NIO  
沉默王二 已提交
162
// 将读取的数据装进我们的字节数组中
沉默王二's avatar
沉默王二 已提交
163
byteBuffer.get(bytes);
沉默王二's avatar
NIO  
沉默王二 已提交
164
// 输出数据
沉默王二's avatar
沉默王二 已提交
165 166 167
System.out.println(new String(bytes, 0, bytes.length));
```

沉默王二's avatar
NIO  
沉默王二 已提交
168
输出后的结果:
沉默王二's avatar
沉默王二 已提交
169

沉默王二's avatar
NIO  
沉默王二 已提交
170 171 172
```
沉默王二
```
沉默王二's avatar
沉默王二 已提交
173 174 175

随后输出一下核心变量的值看看:

沉默王二's avatar
NIO  
沉默王二 已提交
176
![](https://cdn.tobebetterjavaer.com/stutymore/rumen-20230404160130.png)
沉默王二's avatar
沉默王二 已提交
177

沉默王二's avatar
NIO  
沉默王二 已提交
178
**读完如何还想写数据到缓冲区**,那就使用`clear()` 方法,这个方法会“清空”缓冲区,数据没有真正被清空,只是被**遗忘**掉了
沉默王二's avatar
沉默王二 已提交
179

沉默王二's avatar
NIO  
沉默王二 已提交
180
![](https://cdn.tobebetterjavaer.com/stutymore/rumen-20230404160412.png)
沉默王二's avatar
沉默王二 已提交
181

沉默王二's avatar
nio  
沉默王二 已提交
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
### Channel 通道

Channel 通道**只负责传输数据、不直接操作数据**。操作数据都是通过 Buffer 缓冲区来进行操作!通常,通道可以分为两大类:文件通道和套接字通道。

FileChannel:用于文件 I/O 的通道,支持文件的读、写和追加操作。FileChannel 允许在文件的任意位置进行数据传输,支持文件锁定以及内存映射文件等高级功能。FileChannel 无法设置为非阻塞模式,因此它只适用于阻塞式文件操作。

SocketChannel:用于 TCP 套接字 I/O 的通道。SocketChannel 支持非阻塞模式,可以与 Selector(下文会讲)一起使用,实现高效的网络通信。SocketChannel 允许连接到远程主机,进行数据传输。

与之匹配的有ServerSocketChannel:用于监听 TCP 套接字连接的通道。与 SocketChannel 类似,ServerSocketChannel 也支持非阻塞模式,并可以与 Selector 一起使用。ServerSocketChannel 负责监听新的连接请求,接收到连接请求后,可以创建一个新的 SocketChannel 以处理数据传输。

DatagramChannel:用于 UDP 套接字 I/O 的通道。DatagramChannel 支持非阻塞模式,可以发送和接收数据报包,适用于无连接的、不可靠的网络通信。

这篇我们主要来讲 FileChannel,SocketChannel、ServerSocketChannel 和 DatagramChannel 会放到[后面的章节中](https://tobebetterjavaer.com/nio/network-connect.html)讲解。

#### 文件通道 FileChannel
沉默王二's avatar
沉默王二 已提交
197

沉默王二's avatar
nio  
沉默王二 已提交
198
可以通过下面的方式打开一个通道。
沉默王二's avatar
沉默王二 已提交
199 200

```java
沉默王二's avatar
NIO  
沉默王二 已提交
201
FileChannel.open(Paths.get("docs/配套教程.md"), StandardOpenOption.WRITE);
沉默王二's avatar
沉默王二 已提交
202 203
```

沉默王二's avatar
NIO  
沉默王二 已提交
204
这里我们用到了 [Paths](https://tobebetterjavaer.com/nio/paths-files.html),这个后面也会讲到。
沉默王二's avatar
沉默王二 已提交
205

沉默王二's avatar
nio  
沉默王二 已提交
206
①、使用**FileChannel 配合 ByteBuffer 缓冲区**实现文件复制的功能:
沉默王二's avatar
沉默王二 已提交
207

沉默王二's avatar
NIO  
沉默王二 已提交
208 209 210
```java
try (FileChannel sourceChannel = FileChannel.open(Paths.get("logs/javabetter/itwanger.txt"), StandardOpenOption.READ);
    FileChannel destinationChannel = FileChannel.open(Paths.get("logs/javabetter/itwanger1.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
沉默王二's avatar
沉默王二 已提交
211

沉默王二's avatar
NIO  
沉默王二 已提交
212
  ByteBuffer buffer = ByteBuffer.allocate(1024);
沉默王二's avatar
沉默王二 已提交
213

沉默王二's avatar
NIO  
沉默王二 已提交
214 215 216 217 218 219 220
  while (sourceChannel.read(buffer) != -1) {
      buffer.flip();
      destinationChannel.write(buffer);
      buffer.clear();
  }
}
```
沉默王二's avatar
沉默王二 已提交
221

沉默王二's avatar
nio  
沉默王二 已提交
222
我们创建一个容量为 1024 的 ByteBuffer 作为缓冲区。在循环中,我们从源文件的 FileChannel 读取数据到缓冲区。当 `read()` 方法返回 -1 时,表示已经到达文件末尾。
沉默王二's avatar
沉默王二 已提交
223

沉默王二's avatar
nio  
沉默王二 已提交
224 225 226
读取数据后,我们调用 `flip()` 方法,以便在缓冲区中准备好要写入的数据。然后,我们将缓冲区的内容写入目标文件的 FileChannel(`write()` 方法)。在写入完成后,我们调用 `clear()` 方法重置缓冲区,以便在下一次迭代中重用它。

②、使用**内存映射文件(MappedByteBuffer)**的方式实现**文件复制**的功能(直接操作缓冲区):
沉默王二's avatar
沉默王二 已提交
227

沉默王二's avatar
NIO  
沉默王二 已提交
228 229 230
```java
try (FileChannel sourceChannel = FileChannel.open(Paths.get("logs/javabetter/itwanger.txt"), StandardOpenOption.READ);
      FileChannel destinationChannel = FileChannel.open(Paths.get("logs/javabetter/itwanger2.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.READ)) {
沉默王二's avatar
沉默王二 已提交
231

沉默王二's avatar
NIO  
沉默王二 已提交
232 233 234
    long fileSize = sourceChannel.size();
    MappedByteBuffer sourceMappedBuffer = sourceChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
    MappedByteBuffer destinationMappedBuffer = destinationChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
沉默王二's avatar
沉默王二 已提交
235

沉默王二's avatar
NIO  
沉默王二 已提交
236 237 238 239 240 241
    for (int i = 0; i < fileSize; i++) {
        byte b = sourceMappedBuffer.get(i);
        destinationMappedBuffer.put(i, b);
    }
}
```
沉默王二's avatar
沉默王二 已提交
242

沉默王二's avatar
nio  
沉默王二 已提交
243 244 245 246 247 248 249
MappedByteBuffer 是 Java NIO 中的一个类,它继承自 `java.nio.ByteBuffer`。MappedByteBuffer 用于表示一个内存映射文件,即将文件的一部分或全部映射到内存中,以便通过直接操作内存来实现对文件的读写。这种方式可以提高文件 I/O 的性能,因为操作系统可以直接在内存和磁盘之间传输数据,无需通过 Java 应用程序进行额外的数据拷贝。

通常与 FileChannel 一起使用,可以通过调用 FileChannel 的 `map()` 方法创建 MappedByteBuffer 对象。`map()` 方法接受三个参数:映射模式(FileChannel.MapMode)映射起始位置和映射的长度。

映射模式包括只读模式(READ_ONLY)、读写模式(READ_WRITE)和专用模式(PRIVATE)。

我们设置源文件的 MappedByteBuffer 为只读模式(READ_ONLY),目标文件的 MappedByteBuffer 为读写模式(READ_WRITE)。
沉默王二's avatar
沉默王二 已提交
250

沉默王二's avatar
nio  
沉默王二 已提交
251 252 253 254 255
在循环中,我们逐字节地从源文件的 MappedByteBuffer 读取数据并将其写入目标文件的 MappedByteBuffer。这样就实现了文件复制功能。利用内存映射文件(MappedByteBuffer)实现的文件复制,可能会比使用 ByteBuffer 的方法更快。

需要注意的是,使用 MappedByteBuffer 进行文件操作时,数据的修改可能不会立即写入磁盘。可以通过调用 MappedByteBuffer 的 `force()` 方法将数据立即写回磁盘。

③、通道之间通过`transfer()`实现数据的传输(直接操作缓冲区):
沉默王二's avatar
沉默王二 已提交
256

沉默王二's avatar
NIO  
沉默王二 已提交
257 258 259 260 261 262 263 264
```java
try (FileChannel sourceChannel = FileChannel.open(Paths.get("logs/javabetter/itwanger.txt"), StandardOpenOption.READ);
      FileChannel destinationChannel = FileChannel.open(Paths.get("logs/javabetter/itwanger3.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.READ)) {
    sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);
} catch (IOException e) {
    throw new RuntimeException(e);
}
```
沉默王二's avatar
沉默王二 已提交
265

沉默王二's avatar
nio  
沉默王二 已提交
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
FileChannel 的 `transferTo()` 方法是一个高效的文件传输方法,它允许将文件的一部分或全部内容直接从源文件通道传输到目标通道(通常是另一个文件通道或网络通道)。这种传输方式可以避免将文件数据在用户空间和内核空间之间进行多次拷贝,提高了文件传输的性能。

`transferTo()` 方法接受以下三个参数:

- position:源文件中开始传输的位置。
- count:要传输的字节数。
- target:接收数据的目标通道。

需要注意的是,`transferTo()` 方法可能无法一次传输所有请求的字节。在实际应用中,你可能需要使用循环来确保所有字节都被传输。

```java
public class FileChannelTransferToLoopExampleWithPaths {
    public static void main(String[] args) {
        Path sourcePath = Paths.get("logs/itwanger/paicoding.txt");
        Path destinationPath = Paths.get("logs/itwanger/paicoding_copy.txt");

        // 使用 try-with-resources 语句确保通道资源被正确关闭
        try (FileChannel sourceChannel = FileChannel.open(sourcePath, StandardOpenOption.READ);
             FileChannel destinationChannel = FileChannel.open(destinationPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {

            long position = 0;
            long count = sourceChannel.size();

            // 循环传输,直到所有字节都被传输
            while (position < count) {
                long transferred = sourceChannel.transferTo(position, count - position, destinationChannel);
                position += transferred;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

此外,`transferTo()` 方法在底层使用了操作系统提供的零拷贝功能(如 Linux 的 `sendfile()` 系统调用),可以大幅提高文件传输性能。但是,不同操作系统和 JVM 实现可能会影响零拷贝的可用性和性能,因此实际性能可能因环境而异。

零拷贝(Zero-Copy)是一种优化数据传输性能的技术,它最大限度地减少了在数据传输过程中的 CPU 和内存开销。在传统的数据传输过程中,数据通常需要在用户空间和内核空间之间进行多次拷贝,这会导致额外的 CPU 和内存开销。零拷贝技术通过避免这些多余的拷贝操作,实现了更高效的数据传输。

在 Java 中,零拷贝技术主要应用于文件和网络 I/O。FileChannel 类的 `transferTo()``transferFrom()` 方法就利用了零拷贝技术,可以在文件和网络通道之间高效地传输数据。详细参考:[深入剖析Linux IO原理和几种零拷贝机制的实现](https://zhuanlan.zhihu.com/p/83398714)

#### 直接与非直接缓冲区
沉默王二's avatar
沉默王二 已提交
309

沉默王二's avatar
NIO  
沉默王二 已提交
310
直接缓冲区和非直接缓冲区的差别主要在于它们在内存中的存储方式。这里给出了直接缓冲区和非直接缓冲区的简要概述和区别:
沉默王二's avatar
沉默王二 已提交
311

沉默王二's avatar
NIO  
沉默王二 已提交
312
非直接缓冲区:
沉默王二's avatar
沉默王二 已提交
313

沉默王二's avatar
NIO  
沉默王二 已提交
314 315 316 317
- 分配在 JVM 堆内存中
- 受到垃圾回收的管理
- 在读写操作时,需要将数据从堆内存复制到操作系统的本地内存,再进行 I/O 操作
- 创建: `ByteBuffer.allocate(int capacity)`
沉默王二's avatar
沉默王二 已提交
318

沉默王二's avatar
NIO  
沉默王二 已提交
319
直接缓冲区:
沉默王二's avatar
沉默王二 已提交
320

沉默王二's avatar
NIO  
沉默王二 已提交
321 322 323 324
- 分配在操作系统的本地内存中
- 不受垃圾回收的管理
- 在读写操作时,直接在本地内存中进行,避免了数据复制,提高了性能
- 创建: `ByteBuffer.allocateDirect(int capacity)`
沉默王二's avatar
nio  
沉默王二 已提交
325
- 还有前面提到的 `FileChannel.map()` 方法,会返回一个类型为 MappedByteBuffer 的直接缓冲区。
沉默王二's avatar
沉默王二 已提交
326

沉默王二's avatar
nio  
沉默王二 已提交
327
ByteBuffer.allocate和ByteBuffer.allocateDirect直接的差异。
沉默王二's avatar
沉默王二 已提交
328

沉默王二's avatar
nio  
沉默王二 已提交
329
![](https://cdn.tobebetterjavaer.com/stutymore/buffer-channel-20230406183808.png)
沉默王二's avatar
沉默王二 已提交
330

沉默王二's avatar
nio  
沉默王二 已提交
331
直接缓冲区和非直接缓冲区之间的差异。
沉默王二's avatar
沉默王二 已提交
332

沉默王二's avatar
nio  
沉默王二 已提交
333
![](https://cdn.tobebetterjavaer.com/stutymore/buffer-channel-20230406182921.png)
沉默王二's avatar
沉默王二 已提交
334

沉默王二's avatar
nio  
沉默王二 已提交
335
非直接缓冲区存储在JVM内部,数据需要从应用程序(Java)复制到非直接缓冲区,再复制到内核缓冲区,最后发送到设备(磁盘/网络)。而对于直接缓冲区,数据可以直接从应用程序(Java)复制到内核缓冲区,无需经过JVM的非直接缓冲区。
沉默王二's avatar
沉默王二 已提交
336

沉默王二's avatar
NIO  
沉默王二 已提交
337
### 小结
沉默王二's avatar
沉默王二 已提交
338

沉默王二's avatar
NIO  
沉默王二 已提交
339
Java NIO 中的 Buffer 和 Channel 是 NIO 系统的核心组件。Buffer 负责存储数据,提供了对数据的读写操作。它有多种类型,如 ByteBuffer、CharBuffer、IntBuffer 等,以支持不同的数据类型。Channel 代表了与 I/O 设备(如文件或套接字)之间的连接。它提供了从源设备到 Buffer 的数据读取能力和从 Buffer 到目标设备的数据写入能力。Channel 可以是可读、可写或同时可读写的。NIO 使用这两个组件进行高效的数据传输,以提高 I/O 操作的性能。
沉默王二's avatar
沉默王二 已提交
340

沉默王二's avatar
NIO  
沉默王二 已提交
341
> 参考链接:[https://www.zhihu.com/question/29005375/answer/667616386](https://www.zhihu.com/question/29005375/answer/667616386),整理:沉默王二
沉默王二's avatar
沉默王二 已提交
342

沉默王二's avatar
NIO  
沉默王二 已提交
343
---
沉默王二's avatar
沉默王二 已提交
344

沉默王二's avatar
NIO  
沉默王二 已提交
345
最近整理了一份牛逼的学习资料,包括但不限于 Java 基础部分(JVM、Java 集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类 Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是 2022 年全网最全的学习和找工作的 PDF 资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
沉默王二's avatar
沉默王二 已提交
346

沉默王二's avatar
沉默王二 已提交
347
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
沉默王二's avatar
沉默王二 已提交
348

沉默王二's avatar
沉默王二 已提交
349
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)