`

Java NIO(内存映射文件) 与 传统IO write 性能测试

 
阅读更多
package io;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Java NIO(内存映射文件) 与 传统IO write 性能测试
 * 
 * GenerateIntArray生成count个数组,每个数组里面有size个整数,然后将生成的数据写入文件.
 * 整数通过Random.nextInt(total)来生成.
 * 初始化时,数组数据都是0,调用refreshDataArr()可生成新数据填充数组
 * 
 * 生成数据的方法有:
 * (1) refreshDataArr() 使用双重循环生成数据.
 * (2) refreshDataArr_M() 使用count个线程,每个线程中生成size个数据
 * 
 * 将数据写入文件的方法:
 * 
 * (1)writeData2File(File f) 
 *    循环调用RandomAccessFile.writeInt(int)方法,每个数据都写一次,一共写了count*size次
 * (2)writeData2File_B(File f) 
 *    该方法先将coutn个整数转换成字符数组,并将count个字节数组按顺序组合到一个大的字节数组中
 *    然后调用RandomAccessFile.write(byte[] byteArr);方法一次性写入size个整数.
 * (3)writeData2File_M(File f)
 *    该方法启动count个线程,每个线程使用writeData2File_B中的方法,一次性写入size个整数
 *    
 * (4)writeData2FileNIO(File f) NIO 通过Channel 和 Buffer的方式来写文件
 * (5)writeData2FileMap(File f) NIO中通过内存映射文件写 文件
 * (6)writeData2FileNIO_D(File f) NIO中,通过Channel 和 Buffer的方式来写文件,
 *    其中Buffer使用直接分配空间方式allocateDirect分配空间
 * 
 * 由下面的测试结果可知,通过RandomAccessFile.write(byte[] byteArr)写入字节数组的方式一次性写入
 * size个整数时写入速度最快,比一次写入一个整数快了很多.多线程写入时性能提升不大,只有在count不大,但是
 * size巨大时多线程方法写入有一些提升,因为生成count个线程并且要进行线程调度也需要消耗一些系统资源.
 * 多线程方式生成数据,也只有在size特别大(100000),count不是很大时有速度提升.
 * 可能因为测试机器CPU是单核的,对于多线程性能提升不大.
 * 
 * 数据量非常小时,使用单线程一次生成一个数据,以及一次写入一个整数时速度快
 * (因为使用写字节数组方式一次性写入size个整数时需要将整数转换成字节数组,这有一定的开销).
 * 
 * NIO方式使用allocateDirect直接分配buffer空间比传统方式分配buffer空间的性能提升明显.
 * 
 * 使用内存映射文件性能也有提升.
 *
 * 
 * 下面是部分测试数据(耗时单位是 耗时(纳秒)/100000)
 * 
count = 1000, size = 10000 

正在生成数据,请稍后...
refreshDataArr 生成数据成功, 耗时:9977

正在写入数据,请稍后...
数据已写入文件D:\D\test_data.dat\test_data.dat
writeData2File_B写入数据耗时:13006

正在写入数据,请稍后...
数据已写入文件D:\D\test_data.dat\test_data.dat
writeData2File 写入数据耗时:664187

正在写入数据,请稍后...
数据已写入文件D:\D\test_data.dat\test_data.dat
writeData2File_M写入数据耗时:4210

正在写入数据,请稍后...
数据已写入文件D:\D\test_data.dat\test_data.dat
writeData2FileNIO 写入数据耗时:48942

正在写入数据,请稍后...
数据已写入文件D:\D\test_data.dat\test_data.dat
writeData2FileNIO_D 写入数据耗时:15509

正在写入数据,请稍后...
数据已写入文件D:\D\test_data.dat\test_data.dat
writeData2FileMap 写入数据耗时:27390


 *
 */
public class GenerateIntArray
{
  private int     count   = 1000;             // 数组的个数,
  private int     size    = 10;               // 每个数组的元素个数
  private int[][] dataArr;
  private Random  random  = new Random(1000);

  public GenerateIntArray()
  {
    dataArr = new int[count][size];
  }

  public GenerateIntArray(int count, int size)
  {
    this.count = count;
    this.size = size;
    this.dataArr = new int[count][size];
  }

  public int[][] getDataArr()
  {
    return dataArr;
  }

  /**
   * 刷新数组中的数据
   */
  public int[][] refreshDataArr()
  {
    int total = count * size;

    for (int i = 0; i < count; i++)
    {
      for (int j = 0; j < size; j++)
      {
        dataArr[i][j] = random.nextInt(total);
      }
    }

    return dataArr;
  }
  
  private class getIntTask implements Runnable
  {
    private int arrIndex;
    private CountDownLatch latch;
    
    public getIntTask(int arrIndex,CountDownLatch latch)
    {
      this.arrIndex = arrIndex;
      this.latch = latch;
    }
    
    @Override
    public void run()
    {
      int total = count * size;
      for(int i = 0;i < size;i++)
      {
        dataArr[arrIndex][i] = random.nextInt(total);
      }
      latch.countDown();
      
    }
    
  }
  
  

  /**
   * 写数组数据到文件,如果文件已经存在,则会被删除,然后重新生成文件
   * 每次写入数组中的一个数据
   * @param f
   * @throws IOException
   */
  public void writeData2File(File f) throws IOException
  {
    if (null != f && f.exists())
    {
      f.delete();
    }
    RandomAccessFile rf = new RandomAccessFile(f, "rw");
    rf.seek(0);// 每次都从头开始些文件
    for (int i = 0; i < count; i++)
    {
      for (int j = 0; j < size; j++)
      {
        rf.writeInt(dataArr[i][j]);
      }
    }
    
    rf.close();
  }
  
  public void writeData2FileNIO(File f) throws IOException
  {
    if (null != f && f.exists())
    {
      f.delete();
    }
    
  //先生成一个固定尺寸的文件,能够保存所有整数的
    RandomAccessFile rf = new RandomAccessFile(f, "rw");
    rf.setLength(count * size * 4 ); //设置尺寸(一个整型占4字节)
    rf.seek(0);
    //rf.write(1024);//随便写一个,以便保存文件
    rf.close();
    
    rf = new RandomAccessFile(f, "rw");
    FileChannel fc = rf.getChannel();
    ByteBuffer buffer = ByteBuffer.allocate(size * 4);
    
    for (int i = 0; i < count; i++)
    {
      for (int j = 0; j < size; j++)
      {
        //buffer.put(int2byte(dataArr[i][j]));
        buffer.putInt(dataArr[i][j]);
      }
      buffer.rewind();
      fc.write(buffer);
      buffer.rewind();
    }
    
    rf.close();
    fc.close();
  }
  
  public void writeData2FileMap(File f) throws IOException
  {
    if (null != f && f.exists())
    {
      f.delete();
    }
    
  //先生成一个固定尺寸的文件,能够保存所有整数的
    RandomAccessFile rf = new RandomAccessFile(f, "rw");
    rf.setLength(count * size * 4 ); //设置尺寸(一个整型占4字节)
    rf.seek(0);
    //rf.write(1024);//随便写一个,以便保存文件
    rf.close();
    
    rf = new RandomAccessFile(f, "rw");
    FileChannel fc = rf.getChannel();
    
    int iSize = 4 * size;
    
    for (int i = 0; i < count; i++)
    {
      int position = i * size;
      ByteBuffer buffer = fc.map(MapMode.READ_WRITE,position,iSize);
      for (int j = 0; j < size; j++)
      {
        //buffer.put(int2byte(dataArr[i][j]));
        buffer.putInt(dataArr[i][j]);
      }
      buffer.rewind();
      fc.write(buffer);
      buffer.rewind();
    }
    
    rf.close();
    fc.close();
  }
  
  public void writeData2FileNIO_D(File f) throws IOException
  {
    if (null != f && f.exists())
    {
      f.delete();
    }
    RandomAccessFile rf = new RandomAccessFile(f, "rw");
    FileChannel fc = rf.getChannel();
    ByteBuffer buffer = ByteBuffer.allocateDirect(size * 4);
    
    for (int i = 0; i < count; i++)
    {
      for (int j = 0; j < size; j++)
      {
        buffer.putInt(dataArr[i][j]);
      }
      buffer.rewind();
      fc.write(buffer);
      buffer.rewind();
    }
    
    rf.close();
    fc.close();
  }

  /**
   * 写数据时,现将整数转换成字节数据保存,然后一次性写入字节数组到文件,
   * 避免频繁写入.
   * @param f
   * @throws IOException
   */
  public void writeData2File_B(File f) throws IOException
  {
    if (null != f && f.exists())
    {
      f.delete();
    }
    RandomAccessFile rf = new RandomAccessFile(f, "rw");
    rf.seek(0);// 每次都从头开始些文件
    for (int i = 0; i < count; i++)
    {
      byte[] byteArr = new byte[4 * size];
      int iTmp = 0;
      for (int j = 0; j < size; j++)
      {
        byte[] tmpBytes = int2byte(dataArr[i][j]);
        byteArr[iTmp++] = tmpBytes[3];
        byteArr[iTmp++] = tmpBytes[2];
        byteArr[iTmp++] = tmpBytes[1];
        byteArr[iTmp++] = tmpBytes[0];
      }
      rf.write(byteArr);
    }
    rf.close();
  }
  
  /**
   * 多线程方式同时同时写文件
   * @param f
   * @throws IOException
   */
  
  class WriteTask implements Runnable
  {

    private File f;
    private int dataIndex;
    
    public WriteTask(File f,int dataIndex)
    {
      this.f = f;
      this.dataIndex = dataIndex;
    }
    
    @Override
    public void run()
    {
      try
      {
        RandomAccessFile rf = new RandomAccessFile(f, "rw");
        rf.skipBytes(dataIndex * size * 4 );
        byte[] byteArr = new byte[4 * size];
        int iTmp = 0;
        for (int j = 0; j < size; j++)
        {
          byte[] tmpBytes = int2byte(dataArr[dataIndex][j]);
          byteArr[iTmp++] = tmpBytes[3];
          byteArr[iTmp++] = tmpBytes[2];
          byteArr[iTmp++] = tmpBytes[1];
          byteArr[iTmp++] = tmpBytes[0];
        }
        rf.write(byteArr);
        rf.close();
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
    }
  }
  
  public void writeData2File_M(File f) throws IOException
  {
    if (null != f && f.exists())
    {
      f.delete();
    }
    
    //先生成一个固定尺寸的文件,能够保存所有整数的
    RandomAccessFile rf = new RandomAccessFile(f, "rw");
    rf.setLength(count * size * 4 ); //设置尺寸(一个整型占4字节)
    rf.seek(0);
    //rf.write(1024);//随便写一个,以便保存文件
    rf.close();
    
    ExecutorService exec = Executors.newCachedThreadPool();
    for(int i=0;i<count;i++)
    {
      exec.execute(new WriteTask(f,i));
    }
    exec.shutdown();
  }

  // 将二进制数转换成字节数组
  private byte[] int2byte(int res)
  {
    byte[] targets = new byte[4];

    targets[0] = (byte) (res & 0xff);// 最低位
    targets[1] = (byte) ((res >> 8) & 0xff);// 次低位
    targets[2] = (byte) ((res >> 16) & 0xff);// 次高位
    targets[3] = (byte) (res >>> 24);// 最高位,无符号右移
    return targets;
  }

  public static void main(String[] args)
  {
    int count = 1000;
    int size = 10000;
    boolean bPrintData = false; //是否打印生成的数组,当数据量大是不打印,只在小数据量时打印以便测试
    
    System.out.printf("count = %d, size = %d \n\n",count,size);
    
    GenerateIntArray generator = new GenerateIntArray(count, size);

    File f;

    try
    {
      f = new File("D:\\D\\test_data.dat");
      
      System.out.println("正在生成数据,请稍后...");
      long startTmie = System.nanoTime();
      generator.refreshDataArr();
      long totalTime = (System.nanoTime() - startTmie)/ 100000;
      System.out.println("refreshDataArr 生成数据成功, 耗时:" + totalTime);
      System.out.println();
      
      System.out.println("正在写入数据,请稍后...");
      startTmie = System.nanoTime();
      generator.writeData2File_B(f);
      totalTime = (System.nanoTime() - startTmie)/ 100000;;
      System.out.println("数据已写入文件" + f.getPath() + File.separator + f.getName());
      System.out.println("writeData2File_B写入数据耗时:" + totalTime);
      System.out.println();
      
      System.out.println("正在写入数据,请稍后...");
      startTmie = System.nanoTime();
      //generator.writeData2File(f);//耗时太长
      totalTime = (System.nanoTime() - startTmie)/ 100000;
      System.out.println("数据已写入文件" + f.getPath() + File.separator + f.getName());
      System.out.println("writeData2File 写入数据耗时:" + totalTime);
      System.out.println();
      
      System.out.println("正在写入数据,请稍后...");
      startTmie = System.nanoTime();
      generator.writeData2File_M(f);
      totalTime = (System.nanoTime() - startTmie)/ 100000;;
      System.out.println("数据已写入文件" + f.getPath() + File.separator + f.getName());
      System.out.println("writeData2File_M写入数据耗时:" + totalTime);
      System.out.println();
      
      System.out.println("正在写入数据,请稍后...");
      startTmie = System.nanoTime();
      generator.writeData2FileNIO(f);
      totalTime = (System.nanoTime() - startTmie)/ 100000;
      System.out.println("数据已写入文件" + f.getPath() + File.separator + f.getName());
      System.out.println("writeData2FileNIO 写入数据耗时:" + totalTime);
      System.out.println();
      
      System.out.println("正在写入数据,请稍后...");
      startTmie = System.nanoTime();
      generator.writeData2FileNIO_D(f);
      totalTime = (System.nanoTime() - startTmie)/ 100000;
      System.out.println("数据已写入文件" + f.getPath() + File.separator + f.getName());
      System.out.println("writeData2FileNIO_D 写入数据耗时:" + totalTime);
      System.out.println();
      
      System.out.println("正在写入数据,请稍后...");
      startTmie = System.nanoTime();
      generator.writeData2FileMap(f);
      totalTime = (System.nanoTime() - startTmie)/ 100000;
      System.out.println("数据已写入文件" + f.getPath() + File.separator + f.getName());
      System.out.println("writeData2FileMap 写入数据耗时:" + totalTime);
      System.out.println();
      
      if(bPrintData)
      {
        System.out.println("原始数组中生成的数据...");
        int[][] intArr = generator.getDataArr();
        for (int i = 0; i < count; i++)
        {
          for (int j = 0; j < size; j++)
          {
            System.out.printf("%d ", intArr[i][j]);
          }
          System.out.println();
        }
        
        System.out.println("从文件中读取出来的数据...");
        RandomAccessFile rf = new RandomAccessFile(f, "r");
        rf.seek(0);
        int iline = 1;
        while (true)
        {
          System.out.printf("%d ",rf.readInt());
          if(iline % size == 0)
          {
            System.out.println();
          }
          iline ++;
          // 判断已经到文件尾了
          if (rf.getFilePointer() >= rf.length() - 1)
          {
            break;
          }
          
        }
        rf.close();
      }

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

  }

}

 

分享到:
评论

相关推荐

    java nio 写文件

    Java NIO(New IO)是Java 1.4版本引入的一个新特性,它为Java提供了新的I/O操作方式,与传统的Java IO相比,NIO具有更高效、更灵活的特性,尤其是在处理大量并发I/O操作时。本篇将详细探讨Java NIO在写文件方面的...

    java NIO 写文件

    Java NIO(New IO)是Java 1.4版本引入的一个新模块,它提供了一种新的方式来处理I/O操作,相比传统的IO流,NIO提供了更高效、更灵活的数据读写方式。在这个主题中,我们将深入探讨Java NIO如何用于写文件,特别是在...

    commons-mmf.rar_java nio_java共享内存_共享内存

    Java NIO(New Input/Output)是Java标准库中提供的一种替代传统IO的高效I/O模型,它引入了通道(Channel)和缓冲区(Buffer)的概念,极大地优化了数据读写操作。在处理大流量实时业务系统时,NIO的优势尤为突出,...

    Java NIO原理分析及代码实例

    Java NIO(New IO)是Java 1.4版本引入的一个新API,全称为Non-blocking Input/Output,它提供了一种不同于传统IO的编程模型,传统IO基于块I/O,而NIO则基于通道(Channel)和缓冲区(Buffer)进行数据传输。NIO的...

    java NIO原理和使用

    1. **非阻塞模式**:与传统的阻塞 I/O 相比,NIO 支持非阻塞模式,这意味着一个线程可以同时处理多个通道。 2. **缓冲区支持**:NIO 使用缓冲区来进行数据的读写操作,这提高了 I/O 操作的性能。 3. **散列和聚合**...

    java 深入理解内存映射文件原理

    在Java中,使用内存映射文件通常通过java.nio包中的MappedByteBuffer类实现。通过FileChannel的map()方法,可以将文件映射到内存,从而提高大文件处理的性能。例如,以下代码展示了如何使用内存映射文件读取10MB的...

    Java NIO系列教程

    Java NIO (New IO) 是从 Java 1.4 开始提供的一种新的 I/O 处理方式,旨在改进传统 Java IO API 的性能并引入更高效的数据处理机制。Java NIO 主要包括三大核心组成部分:Channels、Buffers 和 Selectors。 #### ...

    Java nio源码

    Java NIO还提供了内存映射文件,允许将文件直接映射到内存,从而提高文件读写的性能。这种方式下,操作系统负责文件和内存之间的数据传输,减少了Java虚拟机的干预。 6. **文件系统操作** Java NIO提供了...

    Java NIO教程文档

    Java NIO(New IO),从 Java 1.4 开始引入,是 Java 标准 IO API 的一个补充,提供了与标准 IO 不同的工作方式。Java NIO 的主要特性包括: 1. **基于通道(Channel)和缓冲区(Buffer)的操作**:与标准 IO 基于字节...

    Java语言基础教程-Java NIO流篇3

    与传统的IO流不同,NIO的文件通道可以实现直接内存到内存的传输,减少了系统调用,提高了性能。以下是文件通道的主要特性: 1. **缓冲区操作**:FileChannel与Buffer类紧密合作,数据读写都是通过缓冲区进行,减少...

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

    内存映射文件是通过Java的NIO(New Input/Output)包中的FileChannel类来实现的。FileChannel提供了map()方法,可以将文件的一部分或全部映射到Java虚拟机的内存中,形成一个MappedByteBuffer对象。这样,对...

    JAVA IO-NIO 详解

    其中,NIO(New IO)是Java 1.4版本引入的一种新的IO处理方式,相较于传统的阻塞IO,NIO提供了更高的效率和灵活性。 #### 二、传统IO与NIO的区别 **1. 阻塞与非阻塞** - **传统IO**: 阻塞式操作,当进行读写操作...

    Java_NIO与IO的区别和比较.doc

    NIO与传统的IO(Old IO)在设计模式、工作原理以及使用方式上存在显著的差异。 1. **Buffer**: 在传统的IO模型中,数据通常在流之间直接读写,而NIO引入了Buffer的概念。Buffer是一个固定大小的存储区,用于临时...

    Java IO 基础操作(文件读写和移动)

    `java.nio`包下的`FileChannel`和`Files`类提供了更高级的文件操作,如映射内存到文件(MMap),以及异步文件操作。 例如,使用`Files`类移动文件: ```java Path sourcePath = Paths.get("sourceFile.txt"); Path...

    java nio.doc

    Java NIO (New I/O) 是 Java 平台中用于处理输入/输出操作的一组高级 API,它首次出现在 JDK 1.4 中,旨在解决传统 Java IO 包(如 `java.io`)在处理大量并发连接时的性能瓶颈问题。NIO 的核心特性包括缓冲区、通道...

    JAVA-NIO程序设计完整实例

    NIO与传统的BIO(Blocking I/O)模型相比,其核心在于它允许程序同时处理多个输入和输出流,提高了并发性能。本实例将深入探讨NIO的基本概念、关键组件以及如何在实际编程中应用NIO。 ### 1. NIO基本概念 - **通道...

    NIO复制文件

    在Java编程中,NIO(New IO)是一个重要的特性,它提供了一种不同于传统IO模型的I/O操作方式。NIO具有非阻塞、多路复用等优势,尤其适用于高性能、高并发的网络应用。本节我们将深入探讨如何利用NIO进行文件复制,并...

Global site tag (gtag.js) - Google Analytics