Java NIO学习:缓冲区

本文详细介绍了Java NIO中的缓冲区概念及其使用方法,包括创建缓冲区、理解缓冲区的capacity、position和limit属性,掌握读写模式切换及常用API。并通过示例演示了如何利用缓冲区进行文件的读写操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用InputStream读取文件内容的时候,我们都是用字节数组进行批量读取的,而不是一个一个字节的读。NIO中的缓冲区其实就和传统IO中的字节数组一样,都是为了提高IO性能。NIO里面为每种基本数据类型都提供了对应的缓冲区对象,如ByteBuffer、ShortBuffer、CharBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。这几种缓冲区对象使用方式基本是一样的,这里以ByteBuffer为例学习下NIO中的缓冲区。

1.创建缓冲区

只能通过ByteBuffer类的静态方法allocate、allocateDirect或者wrap方法来创建ByteBuffer对象。
// 创建一个容量capacity是10的字节缓冲区(使用的是java堆内存)
ByteBuffer buffer1 = ByteBuffer.allocate(10);

// 创建一个容量capacity是20的字节缓冲区(使用的是堆外内存)
ByteBuffer buffer2 = ByteBuffer.allocateDirect(20);

// 通过已有的字节数组创建缓冲区(缓冲区和字节数组共用同一块内存)
byte[] bytes = new byte[40];
ByteBuffer buffer3 = ByteBuffer.wrap(bytes, 0, bytes.length);

这里组要注意一点:使用字节数组创建的缓冲区,与字节数组共用同一块内存。The new buffer will be backed by the given byte array;that is, modifications to the buffer will cause the array to be modified and vice versa(反之亦然)。
byte[] bytes = new byte[4];
ByteBuffer buffer = ByteBuffer.wrap(bytes);

buffer.putInt(0x12345678);
// [18, 52, 86, 120]
System.out.println(Arrays.toString(bytes));

bytes[0] = 1;

// 1
buffer.flip();
System.out.println(buffer.get());

2.Buffer的capacity、position和limit

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成Buffer对象,并提供了一组方法,用来方便的访问该块内存。为了理解Buffer的工作原理,需要熟悉它的三个属性:capacity、position和limit。

capacity
作为一个内存块,Buffer有一个固定的大小,叫“capacity”,在创建缓冲区的时候指定。你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

position
当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1。当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。也就是说:position就是读取或者写入数据的当前位置

limit
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)。



3.Buffer的读模式和写模式

刚创建的Buffer对象,处于写模式,这个很好理解,只有先写入数据,然后才能从中读取数据。
// 分配一块缓存区(写模式),因为只有先写入数据,才能读出数据
ByteBuffer buffer = ByteBuffer.allocate(64);

// 写模式下capacity == limit
System.out.println(buffer.capacity());// 64
System.out.println(buffer.limit());// 64
System.out.println(buffer.position()); // 0

buffer.putInt(12);

// 写模式下,limit等于capacity
System.out.println(buffer.capacity());// 64
System.out.println(buffer.limit());// 64
System.out.println(buffer.position());// 4

写完数据之后,必须要将缓冲区切换到读模式下,才能读取数据。
ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.putInt(1);
buffer.putInt(2);
buffer.putInt(3);
buffer.putInt(4);

// flip切换到读模式
buffer.flip();

// 读模式下,limit含义是已经写入的字节数
System.out.println(buffer.position());//0
System.out.println(buffer.limit());//16
System.out.println(buffer.capacity());//64

System.out.println(buffer.getInt());//1
System.out.println(buffer.getInt());//2

//每次get只改变position
System.out.println(buffer.position());//8
System.out.println(buffer.limit());//16
System.out.println(buffer.capacity());//64

如果读取完数据后,还需要继续向缓冲区写数据怎么办呢?再次调用flip()?不行,flip只是从写模式切换到读模式。可以使用clear(),这个方法会将position设置成0,limit设置成capacity。
ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.putInt(1);
buffer.putInt(2);

buffer.flip();
System.out.println(buffer.getInt());
System.out.println(buffer.getInt());

// 重新切换到写
buffer.clear();
buffer.putInt(3);
buffer.putInt(4);

buffer.flip();
System.out.println(buffer.getInt());
System.out.println(buffer.getInt());

4.向buffer写数据

向buffer中写入数据有2种方式,一种是通过buffer对象的put()方法,一种是通过NIO中的channel写入。
ByteBuffer buffer = ByteBuffer.allocate(64);

// 在index=10处写入一个int
buffer.putInt(10, 1);

// 在指定位置写入数据,不会改变postion
System.out.println(buffer.position());//0

// 在当前位置写入,会改变position
buffer.putInt(22);

System.out.println(buffer.position());//4

RandomAccessFile aFile = new RandomAccessFile("url.txt", "r");
FileChannel inChannel = aFile.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(20);

// read data into buffer from channel
int readBytes = inChannel.read(buffer); 

aFile.close();

5.从buffer读数据

和写操作类似,读操作也有2种方式:从Buffer读取数据到Channel;使用get()方法从Buffer中读取数据。
RandomAccessFile aFile = new RandomAccessFile("demo.txt", "rw");
FileChannel outChannel = aFile.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(20);
buffer.putChar('a');
buffer.putChar('b');
buffer.putChar('c');
buffer.putChar('d');

// 缓冲区数据写入文件
buffer.flip();
outChannel.write(buffer);

aFile.close();

6.rewind()

Buffer.rewind()将position设回0,不改变limit,所以可以重读Buffer中的所有数据。
ByteBuffer buffer = ByteBuffer.allocate(64);

buffer.putInt(1234);

// position是4
System.out.println(buffer.getInt()); // 0

// position是0
buffer.rewind();
System.out.println(buffer.getInt());// 1234

// rewind可以实现重复读取
buffer.rewind();
System.out.println(buffer.getInt());// 1234

7.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准备好写数据了,但是不会覆盖未读的数据。
ByteBuffer buffer = ByteBuffer.allocate(26);
for(char i = 'a' ; i <= 'z'; i++)
{
	buffer.put((byte)i);
}

buffer.flip();

System.out.println((char)buffer.get());//a
System.out.println((char)buffer.get());//b

buffer.compact();

buffer.put((byte)'A');
buffer.put((byte)'B');


buffer.flip();
System.out.println((char)buffer.get());//c
System.out.println((char)buffer.get());//d

System.out.println((char)buffer.get(24));//A
System.out.println((char)buffer.get(25));//B

8.mark()与reset()

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。这2个函数源码如下:
public final Buffer mark() {
	mark = position;
	return this;
}

public final Buffer reset() {
    int m = mark;
	if (m < 0)
	    throw new InvalidMarkException();
	position = m;
	return this;
}

上面就是缓冲区的一些基本用法了,下面我们使用NIO中的缓存区,完成文件的读写操作,看下如何使用上面的API。
public static void copyFile(String srcPath, String destPath)
			throws Exception
{
	RandomAccessFile srcFile = new RandomAccessFile(srcPath, "r");
	FileChannel inputChannel = srcFile.getChannel();

	ByteBuffer buffer = ByteBuffer.allocate(1024);

	RandomAccessFile destFile = new RandomAccessFile(destPath, "rw");
	FileChannel outputChannel = destFile.getChannel();

	while (inputChannel.read(buffer) != -1)
	{
		// prepare for read
		buffer.flip();

		// write data into channel
		outputChannel.write(buffer);
		
		// prepare for next write
		buffer.clear();
	}

	srcFile.close();
	destFile.close();
}


参考文章:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值