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();
    }

  }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章