Java InputStream转化为字节数组和ByteBuffer

2023/06/09

1. 概述

在本教程中,我们介绍如何将InputStream转换为byte[]和ByteBuffer;首先使用纯Java实现,然后使用Guava 和Commons IO工具库。

2. 转换为字节数组

字节数组的重要方面是它支持对存储在内存中的每个8位(一个字节)值进行索引(快速)访问。因此,你可以操纵这些字节来控制每一位。首先我们看看如何将简单的输入流转换为字节数组;首先使用纯Java,然后使用Guava和Apache Commons IO。

2.1 使用纯Java进行转换

让我们从一个专注于处理固定大小流的Java解决方案开始:

@Test
final void givenUsingPlainJavaOnFixedSizeStream_whenConvertingAnInputStreamToAByteArray_thenCorrect() throws IOException {
	final InputStream is = new ByteArrayInputStream(new byte[]{0, 1, 2});
	final byte[] targetArray = new byte[is.available()];
    
	is.read(targetArray);
}

在缓冲流的情况下,我们处理的是缓冲流,并且不知道底层数据的确切大小,我们需要使实现更加灵活:

@Test
final void givenUsingPlainJavaOnUnknownSizeStream_whenConvertingAnInputStreamToAByteArray_thenCorrect() throws IOException {
	final InputStream is = new ByteArrayInputStream(new byte[]{0, 1, 2, 3, 4, 5, 6});
	final ByteArrayOutputStream buffer = new ByteArrayOutputStream();

	int nRead;
	final byte[] data = new byte[4];

	while ((nRead = is.read(data, 0, data.length)) != -1) {
		buffer.write(data, 0, nRead);
	}

	buffer.flush();
	final byte[] targetArray = buffer.toByteArray();
}

从Java 9开始,我们可以使用专用的readNbytes方法实现相同的目的:

@Test
final void givenUsingPlainJava9OnUnknownSizeStream_whenConvertingAnInputStreamToAByteArray_thenCorrect() throws IOException {
	final InputStream is = new ByteArrayInputStream(new byte[]{0, 1, 2, 3, 4, 5, 6});
	final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    
	int nRead;
	final byte[] data = new byte[4];
    
	while ((nRead = is.readNBytes(data, 0, data.length)) != 0) {
		System.out.println("here " + nRead);
		buffer.write(data, 0, nRead);
	}
    
	buffer.flush();
	final byte[] targetArray = buffer.toByteArray();
}

这两种方法之间的区别非常微妙。对于第一种,read(byte[] b, int off, int len)从输入流中读取最多len个字节的数据,而第二个readNBytes(byte[] b, int off, int len)精确读取请求的字节数。

此外,如果输入流中没有更多可用数据,则read返回-1 。然而,readNbytes总是返回读入缓冲区的实际字节数。

我们还可以一次读取所有字节:

@Test
void givenUsingPlainJava9_whenConvertingAnInputStreamToAByteArray_thenCorrect() throws IOException {
	final InputStream is = new ByteArrayInputStream(new byte[]{0, 1, 2});
    
	byte[] data = is.readAllBytes();
}

2.2 使用Guava进行转换

现在让我们看一下基于Guava的简单解决方案,使用方便的ByteStreams工具类:

@Test
final void givenUsingGuava_whenConvertingAnInputStreamToAByteArray_thenCorrect() throws IOException {
	final InputStream initialStream = ByteSource.wrap(new byte[]{0, 1, 2})
			.openStream();
    
	final byte[] targetArray = ByteStreams.toByteArray(initialStream);
}

2.3 使用Commons IO进行转换

最后,下面是使用Apache Commons IO的简单解决方案:

@Test
final void givenUsingCommonsIO_whenConvertingAnInputStreamToAByteArray_thenCorrect() throws IOException {
	final InputStream initialStream = new ByteArrayInputStream(new byte[]{0, 1, 2});
    
	final byte[] targetArray = IOUtils.toByteArray(initialStream);
}

IOUtils.toByteArray()方法在内部缓冲InputStream,因此在需要缓冲时无需使用BufferedInputStream实例。

3. 转换为ByteBuffer

现在,我们介绍从InputStream中获取ByteBuffer。当我们需要在内存中进行快速和直接的低级I/O操作时,这很有用。与之前一样,我们通过三种不同的方式来实现。

3.1 使用纯Java进行转换

对于字节流,我们知道底层数据的确切大小。因此我们可以使用ByteArrayInputStream#available方法将字节流读入ByteBuffer:

@Test
void givenUsingCoreClasses_whenByteArrayInputStreamToAByteBuffer_thenLengthMustMatch() throws IOException {
	byte[] input = new byte[]{0, 1, 2};
	InputStream initialStream = new ByteArrayInputStream(input);
	ByteBuffer byteBuffer = ByteBuffer.allocate(3);
	while (initialStream.available() > 0) {
		byteBuffer.put((byte) initialStream.read());
	}
    
	assertEquals(byteBuffer.position(), input.length);
}

3.2 使用Guava进行转换

下面是一个简单的基于Guava的解决方案,通过使用方便的ByteStreams工具类:

@Test
void givenUsingGuava__whenByteArrayInputStreamToAByteBuffer_thenLengthMustMatch() throws IOException {
	InputStream initialStream = ByteSource
			.wrap(new byte[]{0, 1, 2})
			.openStream();
	byte[] targetArray = ByteStreams.toByteArray(initialStream);
	ByteBuffer bufferByte = ByteBuffer.wrap(targetArray);
	while (bufferByte.hasRemaining()) {
		bufferByte.get();
	}
    
	assertEquals(bufferByte.position(), targetArray.length);
}

在这里,我们使用带有方法hasRemaining的while循环来显示将所有字节读入ByteBuffer的不同方法。否则,断言将失败,因为ByteBuffer索引位置将为零。

3.3 使用Commons IO进行转换

最后,下面是使用Apache Commons IO中IOUtils类的例子:

@Test
void givenUsingCommonsIo_whenByteArrayInputStreamToAByteBuffer_thenLengthMustMatch() throws IOException {
	byte[] input = new byte[]{0, 1, 2};
	InputStream initialStream = new ByteArrayInputStream(input);
	ByteBuffer byteBuffer = ByteBuffer.allocate(3);
	ReadableByteChannel channel = newChannel(initialStream);
	IOUtils.readFully(channel, byteBuffer);
    
	assertEquals(byteBuffer.position(), input.length);
}

4. 总结

本文演示了使用纯Java、Guava和Apache Commons IO将原始输入流转换为字节数组和ByteBuffer的各种方法。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章