Java NIO – 分散/聚集或向量 IO

通道提供了一项重要的新特性,称为分散/聚集(在某些圈子中称为向量 I/O)。 分散/聚集是一个简单而强大的概念。 分散/聚集是一种技术,通过该技术,可以通过一次read()调用将字节从流中读取到一组缓冲区(向量)中,并且可以通过一次write()调用,将字节从一组缓冲区中写入到流中。大多数现代操作系统都支持本机向量 I/O。 当您请求通道上的分散/聚集操作时,该请求将转换为适当的本机调用以直接填充或耗尽缓冲区。 这是一个巨大的胜利,因为减少了或消除了缓冲区副本和系统调用。 直到最近,Java 还没有执行向量 I/O 操作的能力。 因此,我们习惯于将字节直接读取到单个字节数组中,或者进行多次读取以获取数据。

分散/聚集 API

从通道读取的散射是将数据读取到多个缓冲区中的读取操作。因此,通道将来自通道的数据分散到多个缓冲区中。对通道的聚集写入是一种将来自多个缓冲区的数据写入单个通道的写入操作。因此,通道将来自多个缓冲器的数据聚集到一个通道中。在需要分别处理传输数据的各个部分的情况下,分散/聚集可能非常有用。

public interface ScatteringByteChannel extends ReadableByteChannel
{
	public long read (ByteBuffer [] dsts) throws IOException;
	public long read (ByteBuffer [] dsts, int offset, int length) throws IOException;
}

public interface GatheringByteChannel extends WritableByteChannel
{
	public long write(ByteBuffer[] srcs) throws IOException;
	public long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
}

您可以看到每个接口都添加了两个新方法,这些新方法将缓冲区数组作为参数。

现在,让我们写一个简单的例子来了解如何使用此特性。

分散/聚集 IO 示例

在此示例中,我创建了两个缓冲区。 一个缓冲区将存储一个随机数,另一个缓冲区将存储一个随机字符串。 我将使用GatheringByteChannel读写文件通道中两个缓冲区中存储的数据。 然后,我将使用ScatteringByteChannel将文件中的数据读回到两个单独的缓冲区中,并在控制台中打印内容以验证存储和检索的数据是否匹配。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;

public class ScatteringAndGatheringIOExample 
{
	public static void main(String params[]) 
	{
		String data = "Scattering and Gathering example shown in howtodoinjava.com";

		gatherBytes(data);
		scatterBytes();
	}

	/*
	 * gatherBytes() reads bytes from different buffers and writes to file
	 * channel. Note that it uses a single write for both the buffers.
	 */
	public static void gatherBytes(String data) 
	{
		//First Buffer holds a random number
		ByteBuffer bufferOne = ByteBuffer.allocate(4);

		//Second Buffer holds data we want to write
		ByteBuffer buffer2 = ByteBuffer.allocate(200);

		//Writing Data sets to Buffer
		bufferOne.asIntBuffer().put(13);
		buffer2.asCharBuffer().put(data);

		//Calls FileOutputStream(file).getChannel()
		GatheringByteChannel gatherer = createChannelInstance("test.txt", true);

		//Write data to file
		try 
		{
			gatherer.write(new ByteBuffer[] { bufferOne, buffer2 });
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}

	/*
	 * scatterBytes() read bytes from a file channel into a set of buffers. Note that
	 * it uses a single read for both the buffers.
	 */
	public static void scatterBytes() 
	{
		//First Buffer holds a random number
		ByteBuffer bufferOne = ByteBuffer.allocate(4);

		//Second Buffer holds data we want to write
		ByteBuffer bufferTwo = ByteBuffer.allocate(200);

		//Calls FileInputStream(file).getChannel()
		ScatteringByteChannel scatterer = createChannelInstance("test.txt", false);

		try 
		{
			//Reading from the channel
			scatterer.read(new ByteBuffer[] { bufferOne, bufferTwo });
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}

		//Read the buffers seperately
		bufferOne.rewind();
		bufferTwo.rewind();

		int bufferOneContent = bufferOne.asIntBuffer().get();
		String bufferTwoContent = bufferTwo.asCharBuffer().toString();

		//Verify the content
		System.out.println(bufferOneContent);
		System.out.println(bufferTwoContent);
	}

	public static FileChannel createChannelInstance(String file, boolean isOutput) 
	{
		FileChannel fc = null;
		try 
		{
			if (isOutput) {
				fc = new FileOutputStream(file).getChannel();
			} else {
				fc = new FileInputStream(file).getChannel();
			}
		} 
		catch (Exception e) {
			e.printStackTrace();
		}
		return fc;
	}
}

总结

正确使用分散/聚集工具可能会非常强大。 它使您可以将将读取的数据分离到多个存储桶中或将不同的数据块组装成一个整体的繁琐工作委托给操作系统。 这是一个巨大的胜利,因为操作系统已针对此类事物进行了高度优化。 它节省了您四处移动的工作,从而避免了缓冲区复制,并减少了编写和调试所需的代码量。 由于基本上是通过提供对数据容器的引用来组装数据,因此可以通过以不同组合构建多个缓冲区引用数组来以不同的方式来组装各种块。