java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java NIO ByteBuffer读取文件

Java NIO ByteBuffer读取文件方式

作者:u012888365

这篇文章主要介绍了Java NIO ByteBuffer读取文件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

Java NIO ByteBuffer读取文件

FileChannel 和 ByteBuffer

从JDK1.4以后就提供java.nio的包,nio主要提供字节与字符的映射、内存映射文件和文件加锁机制

其中内存映射文件在读取大文件时可能会用上,因为内存映射不是直接把文件加载到JVM内存空间

而是借用操作系统对文件的读取,这经历了由当前Java态进入到操作系统内核态,再由操作系统读取文件,

并返回数据到当前Java态的过程。由Java态进入操作系统内核态离不开nio包中两个重要的类

FileChannel 和 ByteBuffer。FileChannel表示文件通道,可以从FileInputStream、FileOutputStream

以及RandomAccessFile对象获取文件通道,你可以从文件通道直接读取文件,也可以使用“内存映射”

即使用通道,将文件内存映射到ByteBuffer,可以映射一部分内容,也可以映射全部内容,使用内存映射

能大幅提高我们操作大文件的速度

FileChannel 和 ByteBuffer文件读取

package nio;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
/**
 * 
 * Channel类似与流,数据可以从Channel读取到Buffer,也可以从Buffer写入到Channel
 * 但通道和流还是有区别,比如流只能是单向读或写,而通道可以异步读写
 * 
 * @author yli
 */
