0%

java | 零拷贝

零拷贝的体现之一,对原始 ByteBuf 进行切片分成多个 ByteBuf ,切片后的 ByteBuf 并没有发生内存复制。

还是用原始的 ByteBuf 的内存,切片后的 ByteBuf 维护独立的 readwrite 指针。

slice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.redisc;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;


@Slf4j
public class Run {

public static void main(String[] args) throws IOException {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});
log(byteBuf);
// 在切片过程中,没有发生切片复制
ByteBuf byteBuf1 = byteBuf.slice(0, 5);
ByteBuf byteBuf2 = byteBuf.slice(5, 5);

log(byteBuf1);
log(byteBuf2);

byteBuf1.setByte(0, '1'); // 对 byteBuf1 修改,验证切片没有发生复制
log(byteBuf);
}

private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
read index:0 write index:10 capacity:10
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 66 61 62 63 64 |abcdefabcd |
+--------+-------------------------------------------------+----------------+
read index:0 write index:5 capacity:5
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 |abcde |
+--------+-------------------------------------------------+----------------+
read index:0 write index:5 capacity:5
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 66 61 62 63 64 |fabcd |
+--------+-------------------------------------------------+----------------+
read index:0 write index:10 capacity:10
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 62 63 64 65 66 61 62 63 64 |1bcdefabcd |
+--------+-------------------------------------------------+----------------+

slice 的引用计数器需要使用正确使用,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.redisc;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;


@Slf4j
public class Run {

public static void main(String[] args) throws IOException {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});
// 在切片过程中,没有发生切片复制
ByteBuf byteBuf1 = byteBuf.slice(0, 5);
ByteBuf byteBuf2 = byteBuf.slice(5, 5);

byteBuf.release();
log(byteBuf1);
}

private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}

输出报错

1
2
3
4
5
6
7
8
9
10
11
12
13
Exception in thread "main" io.netty.util.IllegalReferenceCountException: refCnt: 0
at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1454)
at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1383)
at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1379)
at io.netty.buffer.AbstractByteBuf.getByte(AbstractByteBuf.java:355)
at io.netty.buffer.AbstractUnpooledSlicedByteBuf.getByte(AbstractUnpooledSlicedByteBuf.java:120)
at io.netty.buffer.AbstractByteBuf.getUnsignedByte(AbstractByteBuf.java:368)
at io.netty.buffer.ByteBufUtil$HexUtil.appendPrettyHexDump(ByteBufUtil.java:1580)
at io.netty.buffer.ByteBufUtil$HexUtil.access$500(ByteBufUtil.java:1420)
at io.netty.buffer.ByteBufUtil.appendPrettyHexDump(ByteBufUtil.java:1416)
at io.netty.buffer.ByteBufUtil.appendPrettyHexDump(ByteBufUtil.java:1407)
at com.redisc.Run.log(Run.java:35)
at com.redisc.Run.main(Run.java:24)

这是因为 ByteBuf 的切片,并不是真的复制。当 byteBuf.release(); 之后,bytebuf 已经消失,所以,bytebuf1 也就没有了。想要继续使用需要用下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.redisc;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;


@Slf4j
public class Run {

public static void main(String[] args) throws IOException {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});
// 在切片过程中,没有发生切片复制
ByteBuf byteBuf1 = byteBuf.slice(0, 5);
byteBuf1.retain();
ByteBuf byteBuf2 = byteBuf.slice(5, 5);

byteBuf.release();
log(byteBuf1);
byteBuf1.release();
}

private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}

分片完,需要先执行 retain,使用完之后再执行 release

duplicate

零拷贝,相当于截取 ByteBuf 的所有内容,没有 max capacity 的限制。也是与原始的 ByteBuf 使用同一块底层内存,只不过读写指针独立。

copy

将底层内存数据进行深度拷贝,无论读写都与原始 ByteBuf 无关。

compositeBuffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.redisc;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;


@Slf4j
public class Run {

public static void main(String[] args) throws IOException {
ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf1.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});
ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf2.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});

// ByteBuf byteBuf3 = ByteBufAllocator.DEFAULT.buffer();
// byteBuf3.writeBytes(byteBuf1).writeBytes(byteBuf2); // 这会导致真正的复制

CompositeByteBuf byteBuf4 = ByteBufAllocator.DEFAULT.compositeBuffer();
byteBuf4.addComponents(true, byteBuf1, byteBuf2); // 零拷贝
log(byteBuf4);
}

private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}

输出

1
2
3
4
5
6
7
read index:0 write index:20 capacity:20
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 66 61 62 63 64 61 62 63 64 65 66 |abcdefabcdabcdef|
|00000010| 61 62 63 64 |abcd |
+--------+-------------------------------------------------+----------------+
请我喝杯咖啡吧~