Java NIO写大文件对比(win7和mac)

测试说明

写2G文件,分批次写入,每批次写入128MB;

分别在Win7系统(3G内存,双核,32位,T系列处理器)和MacOS系统(8G内存,四核,64位,i7系列处理器)下运行测试。理论上跟硬盘类型和配置也有关系,这里不再贴出了。

测试代码

package rwbigfile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.ReadableByteChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;

import util.StopWatch;

/**
 * NIO写大文件比较
 * @author Will
 *
 */
public class WriteBigFileComparison {

	// data chunk be written per time
	private static final int DATA_CHUNK = 128 * 1024 * 1024; 

	// total data size is 2G
	private static final long LEN = 2L * 1024 * 1024 * 1024L; 

	public static void writeWithFileChannel() throws IOException {
		File file = new File("e:/test/fc.dat");
		if (file.exists()) {
			file.delete();
		}

		RandomAccessFile raf = new RandomAccessFile(file, "rw");
		FileChannel fileChannel = raf.getChannel();

		byte[] data = null;
		long len = LEN;
		ByteBuffer buf = ByteBuffer.allocate(DATA_CHUNK);
		int dataChunk = DATA_CHUNK / (1024 * 1024);
		while (len >= DATA_CHUNK) {
			System.out.println("write a data chunk: " + dataChunk + "MB");

			buf.clear(); // clear for re-write
			data = new byte[DATA_CHUNK];
			for (int i = 0; i < DATA_CHUNK; i++) {
				buf.put(data[i]);
			}

			data = null;

			buf.flip(); // switches a Buffer from writing mode to reading mode
			fileChannel.write(buf);
			fileChannel.force(true);

			len -= DATA_CHUNK;
		}

		if (len > 0) {
			System.out.println("write rest data chunk: " + len + "B");
			buf = ByteBuffer.allocateDirect((int) len);
			data = new byte[(int) len];
			for (int i = 0; i < len; i++) {
				buf.put(data[i]);
			}

			buf.flip(); // switches a Buffer from writing mode to reading mode, position to 0, limit not changed
			fileChannel.write(buf);
			fileChannel.force(true);
			data = null;
		}

		fileChannel.close();
		raf.close();
	}

	/**
	 * write big file with MappedByteBuffer
	 * @throws IOException
	 */
	public static void writeWithMappedByteBuffer() throws IOException {
		File file = new File("e:/test/mb.dat");
		if (file.exists()) {
			file.delete();
		}

		RandomAccessFile raf = new RandomAccessFile(file, "rw");
		FileChannel fileChannel = raf.getChannel();
		int pos = 0;
		MappedByteBuffer mbb = null;
		byte[] data = null;
		long len = LEN;
		int dataChunk = DATA_CHUNK / (1024 * 1024);
		while (len >= DATA_CHUNK) {
			System.out.println("write a data chunk: " + dataChunk + "MB");

			mbb = fileChannel.map(MapMode.READ_WRITE, pos, DATA_CHUNK);
			data = new byte[DATA_CHUNK];
			mbb.put(data);

			data = null;

			len -= DATA_CHUNK;
			pos += DATA_CHUNK;
		}

		if (len > 0) {
			System.out.println("write rest data chunk: " + len + "B");

			mbb = fileChannel.map(MapMode.READ_WRITE, pos, len);
			data = new byte[(int) len];
			mbb.put(data);
		}

		data = null;
		unmap(mbb);  // release MappedByteBuffer
		fileChannel.close();
	}

	public static void writeWithTransferTo() throws IOException {
		File file = new File("e:/test/transfer.dat");
		if (file.exists()) {
			file.delete();
		}

		RandomAccessFile raf = new RandomAccessFile(file, "rw");
		FileChannel toFileChannel = raf.getChannel();

		long len = LEN;
		byte[] data = null;
		ByteArrayInputStream bais = null;
		ReadableByteChannel fromByteChannel = null;
		long position = 0;
		int dataChunk = DATA_CHUNK / (1024 * 1024);
		while (len >= DATA_CHUNK) {
			System.out.println("write a data chunk: " + dataChunk + "MB");

			data = new byte[DATA_CHUNK];
			bais = new ByteArrayInputStream(data);
			fromByteChannel = Channels.newChannel(bais);

			long count = DATA_CHUNK;
			toFileChannel.transferFrom(fromByteChannel, position, count);

			data = null;
			position += DATA_CHUNK;
			len -= DATA_CHUNK;
		}

		if (len > 0) {
			System.out.println("write rest data chunk: " + len + "B");

			data = new byte[(int) len];
			bais = new ByteArrayInputStream(data);
			fromByteChannel = Channels.newChannel(bais);

			long count = len;
			toFileChannel.transferFrom(fromByteChannel, position, count);
		}

		data = null;
		toFileChannel.close();
		fromByteChannel.close();
	}