public class FileChannelTest {
	// 110M
	private static String file = "/mnt/hgfs/pdf/Java2核心技术II卷.高级特性.pdf";
	public static void main(String[] args) throws IOException {
		// 普通 NIO 读取
		// 每次读取1024个字节
		// readByChannelTest(1024);	// 28151毫秒
		// 普通 NIO 读取
		// 每次读取1个字节,每次读取1个字节太慢了
		// readByChannelTest(1);
		// 使用内存映射文件来读取
		// 从FileChannel拿到MappedByteBuffer,读取文件内容
		readByChannelTest3(1024);	// 61毫秒,甚至不到100毫秒
		// 对于一个只有110M的文件,验证使用FileChannel映射得到MappedByteBuffer
		// 就能大幅提交文件读取速度
		// 普通的缓冲流读取
		// readByBufferdStream();	// 3922毫秒
	}
	/**
	 * 使用FileChannel读取文件,并打印在控制台
	 * 
	 * @param 每次读取多少个字节
	 * @throws IOException
	 */
	public static void readByChannelTest(int allocate) throws IOException {
		long start = System.currentTimeMillis();
		FileInputStream fis = new FileInputStream(file);
		// 1.从FileInputStream对象获取文件通道FileChannel
		FileChannel channel = fis.getChannel();
		long size = channel.size();
		// 2.从通道读取文件内容
		byte[] bytes = new byte[1024];
		ByteBuffer byteBuffer = ByteBuffer.allocate(allocate);
		// channel.read(ByteBuffer) 方法就类似于 inputstream.read(byte)
		// 每次read都将读取 allocate 个字节到ByteBuffer
		int len;
		while ((len = channel.read(byteBuffer)) != -1) {
			// 注意先调用flip方法反转Buffer,再从Buffer读取数据
			byteBuffer.flip();
			// 有几种方式可以操作ByteBuffer
			// 1.可以将当前Buffer包含的字节数组全部读取出来
			//bytes = byteBuffer.array();
			// System.out.print(new String(bytes));
			// 2.类似与InputStrean的read(byte[],offset,len)方法读取 
			byteBuffer.get(bytes, 0, len);
			// System.out.print(new String(bytes, 0 ,len));
			// 3.也可以遍历Buffer读取每个字节数据
			// 一个字节一个字节打印在控制台,但这种更慢且耗时
			// while(byteBuffer.hasRemaining()) {
			// System.out.print((char)byteBuffer.get());
			// }
			// 最后注意调用clear方法,将Buffer的位置回归到0
			byteBuffer.clear();
		}
		// 关闭通道和文件流
		channel.close();
		fis.close();
		long end = System.currentTimeMillis();
		System.out.println(String.format("\n===>文件大小:%s 字节", size));
		System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start));
	}
	/**
	 * 仍然是根据FileChannel操作ByteBuffer,从ByteBuffer读取内容
	 * 通道读取文件,速度比内存映射慢很多,甚至比普通缓冲流要慢
	 * 
	 * @param allocate
	 * @throws IOException
	 */
	public static void readByChannelTest2(int allocate) throws IOException {
		long start = System.currentTimeMillis();
		FileInputStream fis = new FileInputStream(file);
		// 1.从FileInputStream对象获取文件通道FileChannel
		FileChannel channel = fis.getChannel();
		long size = channel.size();
		// 每次读取allocate个字节,计算要循环读取多少次
		long cycle = size / allocate;
		// 看是否能整数倍读完
		int mode = (int) (size % allocate);
		// 循环读取
		byte[] bytes;
		ByteBuffer byteBuffer = ByteBuffer.allocate(allocate);
		for (long i = 0; i < cycle; i++) {
			if (channel.read(byteBuffer) != -1) {
				byteBuffer.flip();
				bytes = byteBuffer.array();
				// System.out.print(new String(bytes));
				byteBuffer.clear();
			}
		}
		// 读取最后mode个字节
		if (mode > 0) {
			byteBuffer = ByteBuffer.allocate(mode);
			if (channel.read(byteBuffer) != -1) {
				byteBuffer.flip();
				bytes = byteBuffer.array();
				// System.out.print(new String(bytes));
				byteBuffer.clear();
			}
		}
		// 关闭通道和文件流
		channel.close();
		fis.close();
		long end = System.currentTimeMillis();
		System.out.println(String.format("\n===>文件大小:%s 字节", size));
		System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start));
	}
	/**
	 * 通过 FileChannel.map()拿到MappedByteBuffer
	 * 使用内存文件映射,速度会快很多
	 * 
	 * @throws IOException
	 */
	public static void readByChannelTest3(int allocate) throws IOException {
		long start = System.currentTimeMillis();
		RandomAccessFile fis = new RandomAccessFile(new File(file), "rw");
		FileChannel channel = fis.getChannel();
		long size = channel.size();
		// 构建一个只读的MappedByteBuffer
		MappedByteBuffer mappedByteBuffer = channel.map(MapMode.READ_ONLY, 0, size);
		// 如果文件不大,可以选择一次性读取到数组
		// byte[] all = new byte[(int)size];
		// mappedByteBuffer.get(all, 0, (int)size);
		// 打印文件内容
		// System.out.println(new String(all));
		// 如果文件内容很大,可以循环读取,计算应该读取多少次
		byte[] bytes = new byte[allocate];
		long cycle = size / allocate;
		int mode = (int)(size % allocate);
		//byte[] eachBytes = new byte[allocate];
		for (int i = 0; i < cycle; i++) {
			// 每次读取allocate个字节
			mappedByteBuffer.get(bytes);
			// 打印文件内容,关闭打印速度会很快
			// System.out.print(new String(eachBytes));
		}
		if(mode > 0) {
			bytes = new byte[mode];
			mappedByteBuffer.get(bytes);
			// 打印文件内容,关闭打印速度会很快
			// System.out.print(new String(eachBytes));
		}
		// 关闭通道和文件流
		channel.close();
		fis.close();
		long end = System.currentTimeMillis();
		System.out.println(String.format("\n===>文件大小:%s 字节", size));
		System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start));
	}
	/**
	 * 普通Java IO 缓冲流读取
	 * @throws IOException
	 */
	public static void readByBufferdStream() throws IOException {
		long start = System.currentTimeMillis();
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
		long size = bis.available();
		int len = 0;
		int allocate = 1024;
		byte[] eachBytes = new byte[allocate];
		while((len = bis.read(eachBytes)) != -1) {
			// System.out.print(new String(eachBytes, 0, len));
		}
		bis.close();
		long end = System.currentTimeMillis();
		System.out.println(String.format("\n===>文件大小:%s 字节", size));
		System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start));
	}
}

java nio ByteBuffer的使用

Buffer是nio包的一个抽象类,作为java nio的三大组件(Buffer、Channel,Selector)之一,在java nio网络编程中尤为重要。

Buffer提供了一个字节缓冲区,配合Channel使用,可以从Channel中读取或写入数据。

结构

属性介绍

以ByteBuffer为例,其包括5个主要的属性:hb、position、limit、capacity、mark。

常用API方法

