NIO之Buffer的学习

Buffer是JavaNIO的核心组件,用于与通道进行交互。它有多种类型如ByteBuffer、CharBuffer等,具有capacity、position和limit等属性。Buffer的使用包括分配、写入数据、flip()切换到读模式、读取数据以及clear()或compact()复用。直接缓冲区和内存映射文件提供高效的I/O操作。
摘要由CSDN通过智能技术生成

Buffer
Java NIO 中的 Buffer 用于和 NIO 通道进行交互。数据是从通道读入缓冲区,从缓冲
区写入到通道中的
在这里插入图片描述
Java NIO 有以下 Buffer 类型

- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffe

capacity
作为一个内存块,Buffer 有一个固定的大小值,也叫“capacity”.你只能往里写
capacity 个 byte、long,char 等类型。一旦 Buffer 满了,需要将其清空(通过读数
据或者清除数据)才能继续写数据往里写数据。
position
1)写数据到 Buffer 中时,position 表示写入数据的当前位置,position 的初始值为
0。当一个 byte、long 等数据写到 Buffer 后, position 会向下移动到下一个可插入
数据的 Buffer 单元。position 最大可为 capacity – 1(因为 position 的初始值为 0).
2)读数据到 Buffer 中时,position 表示读入数据的当前位置,如 position=2 时表
示已开始读入了 3 个 byte,或从第 3 个 byte 开始读取。通过 ByteBuffer.flip()切换到
读模式时 position 会被重置为 0,当 Buffer 从 position 读入数据后,position 会下
移到下一个可读入的数据 Buffer 单元。
limit
1)写数据时,limit 表示可对 Buffer 最多写入多少个数据。写模式下,limit 等于
Buffer 的 capacity。
2)读数据时,limit 表示 Buffer 里有多少可读数据(not null 的数据),因此能读到
之前写入的所有数据(limit 被设置成已写数据的数量,这个值在写模式下就是
position)。
Buffer 分配和写数据
Buffer 分配
要想获得一个 Buffer 对象首先要进行分配。 每一个 Buffer 类都有一个 allocate 方法。
下面是一个分配 48 字节 capacity 的 ByteBuffer 的例子。

ByteBuffer buf = ByteBuffer.allocate(48);

这是分配一个可存储 1024 个字符的 CharBuffer:

CharBuffer buf = CharBuffer.allocate(1024);

向 Buffer 中写数据
写数据到 Buffer 有两种方式:
(1)从 Channel 写到 Buffer。
(2)通过 Buffer 的 put()方法写到 Buffer 里。
从 Channel 写到 Buffer 的例子

int bytesRead = inChannel.read(buf); 

通过 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 的例子:

 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。例如:

buffer.mark();
buffer.reset();

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

@Test
	public void testSlice() throws IOException {
		ByteBuffer buffer = ByteBuffer.allocate(10);
		// 缓冲区中的数据 0-9
		for (int i = 0; i < buffer.capacity(); ++i) {
			buffer.put((byte) i);
		}
		// 创建子缓冲区
		buffer.position(5);
		buffer.limit(8);
		ByteBuffer slice = buffer.slice();
		// 改变子缓冲区的内容
		for (int i = 0; i < slice.capacity(); ++i) {
			byte b = slice.get(i);
			b *= 10;
			slice.put(i, b);
		}
		buffer.position(0);
		buffer.limit(buffer.capacity());
		while (buffer.remaining() > 0) {
			System.out.println(buffer.get());
		}
	}
//	0
//	1
//	2
//	3
//	4
//	50
//	60
//	70
//	8
//	9

只读缓冲区
可以读取它们,但是不能向它们写入数据。调用缓冲
区的 asReadOnlyBuffer()方法,将任何常规缓冲区转 换为只读缓冲区,这个方法返回
一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。如
果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化
只读缓冲区对于保护数据很有用。在将缓冲区传递给某个 对象的方法时,无法知道这个方法是
否会修改缓冲区中的数据。创建一个只读的缓冲区可以保证该缓冲区不会被修改。只
可以把常规缓冲区转换为只读缓冲区,而不能将只读的缓冲区转换为可写的缓冲区

@Test
	public void testReadOnly() throws IOException {
		ByteBuffer buffer = ByteBuffer.allocate(10);
		// 缓冲区中的数据 0-9
		for (int i = 0; i < buffer.capacity(); ++i) {
			buffer.put((byte) i);
		}
		// 创建只读缓冲区
		ByteBuffer readonly = buffer.asReadOnlyBuffer();
		// 改变原缓冲区的内容
		for (int i = 0; i < buffer.capacity(); ++i) {
			byte b = buffer.get(i);
			b *= 10;
			buffer.put(i, b);
		}
		readonly.position(0);
		readonly.limit(buffer.capacity());
		// 只读缓冲区的内容也随之改变
		while (readonly.remaining() > 0) {
			System.out.println(readonly.get());
		}
	}
//	0
//	10
//	20
//	30
//	40
//	50
//	60
//	70
//	80
//	90

直接缓冲区
直接缓冲区是为加快 I/O 速度,也就是说,它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后),尝试避免将缓冲区的内容拷贝到一个中间缓冲区中 或者从一个中间缓冲区中拷贝数据。
要分配直接缓冲区,需要调用 allocateDirect()方法

复制文件
@Test
	public void testDirectBuffer() throws IOException {
		String infile = "d:\\TEST\\666.txt";
		FileInputStream fin = new FileInputStream(infile);
		FileChannel fcin = fin.getChannel();
		String outfile = String.format("d:\\TEST\\333.txt");
		FileOutputStream fout = new FileOutputStream(outfile);
		FileChannel fcout = fout.getChannel();
		// 使用 allocateDirect,而不是 allocate
		ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
		while (true) {
			buffer.clear();
			int r = fcin.read(buffer);
			if (r == -1) {
				break;
			}
			buffer.flip();
			fcout.write(buffer);
		}
	}

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通
道的 I/O 快的多。内存映射文件 I/O 是通过使文件中的数据出现为 内存数组的内容来
完成的,这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。
一般来说,只有文件中实际读取或者写入的部分才会映射到内存中
(Java在处理大文件时,一般使用BufferedReader,BufferedInputStream这里带缓冲的IO类,如果是超大的文件的话,更快的方式是使用MappedByteBuffer。)

	static private final int start = 0;
	static private final int size = 102;
	public static void main(String args[]) throws IOException {
		RandomAccessFile raf = new RandomAccessFile("d:\\TEST\\666.txt", "rw");
		FileChannel fc = raf.getChannel();
		MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, start, size);
		mbb.put(0, (byte) 97);
		mbb.put(1023, (byte) 122);
		raf.close();
	}

扩展:拷贝大文件效率比较学习
1、https://blog.51cto.com/u_15147537/5939200
2、https://blog.csdn.net/ljh_learn_from_base/article/details/77816957
3、https://www.jianshu.com/p/f90866dcbffc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值