`
tooby
  • 浏览: 118206 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Java NIO写大文件比较

    博客分类:
  • Java
 
阅读更多

测试说明

写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轴是耗时秒数)


  1. 显然writeWithMappedByteBuffer方式性能最好,且在硬件配置较高情况下优势越加明显

  2. 在硬件配置较低情况下,writeWithTransferTo比writeWithFileChannel性能稍好

  3. 在硬件配置较高情况下,writeWithTransferTo和writeWithFileChannel的性能基本持平

  4. 此外,注意writeWithMappedByteBuffer方式除了占用JVM堆内存外,还要占用额外的native内存(Direct Byte Buffer内存)

 

内存映射文件使用经验

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

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

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

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

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

  6. 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;
    }

}
分享到:
评论

相关推荐

    java nio 包读取超大数据文件

    相较于传统的Java IO,NIO具有更高的性能和更丰富的功能,尤其适合处理大文件或高并发场景。 #### 二、Java NIO关键组件 Java NIO的核心组件包括: - **Channels**:用于表示IO源或目标的一个连接点。 - **Buffers*...

    java nio 写文件

    本篇将详细探讨Java NIO在写文件方面的应用。 1. **通道(Channels)** 在Java NIO中,数据的读取和写入都是通过通道(Channel)进行的。通道可以理解为连接到I/O设备的桥梁,例如文件、网络套接字等。要使用NIO写...

    java NIO 写文件

    在这个主题中,我们将深入探讨Java NIO如何用于写文件,特别是在处理大数据文件时如何提高性能。 首先,理解Java NIO的基本概念非常重要。NIO中的“N”代表“非阻塞”,这意味着在进行I/O操作时,程序可以继续执行...

    java nio 读文件

    在本篇文章中,我们将深入探讨Java NIO如何读取文件。 一、NIO的基本概念 1. 缓冲区(Buffer):NIO的核心组件,用于存储数据。Java提供了多种Buffer类,如ByteBuffer、CharBuffer、IntBuffer等,分别对应不同数据...

    JAVA NIO 按行读取大文件支持 GB级别-修正版

    本类,是专门为了处理大文件,按行读取开发的类。 采用读文件的缓存 fbb 1024*5 行缓存 bb 256 字节 设计思想: 每次通过nio读取字节到 fbb中 然后对fbb自己中的内容进行行判断即 10 回车 13 行号 0 文件结束 ...

    JAVA NIO 简单PFT 文件服务

    PFT(可能指的是文件传输或处理服务)在本场景中可能是通过JAVA NIO实现的一个功能模块,用于提供文件的上传、下载和列表展示服务。 文件服务是任何应用程序中不可或缺的部分,尤其是在Web环境中。在JAVA NIO中,...

    JAVA NIO 按行读取大文件,支持 GB级别

    本类,是专门为了处理大文件,按行读取开发的类。 采用读文件的缓存 fbb 1024*5 行缓存 bb 256 字节 设计思想: 每次通过nio读取字节到 fbb中 然后对fbb自己中的内容进行行判断即 10 回车 13 行号 0 文件...

    java NIO.zip

    Java NIO支持多种类型的通道,包括文件通道(FileChannel)、套接字通道(SocketChannel)和服务器套接字通道(ServerSocketChannel)等。通道可以同时进行读写操作,并且可以实现异步读写。 2. **缓冲区(Buffers...

    java NIO实例

    `NIOServer.java`和`NIOClient.java`这两个文件很可能是用于演示Java NIO服务器端和客户端的基本操作。下面将详细介绍Java NIO的主要组件和工作原理,并结合这两个文件名推测它们可能包含的内容。 1. **Selector...

    Large-File-Processing-master_javanio_java大文件处理_

    本项目“Large-File-Processing-master_javanio_java大文件处理_”显然专注于通过Java NIO实现大文件处理,下面我们将详细探讨相关的知识点。 1. **Java NIO基础**:NIO的核心组件包括通道(Channels)、缓冲区...

    NIO处理大文件

    为了解决这个问题,Java引入了New Input/Output (NIO) 模型,它提供了非阻塞I/O操作,使得在处理大文件时更加高效。本文将深入探讨如何使用NIO处理大文件,并分析其背后的机制和优势。 1. NIO简介: NIO是Java 1.4...

    nio.rar_FastCopyFile.java_NIO_UseFloatBuffer.java_java nio_文件锁

    这个文件很可能是一个示例程序,演示了如何使用Java NIO进行高效的大文件复制。在传统的Java I/O中,我们通常使用InputStream和OutputStream进行文件复制,而这种方式需要不断读写,造成大量的上下文切换,效率较低...

    Java NIO英文高清原版

    NIO在Java 1.4版本引入,提供了更高效的数据处理和通道通信方式,特别适用于高并发、大数据量的系统。Netty是一个基于NIO的高性能、异步事件驱动的网络应用框架,它简化了网络编程,广泛应用于服务器端应用开发。 ...

    Java用NIO读取文件示范

    简单的用Java的NIO读取文件的程序,给大家参考。

    NIO与零拷贝_javanio_nio和零拷贝_

    Java NIO(New IO)是Java 1.4引入的一个新特性,它是对传统IO模型的重大改进,提供了更高效的数据处理方式。NIO的核心概念包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。它允许多个输入/输出操作...

    Java NIO测试示例

    使用MappedByteBuffer,NIO可以将文件映射到内存,使得文件操作如同操作内存一样快速,特别适合大数据处理。 在实际应用中,Java NIO通常用于高性能的服务器编程,例如在开发聊天服务器、Web服务器或游戏服务器时...

    Java NIO 中英文版

    - Java NIO提供了一组文件系统操作API,例如FileChannel用于读写文件,MappedByteBuffer实现了内存映射文件,可以直接通过内存访问文件内容,提高了读写速度。 4. **缓冲区的分类** - **ByteBuffer**:用于处理...

Global site tag (gtag.js) - Google Analytics