	/**
	 * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash,在并发情况下很容易发生
	 * 正在释放时另一个线程正开始读取,于是crash就发生了。所以为了系统稳定性释放前一般需要检
	 * 查是否还有线程在读或写
	 * @param mappedByteBuffer
	 */
	public static void unmap(final MappedByteBuffer mappedByteBuffer) {
		try {
			if (mappedByteBuffer == null) {
				return;
			}

			mappedByteBuffer.force();
			AccessController.doPrivileged(new PrivilegedAction<Object>() {
				@Override
				@SuppressWarnings("restriction")
				public Object run() {
					try {
						Method getCleanerMethod = mappedByteBuffer.getClass()
								.getMethod("cleaner", new Class[0]);
						getCleanerMethod.setAccessible(true);
						sun.misc.Cleaner cleaner =
								(sun.misc.Cleaner) getCleanerMethod
									.invoke(mappedByteBuffer, new Object[0]);
						cleaner.clean();

					} catch (Exception e) {
						e.printStackTrace();
					}
					System.out.println("clean MappedByteBuffer completed");
					return null;
				}
			});

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws IOException {
		StopWatch sw = new StopWatch();

		sw.startWithTaskName("write with file channel's write(ByteBuffer)");
		writeWithFileChannel();
		sw.stopAndPrint();

		sw.startWithTaskName("write with file channel's transferTo");
		writeWithTransferTo();
		sw.stopAndPrint();

		sw.startWithTaskName("write with MappedByteBuffer");
		writeWithMappedByteBuffer();
		sw.stopAndPrint();
	}

}

测试结果(Y轴是耗时秒数)

  • 显然writeWithMappedByteBuffer方式性能最好,且在硬件配置较高情况下优势越加明显
  • 在硬件配置较低情况下,writeWithTransferTo比writeWithFileChannel性能稍好
  • 在硬件配置较高情况下,writeWithTransferTo和writeWithFileChannel的性能基本持平
  • 此外,注意writeWithMappedByteBuffer方式除了占用JVM堆内存外,还要占用额外的native内存(Direct Byte Buffer内存)

内存映射文件使用经验

MappedByteBuffer需要占用“双倍”的内存(对象JVM堆内存和Direct Byte Buffer内存),可以通过-XX:MaxDirectMemorySize参数设置后者最大大小

不要频繁调用MappedByteBuffer的force()方法,因为这个方法会强制OS刷新内存中的数据到磁盘,从而只能获得些微的性能提升(相比IO方式),可以用后面的代码实例进行定时、定量刷新

如果突然断电或者服务器突然Down,内存映射文件数据可能还没有写入磁盘,这时就会丢失一些数据。为了降低这种风险,避免用MappedByteBuffer写超大文件,可以把大文件分割成几个小文件,但不能太小(否则将失去性能优势)

ByteBuffer的rewind()方法将position属性设回为0,因此可以重新读取buffer中的数据;limit属性保持不变,因此可读取的字节数不变

ByteBuffer的flip()方法将一个Buffer由写模式切换到读模式

ByteBuffer的clear()和compact()可以在我们读完ByteBuffer中的数据后重新切回写模式。不同的是clear()会将position设置为0,limit设为capacity,换句话说Buffer被清空了,但Buffer内的数据并没有被清空。如果Buffer中还有未被读取的数据,那调用clear()之后,这些数据会被“遗忘”,再写入就会覆盖这些未读数据。而调用compcat()之后,这些未被读取的数据仍然可以保留,因为它将所有还未被读取的数据拷贝到Buffer的左端,然后设置position为紧随未读数据之后,limit被设置为capacity,未读数据不会被覆盖

定时、定量刷新内存映射文件到磁盘

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class MappedFile {

	// 文件名
	private String fileName;

	// 文件所在目录路径
	private String fileDirPath;

	// 文件对象
	private File file;

	private MappedByteBuffer mappedByteBuffer;
	private FileChannel fileChannel;
	private boolean boundSuccess = false;

	// 文件最大只能为50MB
	private final static long MAX_FILE_SIZE = 1024 * 1024 * 50;

	// 最大的脏数据量512KB,系统必须触发一次强制刷
	private long MAX_FLUSH_DATA_SIZE = 1024 * 512;

	// 最大的刷间隔,系统必须触发一次强制刷
	private long MAX_FLUSH_TIME_GAP = 1000;

	// 文件写入位置
	private long writePosition = 0;

	// 最后一次刷数据的时候
	private long lastFlushTime;

	// 上一次刷的文件位置
	private long lastFlushFilePosition = 0;

	public MappedFile(String fileName, String fileDirPath) {
		super();
		this.fileName = fileName;
		this.fileDirPath = fileDirPath;
		this.file = new File(fileDirPath + "/" + fileName);
		if (!file.exists()) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	/**
	 *
	 * 内存映照文件绑定
	 * @return
	 */
	public synchronized boolean boundChannelToByteBuffer() {
		try {
			RandomAccessFile raf = new RandomAccessFile(file, "rw");
			this.fileChannel = raf.getChannel();
		} catch (Exception e) {
			e.printStackTrace();
			this.boundSuccess = false;
			return false;
		}

		try {
			this.mappedByteBuffer = this.fileChannel
					.map(FileChannel.MapMode.READ_WRITE, 0, MAX_FILE_SIZE);
		} catch (IOException e) {
			e.printStackTrace();
			this.boundSuccess = false;
			return false;
		}

		this.boundSuccess = true;
		return true;
	}

	/**
	 * 写数据:先将之前的文件删除然后重新
	 * @param data
	 * @return
	 */
	public synchronized boolean writeData(byte[] data) {

		return false;
	}

	/**
	 * 在文件末尾追加数据
	 * @param data
	 * @return
	 * @throws Exception
	 */
	public synchronized boolean appendData(byte[] data) throws Exception {
		if (!boundSuccess) {
			boundChannelToByteBuffer();
		}

		writePosition = writePosition + data.length;
		if (writePosition >= MAX_FILE_SIZE) {  // 如果写入data会超出文件大小限制,不写入
			flush();
			writePosition = writePosition - data.length;
			System.out.println("File="
								+ file.toURI().toString()
								+ " is written full.");
			System.out.println("already write data length:"
								+ writePosition
								+ ", max file size=" + MAX_FILE_SIZE);
			return false;
		}

		this.mappedByteBuffer.put(data);

		// 检查是否需要把内存缓冲刷到磁盘
		if ( (writePosition - lastFlushFilePosition > this.MAX_FLUSH_DATA_SIZE)
			 ||
			 (System.currentTimeMillis() - lastFlushTime > this.MAX_FLUSH_TIME_GAP
			 && writePosition > lastFlushFilePosition) ) {
			flush();  // 刷到磁盘
		}

		return true;
	}

	public synchronized void flush() {
		this.mappedByteBuffer.force();
		this.lastFlushTime = System.currentTimeMillis();
		this.lastFlushFilePosition = writePosition;
	}

	public long getLastFlushTime() {
		return lastFlushTime;
	}

	public String getFileName() {
		return fileName;
	}

	public String getFileDirPath() {
		return fileDirPath;
	}

	public boolean isBundSuccess() {
		return boundSuccess;
	}

	public File getFile() {
		return file;
	}

	public static long getMaxFileSize() {
		return MAX_FILE_SIZE;
	}

	public long getWritePosition() {
		return writePosition;
	}

	public long getLastFlushFilePosition() {
		return lastFlushFilePosition;
	}

	public long getMAX_FLUSH_DATA_SIZE() {
		return MAX_FLUSH_DATA_SIZE;
	}

	public long getMAX_FLUSH_TIME_GAP() {
		return MAX_FLUSH_TIME_GAP;
	}

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java NIO和IO的区别

    下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异. 复制代码 代码如下: IO                NIO面向流            面向缓冲阻塞IO            非阻塞IO无                选择器 面向流与面向缓冲 Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的. Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方.此外,它不能前后移动流中的数

  • JDK1.7 之java.nio.file.Files 读取文件仅需一行代码实现

    JDK1.7中引入了新的文件操作类java.nio.file这个包,其中有个Files类它包含了很多有用的方法来操作文件,比如检查文件是否为隐藏文件,或者是检查文件是否为只读文件.开发者还可以使用Files.readAllBytes(Path)方法把整个文件读入内存,此方法返回一个字节数组,还可以把结果传递给String的构造器,以便创建字符串输出.此方法确保了当读入文件的所有字节内容时,无论是否出现IO异常或其它的未检查异常,资源都会关闭.这意味着在读文件到最后的块内容后,无需关闭文件.要注意

  • Java文件读写IO/NIO及性能比较详细代码及总结

    干Java这么久,一直在做WEB相关的项目,一些基础类差不多都已经忘记.经常想得捡起,但总是因为一些原因,不能如愿. 其实不是没有时间,只是有些时候疲于总结,今得空,下定决心将丢掉的都给捡起来. 文件读写是一个在项目中经常遇到的工作,有些时候是因为维护,有些时候是新功能开发.我们的任务总是很重,工作节奏很快,快到我们不能停下脚步去总结. 文件读写有以下几种常用的方法 1.字节读写(InputStream/OutputStream) 2.字符读取(FileReader/FileWriter) 3.

  • java的nio的使用示例分享

    Java NIO(New Input/Output)--新的输入/输出API包--是2002年引入到J2SE 1.4里的.Java NIO的目标是提高Java平台上的I/O密集型任务的性能.过了十年,很多Java开发者还是不知道怎么充分利用NIO,更少的人知道在Java SE 7里引入了更新的输入/输出 API(NIO.2).NIO和NIO.2对于Java平台最大的贡献是提高了Java应用开发中的一个核心组件的性能:输入/输出处理.不过这两个包都不是很好用,并且它们也不是适用于所有的场景.如果能

  • JAVA-NIO之Socket/ServerSocket Channel(详解)

    一.ServerSocketChannel Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样.ServerSocketChannel类在 java.nio.channels包中. 打开 ServerSocketChannel 通过调用 ServerSocketChannel.open() 方法来打开ServerSocketChannel. 关闭 ServerSocketChannel 通过调用Se

  • Java NIO框架Netty简单使用的示例

    之前写了一篇文章:Java 网络IO编程总结(BIO.NIO.AIO均含完整实例代码),介绍了如何使用Java原生IO支持进行网络编程,本文介绍一种更为简单的方式,即Java NIO框架. Netty是业界最流行的NIO框架之一,具有良好的健壮性.功能.性能.可定制性和可扩展性.同时,它提供的十分简单的API,大大简化了我们的网络编程. 同Java IO介绍的文章一样,本文所展示的例子,实现了一个相同的功能. 1.服务端 Server: package com.anxpp.io.calculat

  • Java NIO实例UDP发送接收数据代码分享

    Java的NIO包中,有一个专门用于发送UDP数据包的类:DatagramChannel,UDP是一种无连接的网络协议, 一般用于发送一些准确度要求不太高的数据等. 完整的服务端程序如下: public class StatisticsServer { //每次发送接收的数据包大小 private final int MAX_BUFF_SIZE = 1024 * 10; //服务端监听端口,客户端也通过该端口发送数据 private int port; private DatagramChann

  • Java NIO写大文件对比(win7和mac)

    测试说明 写2G文件,分批次写入,每批次写入128MB: 分别在Win7系统(3G内存,双核,32位,T系列处理器)和MacOS系统(8G内存,四核,64位,i7系列处理器)下运行测试.理论上跟硬盘类型和配置也有关系,这里不再贴出了. 测试代码 package rwbigfile; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.RandomA

  • Java高效读取大文件实例分析

    1.概述 本教程将演示如何用Java高效地读取大文件.Java--回归基础. 2.在内存中读取 读取文件行的标准方式是在内存中读取,Guava和ApacheCommonsIO都提供了如下所示快速读取文件行的方法: Files.readLines(new File(path), Charsets.UTF_8); FileUtils.readLines(new File(path)); 这种方法带来的问题是文件的所有行都被存放在内存中,当文件足够大时很快就会导致程序抛出OutOfMemoryErro

  • Java超详细大文件分片上传代码

    目录 Java 大文件分片上传 首先是交互的控制器 上传文件分片参数接收 大文件分片上传服务类实现 文件分片上传定义公共服务类接口 文件分片上传文件操作接口实现类 OSS阿里云对象存储分片上传实现 京东云对象存储实现 腾讯云对象存储分片上传 分片上传前端代码实现 Java 大文件分片上传 原理:前端通过js读取文件,并将大文件按照指定大小拆分成多个分片,并且计算每个分片的MD5值.前端将每个分片分别上传到后端,后端在接收到文件之后验证当前分片的MD5值是否与上传的MD5一致,待所有分片上传完成之

  • java高效实现大文件拷贝功能

    在java中,FileChannel类中有一些优化方法可以提高传输的效率,其中transferTo( )和 transferFrom( )方法允许将一个通道交叉连接到另一个通道,而不需要通过一个缓冲区来传递数据.只有FileChannel类有这两个方法,因此 channel-to-channel 传输中通道之一必须是 FileChannel.不能在sock通道之间传输数据,不过socket 通道实现WritableByteChannel 和 ReadableByteChannel 接口,因此文件

  • Java内存映射 大文件轻松处理

    前言 内存映射文件(Memory-mapped File),指的是将一段虚拟内存逐字节映射于一个文件,使得应用程序处理文件如同访问主内存(但在真正使用到这些数据前却不会消耗物理内存,也不会有读写磁盘的操作),这要比直接文件读写快几个数量级. 稍微解释一下虚拟内存(很明显,不是物理内存),它是计算机系统内存管理的一种技术.像施了妖法一样使得应用程序认为它拥有连续的可用的内存,实际上呢,它通常是被分隔成多个物理内存的碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换. 内存映射文件主要的

  • Java如何将大文件切割成小文件

    运用Java编写代码将一个大文件切割成指定大小的小文件 思路: 对已知文件进行切割操作 –> 得到多个碎片文件 使用: 1. 1个字节输入流 –> 读取已知文件中的数据 2. 多个字节输出流 –> 生成多个碎片文件 思路补充: 创建一个指定大小的byte数组,将大文件读取到byte数组中,读满一次将byte数组写入一个新的小文件中,如此循环直到将大文件读取完毕 注意:此时最后一个小文件可能不足规定的内存大小,在从大文件读取最后一个byte数组时,可能还没读满byte数组,大文件就读取完毕

  • java如何读取超大文件

    Java NIO读取大文件已经不是什么新鲜事了,但根据网上示例写出的代码来处理具体的业务总会出现一些奇怪的Bug. 针对这种情况,我总结了一些容易出现Bug的经验 1.编码格式 由于是使用NIO读文件通道的方式,拿到的内容都是byte[],在生成String对象时一定要设置与读取文件相同的编码,而不是项目编码. 2.换行符 一般在业务中,多数情况都是读取文本文件,在解析byte[]时发现有换行符时则认为该行已经结束. 在我们写Java程序时,大多数都认为\r\n为一个文本的一行结束,但这个换行符

  • Java实现按行读取大文件

    Java实现按行读取大文件 String file = "F:" + File.separator + "a.txt"; FileInputStream fis = new FileInputStream(file); RandomAccessFile raf = new RandomAccessFile(new File(file),"r"); String s ; while((s =raf.readLine())!=null){ Syste

  • Java中使用内存映射实现大文件上传实例

    在处理大文件时,如果利用普通的FileInputStream 或者FileOutputStream 抑或RandomAccessFile 来进行频繁的读写操作,都将导致进程因频繁读写外存而降低速度.如下为一个对比实验. 复制代码 代码如下: package test; import java.io.BufferedInputStream;  import java.io.FileInputStream;  import java.io.FileNotFoundException;  import

  • Java中用内存映射处理大文件的实现代码

    在处理大文件时,如果利用普通的FileInputStream 或者FileOutputStream 抑或RandomAccessFile 来进行频繁的读写操作,都将导致进程因频繁读写外存而降低速度.如下为一个对比实验. package test; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOExc

随机推荐