NIO之Buffer解读(下)

简介: NIO之Buffer解读(下)

向 Buffer 中写数据

写数据到 Buffer 有两种方式:

(1)从 Channel 写到 Buffer。

(2)通过 Buffer 的 put()方法写到 Buffer 里。

从 Channel 写到 Buffer 的例子:

int bytesRead = inChannel.read(buf); //read into buffer.

通过 put 方法写 Buffer 的例子:

buf.put(127);

put 方法有很多版本,允许你以不同的方式把数据写入到 Buffer 中。例如, 写到一个 指定的位置,或者把一个字节数组写入到 Buffer

flip()方法

flip 方法将 Buffer 从写模式切换到读模式。调用 flip()方法会将 position 设回 0,并 将 limit 设置成之前 position 的值。换句话说,position 现在用于标记读的位置, limit 表示之前写进了多少个 byte、char 等 (现在能读取多少个 byte、char 等)。

从 Buffer 中读取数据

从 Buffer 中读取数据有两种方式:

(1)从 Buffer 读取数据到 Channel。

(2)使用 get()方法从 Buffer 中读取数据。

从 Buffer 读取数据到 Channel 的例子:

1. //read from buffer into channel. 
2. int bytesWritten = inChannel.write(buf);

使用 get()方法从 Buffer 中读取数据的例子

byte aByte = buf.get();

get 方法有很多版本,允许你以不同的方式从 Buffer 中读取数据。例如,从指定

position 读取,或者从 Buffer 中读取数据到字节数组。

Buffer 几个方法

rewind()方法

Buffer.rewind()将 position 设回 0,所以你可以重读 Buffer 中的所有数据。limit 保持不变,仍然表示能从 Buffer 中读取多少个元素(byte、char 等)。

clear()与 compact()方法

一旦读完 Buffer 中的数据,需要让 Buffer 准备好再次被写入。可以通过 clear()或 compact()方法来完成。

如果调用的是 clear()方法,position 将被设回 0,limit 被设置成 capacity 的值。换 句话说,Buffer 被清空了。Buffer 中的数据并未清除,只是这些标记告诉我们可以从 哪里开始往 Buffer 里写数据。

如果 Buffer 中有一些未读的数据,调用 clear()方法,数据将“被遗忘”,意味着不再 有任何标记会告诉你哪些数据被读过,哪些还没有。

如果 Buffer 中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据, 那么使用 compact()方法。

compact()方法将所有未读的数据拷贝到 Buffer 起始处。然后将 position 设到最后一 个未读元素正后面。limit 属性依然像 clear()方法一样,设置成 capacity。现在 Buffer 准备好写数据了,但是不会覆盖未读的数据。

mark()与 reset()方法

通过调用 Buffer.mark()方法,可以标记 Buffer 中的一个特定 position。之后可以通 过调用 Buffer.reset()方法恢复到这个 position。例如:

1. buffer.mark();
2. //call buffer.get() a couple of times, e.g. during parsing. 
3. buffer.reset(); //set position back to mark.

缓冲区操作

缓冲区分片

在 NIO 中,除了可以分配或者包装一个缓冲区对象外,还可以根据现有的缓冲区对象 来创建一个子缓冲区,即在现有缓冲区上切出一片来作为一个新的缓冲区,但现有的 缓冲区与创建的子缓冲区在底层数组层面上是数据共享的,也就是说,子缓冲区相当 于是现有缓冲区的一个视图窗口。调用 slice()方法可以创建一个子缓冲区。

1. @Test
2. public void testConect3() throws IOException {
3. ByteBuffer buffer = ByteBuffer.allocate(10);
4. // 缓冲区中的数据 0-9
5. for (int i = 0; i < buffer.capacity(); ++i) {
6.             buffer.put((byte) i);
7.         }
8. // 创建子缓冲区
9.         buffer.position(3);
10.         buffer.limit(7);
11. ByteBuffer slice = buffer.slice();
12. // 改变子缓冲区的内容
13. for (int i = 0; i < slice.capacity(); ++i) {
14. byte b = slice.get(i);
15.             b *= 10;
16.             slice.put(i, b);
17.         }
18.         buffer.position(0);
19.         buffer.limit(buffer.capacity());
20. while (buffer.remaining() > 0) {
21.             System.out.print(buffer.get()+" ");
22.         }
23.     }

只读缓冲区

只读缓冲区非常简单,可以读取它们,但是不能向它们写入数据。可以通过调用缓冲 区的 asReadOnlyBuffer()方法,将任何常规缓冲区转 换为只读缓冲区,这个方法返回 一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化:

1. @Test
2. public void testConect4() throws IOException {
3. ByteBuffer buffer = ByteBuffer.allocate(10);
4. // 缓冲区中的数据 0-9
5. for (int i = 0; i < buffer.capacity(); ++i) {
6.             buffer.put((byte) i);
7.         }
8. 
9. // 创建只读缓冲区
10. ByteBuffer readonly = buffer.asReadOnlyBuffer();
11. // 改变原缓冲区的内容
12. for (int i = 0; i < buffer.capacity(); ++i) {
13. 
14. byte b = buffer.get(i);
15.             b *= 10;
16.             buffer.put(i, b);
17.         }
18.         readonly.position(0);
19.         readonly.limit(buffer.capacity());
20. // 只读缓冲区的内容也随之改变
21. while (readonly.remaining() > 0) {
22.             System.out.println(readonly.get());
23.         }
24.     }

