Categories: Java

【Java入門】ファイル入出力の性能比較

今回は約100MBの文字コードMS932で書かれたテキストファイルを読み込んで、中身を別ファイルに書き写す際の処理速度について計測してみました。用意したファイルのサイズ(byte数)は114,999,998です。

Java

System.out.println("スタート(総量MB):" + (Runtime.getRuntime().totalMemory()/1024/1204));
long startUsedMemory = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
System.out.println("used(start):" + startUsedMemory);
long startTime = System.currentTimeMillis();

Path input = Paths.get("C:\\workspace\\test\\100MBms932.txt");
Path output = Paths.get("C:\\workspace\\test\\result_sample.txt");

//「読み込み用ファイル」と「書き込み用ファイル」を開く
try(InputStream is = Files.newInputStream(input);
    OutputStream os = Files.newOutputStream(output);){

    //「読み込み用ファイル」からバイトを読み取りを「書き出し用ファイル」にバイトのまま書き出す。
    int len = 0;
    int roopCnt = 0;
    while ((len = is.read()) > 0) {
        os.write(len);
        roopCnt++;
    }
    System.out.println("I/O回数:" + roopCnt);
} catch (IOException e) {
    e.printStackTrace();
}

long endTime = System.currentTimeMillis();
System.out.println("ゴール(総量MB):" + (Runtime.getRuntime().totalMemory()/1024/1204));
long endUsedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("used(end):" + endUsedMemory);
long usedMem = endUsedMemory - startUsedMemory;
System.out.println("実行時間:" + (endTime - startTime) + "ms");
System.out.println("使用メモリ増加量(総量MB):" + (usedMem /1024/1024));

上記のコードを5回試した結果は次のとおりです。

項目1回目2回目3回目4回目5回目
スタート(総量MB)104104104104104
used(start)20449202044920204492020449202044920
I/O回数114999998114999998114999998114999998114999998
ゴール(総量MB)104104104104104
used(end)20449202044920204492020449202044920
実行時間745564ms742157ms749027ms747909ms743747ms
使用メモリ増加量(総量MB)00000
この結果から分かったこと
  • I/O回数はファイル容量(byte)である114999998と同じになっている
  • 実行時間は平均で約12分かかっている
  • 使用メモリ増加量(総量MB)は増えていない

パターン②:BufferedInputStream有りかつ、バッファサイズ指定無し

BufferedInputStreamでラップしてそのまま入出力するパターンです。

Java

System.out.println("スタート(総量MB):" + (Runtime.getRuntime().totalMemory()/1024/1204));
long startUsedMemory = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
System.out.println("used(start):" + startUsedMemory);
long startTime = System.currentTimeMillis();

Path input = Paths.get("C:\\workspace\\test\\100MBms932.txt");
Path output = Paths.get("C:\\workspace\\test\\result_sample.txt");