/**
 * 初始化指定大小的ByteBuffer对象返回。
 */
static ByteBuffer allocate(int capacity);
/**
 * 使用指定字节数组初始化ByteBuffer对象返回。
 */
static ByteBuffer wrap(byte[] array)
/**
 * 返回当前position位置的字节数据,position自增1。
 */    
byte get();
/**
 * 返回指定位置的数据。
 */  
byte get(int index);
/**
 * 当前position位置设置为传入字节,position自增1。
 */ 
ByteBuffer put(byte b);
/**
 * 切换到读模式。
 */ 
Buffer flip();
/**
 * 切换到写模式,不保留未读完数据。
 */ 
Buffer clear();
/**
 * 切换到写模式,保留未读完数据。
 */ 
ByteBuffer compact();

图解

①初始化状态。执行**ByteBuffer.allocate(10);**后状态。

在这里插入图片描述

②调用put方法插入数据,每往当前position位置插入一个数据,position执行加1操作。插入4个数据后状态。

在这里插入图片描述

③调用flip方法,依次设置limit=position、position=0、mark=-1。

在这里插入图片描述

④调用get方法读取数据,返回当前position下标对应的值,然后positon执行加1操作。读取三个数据后状态。

在这里插入图片描述

⑤调用clearcompact方法,重置position、limit的值。

调用clear后状态(依次执行position = 0、limit = capacity、mark = -1)。

在这里插入图片描述

调用compact后状态。

在这里插入图片描述

读模式和写模式

其实ByteBuffer本身并没有读模式、写模式的概念,为了便于初学者理解网友们强加的概念。

flip、clear、compact等方法只是修改了position、limit、mark等属性的值而已,理解了上面的几个操作图就不需要理解不存在的读模式、写模式,避免混淆理解。

使用演示

/**
 * 代码
 */ 
public class ByteBufferDemo {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put((byte)'a');
        buffer.put((byte)'b');
        buffer.put((byte)'c');
        buffer.put((byte)'d');
        System.out.println(buffer);
        buffer.flip();
        System.out.println(buffer);
        System.out.println(buffer.get());
        System.out.println(buffer.get());
        System.out.println(buffer.get());
        buffer.clear();
        System.out.println(buffer);
        System.out.println(Arrays.toString(buffer.array()));
        System.out.println(buffer.get(2));
    }
}
/**
 * 运行结果
 */
java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
java.nio.HeapByteBuffer[pos=0 lim=4 cap=10]
97
98
99
java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
[97, 98, 99, 100, 0, 0, 0, 0, 0, 0]
99
/**
 * 代码
 */ 
public class ByteBufferDemo {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put((byte)'a');
        buffer.put((byte)'b');
        buffer.put((byte)'c');
        buffer.put((byte)'d');
        System.out.println(buffer);
        buffer.flip();
        System.out.println(buffer);
        System.out.println(buffer.get());
        System.out.println(buffer.get());
        System.out.println(buffer.get());
        buffer.compact();
        System.out.println(buffer);
        System.out.println(Arrays.toString(buffer.array()));
        System.out.println(buffer.get(2));
    }
}
/**
 * 运行结果
 */
java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
java.nio.HeapByteBuffer[pos=0 lim=4 cap=10]
97
98
99
java.nio.HeapByteBuffer[pos=1 lim=10 cap=10]
[100, 98, 99, 100, 0, 0, 0, 0, 0, 0]
99
/**
 * 代码
 */ 
public class ByteBufferDemo {
    public static void main(String[] args) {
        byte[] bytes = {(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g'};
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        System.out.println(buffer);
        System.out.println(Arrays.toString(buffer.array()));
        //直接读取数据
        System.out.println(buffer.get());
        System.out.println(buffer);
        //写入数据
        buffer.put((byte)0x01);
        buffer.put((byte)0x02);
        System.out.println(buffer);
        System.out.println(Arrays.toString(buffer.array()));
    }
}
/**
 * 运行结果
 */
java.nio.HeapByteBuffer[pos=0 lim=7 cap=7]
[97, 98, 99, 100, 101, 102, 103]
97
java.nio.HeapByteBuffer[pos=1 lim=7 cap=7]
java.nio.HeapByteBuffer[pos=3 lim=7 cap=7]
[97, 1, 2, 100, 101, 102, 103]

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文