如果尝试修改只读缓冲区的内容,则会报 ReadOnlyBufferException 异常。只读缓冲 区对于保护数据很有用。在将缓冲区传递给某个 对象的方法时,无法知道这个方法是 否会修改缓冲区中的数据。创建一个只读的缓冲区可以保证该缓冲区不会被修改。只 可以把常规缓冲区转换为只读缓冲区,而不能将只读的缓冲区转换为可写的缓冲区。

直接缓冲区

直接缓冲区是为加快 I/O 速度,使用一种特殊方式为其分配内存的缓冲区,JDK 文档 中的描述为:给定一个直接字节缓冲区,Java 虚拟机将尽最大努力直接对它执行本机 I/O 操作。也就是说,它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后), 尝试避免将缓冲区的内容拷贝到一个中间缓冲区中 或者从一个中间缓冲区中拷贝数据。 要分配直接缓冲区,需要调用 allocateDirect()方法,而不是 allocate()方法,使用方 式与普通缓冲区并无区别。

拷贝文件示例:

1. @Test
2. public void testConect5() throws IOException {
3. String infile = "d:\\atguigu\\01.txt";
4. FileInputStream fin = new FileInputStream(infile);
5. FileChannel fcin = fin.getChannel();
6. String outfile = String.format("d:\\atguigu\\02.txt");
7. FileOutputStream fout = new FileOutputStream(outfile);
8. FileChannel fcout = fout.getChannel();
9. // 使用 allocateDirect,而不是 allocate
10. ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
11. while (true) {
12.             buffer.clear();
13. int r = fcin.read(buffer);
14. if (r == -1) {
15. break;
16.             }
17.             buffer.flip();
18.             fcout.write(buffer);
19.         }
20.     }

内存映射文件 I/O

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快的多。内存映射文件 I/O 是通过使文件中的数据出现为 内存数组的内容来 完成的,这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。 一般来说,只有文件中实际读取或者写入的部分才会映射到内存中。

1. static private final int start = 0;
2. static private final int size = 1024;
3. static public void main(String args[]) throws Exception {
4. RandomAccessFile raf = new RandomAccessFile("d:\\atguigu\\01.txt", "rw");
5. FileChannel fc = raf.getChannel();
6. MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 
7.     start, size);
8.     mbb.put(0, (byte) 97);
9.     mbb.put(1023, (byte) 122);raf.close();
10. }

ByteBuffer的大小分配

  • 每个 channel 都需要记录可能被切分的消息,因为 ByteBuffer 不能被多个 channel 共同使用,因此需要为每个 channel 维护一个独立的 ByteBuffer
  • ByteBuffer 不能太大,比如一个 ByteBuffer 1Mb 的话,要支持百万连接就要 1Tb 内存,因此需要设计大小可变的 ByteBuffer
  • 一种思路是首先分配一个较小的 buffer,例如 4k,如果发现数据不够,再分配 8k 的 buffer,将 4k buffer 内容拷贝至 8k buffer,优点是消息连续容易处理,缺点是数据拷贝耗费性能,参考实现image.png
  • 另一种思路是用多个数组组成 buffer,一个数组不够,把多出来的内容写入新的数组,与前面的区别是消息存储不连续解析复杂,优点是避免了拷贝引起的性能损耗



相关文章
|
5月前
|
存储 编解码 移动开发
技术笔记:NIO流—理解Buffer、Channel概念和NIO的读写操作
技术笔记:NIO流—理解Buffer、Channel概念和NIO的读写操作
38 1
|
1月前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
28 2
|
6月前
|
Java 索引
📌 Java NIO Buffer
Java NIO缓冲区在与NIO通道交互时使用。数据从通道读取到缓冲区,然后从缓冲区写入通道。 缓冲区本质上是一块内存,可以在其中写入数据,然后再进行读取。这个内存块被封装在一个NIOBuffer对象中,该对象提供了一组方法,可以更容易地使用内存块。
|
存储 网络协议 Java
Netty入门到超神系列-Java NIO 三大核心(selector,channel,buffer)
选择器,也叫多路复用器,Java的NIO通过selector实现一个线程处理多个客户端链接,多个channel可以注册到同一个Selector,Selector能够监测到channel上是否有读/写事件发生,从而获取事件和对事件进行处理,所以Selector切到哪个channel是由事件决定的。当线程从某个客户端通道未读取到数据时,可以把空闲时间用来做其他任务,性能得到了提升。
154 0
|
弹性计算 Java API
Netty入门到超神系列-Java NIO 三大核心(selector,channel,buffer)
理解Selector 和 Channel Selector 选择器,也叫多路复用器,可以同时处理多个客户端连接,多路复用器采用轮询机制来选择有读写事件的客户端链接进行处理。 通过 Selector ,一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。 由于它的读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。
251 0
|
存储 Java 容器
java Nio(二): Buffer(缓冲区)的数据存取
java Nio(二): Buffer(缓冲区)的数据存取
java Nio(二): Buffer(缓冲区)的数据存取
|
Java
Java NIO 中的 Buffer 缓冲区详解(下)
Java NIO 中的 Buffer 缓冲区详解
190 0
Java NIO 中的 Buffer 缓冲区详解(下)
|
设计模式 缓存 Java
NIO中Buffer的重要属性关系解析
NIO中Buffer的重要属性关系解析
188 0
|
存储 Java
小师妹学JavaIO之:NIO中那些奇怪的Buffer
小师妹学JavaIO之:NIO中那些奇怪的Buffer
小师妹学JavaIO之:NIO中那些奇怪的Buffer