//「読み込み用ファイル」と「書き込み用ファイル」を開く
try(InputStream is = Files.newInputStream(input);
    BufferedInputStream bis = new BufferedInputStream(is);
    OutputStream os = Files.newOutputStream(output);
    BufferedOutputStream bos = new BufferedOutputStream(os);){

    //「読み込み用ファイル」からバイトを読み取りを「書き出し用ファイル」にバイトのまま書き出す。
    int len = 0;
    int roopCnt = 0;
    while ((len = bis.read()) > 0) {
        bos.write(len);
        roopCnt++;
    }
    System.out.println("I/O回数:" + roopCnt);
} catch (IOException e) {
    e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("ゴール(総量MB):" + (Runtime.getRuntime().totalMemory()/1024/1204));
long endUsedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("used(end):" + endUsedMemory);
long usedMem = endUsedMemory - startUsedMemory;
System.out.println("実行時間:" + (endTime - startTime) + "ms");
System.out.println("使用メモリ増加量(総量MB):" + (usedMem /1024/1024));

上記のコードを5回試した結果は次のとおりです。

項目1回目2回目3回目4回目5回目
スタート(総量MB)104104104104104
used(start)20449202044920204492020449202044920
I/O回数114999998114999998114999998114999998114999998
ゴール(総量MB)104104104104104
used(end)27265122726512272656027265602726512
実行時間5264ms4907ms5046ms5593ms5413ms
使用メモリ増加量(総量MB)00000
この結果から分かったこと
  • I/O回数はファイル容量(byte)である114999998と同じになっている
  • 実行時間は平均で約5秒かかっている
  • used(end)とused(start)に差はあるものの、1MBに満たないため、使用メモリ増加量(総量MB)は0になっている

パターン③:BufferedInputStream無しかつ、バッファサイズ指定有り

BufferedInputStreamでラップせずに、バッファサイズを指定して実行するパターンです。

Java

System.out.println("スタート(総量MB):" + (Runtime.getRuntime().totalMemory()/1024/1204));
long startUsedMemory = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
System.out.println("used(start):" + startUsedMemory);
long startTime = System.currentTimeMillis();

Path input = Paths.get("C:\\workspace\\test\\100MBms932.txt");
Path output = Paths.get("C:\\workspace\\test\\result_sample.txt");

//「読み込み用ファイル」と「書き込み用ファイル」を開く
try(InputStream is = Files.newInputStream(input);
    OutputStream os = Files.newOutputStream(output);){

    //「読み込み用ファイル」からバイトを読み取りを「書き出し用ファイル」にバイトのまま書き出す。
    byte[] buf = new byte[1024];
    int len = 0;

    int roopCnt = 0;
    while ((len = is.read(buf)) > 0) {
        os.write(buf, 0, len);
        roopCnt++;
    }
    System.out.println("I/O回数:" + roopCnt);
} catch (IOException e) {
    e.printStackTrace();
}

long endTime = System.currentTimeMillis();
System.out.println("ゴール(総量MB):" + (Runtime.getRuntime().totalMemory()/1024/1204));
long endUsedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("used(end):" + endUsedMemory);
long usedMem = endUsedMemory - startUsedMemory;
System.out.println("実行時間:" + (endTime - startTime) + "ms");
System.out.println("使用メモリ増加量(総量MB):" + (usedMem /1024/1024));

上記のコードを5回試した結果は次のとおりです。

項目1回目2回目3回目4回目5回目
スタート(総量MB)104104104104104
used(start)20449202044920204492020449202044920
I/O回数112305112305112305112305112305
ゴール(総量MB)104104104104104
used(end)20449202044920204492020449202044920
実行時間1253ms1355ms1545ms1413ms1527ms
使用メモリ増加量(総量MB)00000
この結果から分かったこと
  • I/O回数は112305になっている。これはファイルサイズのbyte数114999998を読み込みバッファとして指定した1024で割った値を切り上げるとこの値になる。
  • 実行時間は平均で約1.2秒かかっている
  • 使用メモリ増加量(総量MB)は増えていない

パターン④:BufferedInputStream有りかつ、バッファサイズ指定有り

BufferedInputStreamでラップして、さらにバッファサイズも指定して実行するパターンです。

Java

System.out.println("スタート(総量MB):" + (Runtime.getRuntime().totalMemory()/1024/1204));
long startUsedMemory = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
System.out.println("used(start):" + startUsedMemory);
long startTime = System.currentTimeMillis();

Path input = Paths.get("C:\\workspace\\test\\100MBms932.txt");
Path output = Paths.get("C:\\workspace\\test\\result_sample.txt");

//「読み込み用ファイル」と「書き込み用ファイル」を開く
try(InputStream is = Files.newInputStream(input);
    BufferedInputStream bis = new BufferedInputStream(is);
    OutputStream os = Files.newOutputStream(output);
    BufferedOutputStream bos = new BufferedOutputStream(os);){

    //「読み込み用ファイル」からバイトを読み取りを「書き出し用ファイル」にバイトのまま書き出す。
    int len = 0;
    byte[] buf = new byte[1024];

    int roopCnt = 0;
    while ((len = bis.read(buf)) > 0) {
        bos.write(buf,0,len);
        roopCnt++;
    }
    System.out.println("I/O回数:" + roopCnt);
} catch (IOException e) {
    e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("ゴール(総量MB):" + (Runtime.getRuntime().totalMemory()/1024/1204));
long endUsedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("used(end):" + endUsedMemory);
long usedMem = endUsedMemory - startUsedMemory;
System.out.println("実行時間:" + (endTime - startTime) + "ms");
System.out.println("使用メモリ増加量(総量MB):" + (usedMem /1024/1024));

上記のコードを5回試した結果は次のとおりです。

項目1回目2回目3回目4回目5回目
スタート(総量MB)104104104104104
used(start)20449202044920204492020449202044920
I/O回数112305112305112305112305112305
ゴール(総量MB)104104104104104
used(end)27265762726576272657627265762726576
実行時間505ms535ms515ms717ms611ms
使用メモリ増加量(総量MB)00000
この結果から分かったこと
  • I/O回数は112305になっている。これはファイルサイズのbyte数114999998を読み込みバッファとして指定した1024で割った値を切り上げるとこの値になる。
  • 実行時間は平均で約0.5秒かかっている
  • used(end)とused(start)に差はあるものの、1MBに満たないため、使用メモリ増加量(総量MB)は0になっている

結論

この結果から分かったこと

実行速度について

  • パターン①よりパターン②の方が実行速度が早い
  • パターン②よりパターン③の方が実行速度が早い
  • パターン③よりパターン④の方が実行速度が早い

I/O回数について

  • バッファサイズを大きく指定すればするほどI/O回数が減少する。(但し、メモリ使用量が増加する)

総論

  •  つまり、パターン4の状態でメモリ使用量を観察しながらバッファサイズをチューニングする方法が一番パフォーマンスが良くなる

以上で記事の解説はお終い!

もっとJavaやSpringを勉強したい方にはUdemyがオススメ!同僚に差をつけよう!

issiki_wp