いろいろミスがあったので、修正。
##やりたいこと
新しく生成された CSV ファイルの、「前回から何が違うか」を調べるコードを書かなければならなかったので、メモ。
当初、片方のファイルの各行を、もう一方のファイルの全行と比較する処理を書いたが、800,000 行くらいの CSV ファイルを処理させたら MacBookPro Retina 15" (core i7) で結構な時間がかかったので、StackOverFlow あたりを這いずり回って調査した結果、下記の方法にたどり着く。下記の方法だと 800,000 行のファイルの比較に 40秒くらい。 1秒くらい。
##実装方針
ファイルをふたつ (A, B) 受け取り、A と B で共通している行を削除する。
このコード例では、A から「B と重複する行」を取り除いた後、残った行(差異のある行)を HashSet として受け取る。
##サンプルコード
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
/**
* Created by kanoloa on 16/03/29.
*
* sample of diff.
*
*/
public class FileDiff {
protected HashSet<String> getUpdatedLines(Path file_A, Path file_B) {
HashSet<String> hash_A = null;
HashSet<String> hash_B = null;
if (Files.exists(file_A)) {
hash_A = getHashSet(file_A);
}
if (Files.exists(file_B)) {
hash_B = getHashSet(file_B);
}
// ファイル B が存在しない場合は、A の全行が残る。
if (hash_A != null && hash_B != null) {
hash_A.removeAll(hash_B);
}
return hash_A;
}
protected HashSet<String> getHashSet(Path file) {
if (Files.notExists(file)) {
return null;
} else {
HashSet<String> hashSet = new HashSet<>();
try (BufferedReader br = Files.newBufferedReader(file)) {
br.lines().forEach(hashSet::add);
return hashSet;
} catch (IOException ioe) {
ioe.printStackTrace();
return null;
}
}
}
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("please specify two files.");
return;
}
Path file_A = Paths.get(args[0]);
Path file_B = Paths.get(args[1]);
if (Files.notExists(file_A)) {
System.out.println("file does not exist: " + file_A.toString());
return;
}
if (Files.notExists(file_B)) {
System.out.println("file does not exist: " + file_B.toString());
return;
}
FileDiff diff = new FileDiff();
long start = System.currentTimeMillis();
HashSet<String> lines = diff.getUpdatedLines(file_A, file_B);
long end = System.currentTimeMillis();
if (lines.size() > 0) {
lines.forEach(System.out::println);
}
System.out.println("found " + lines.size() + " lines.");
System.out.println("took + " + (end - start) + " msec.");
}
}
二つ目の引数として渡されたファイルが存在しない場合は、一つ目の引数の全行が戻される。
一つ目の引数として渡されたファイルが存在しない場合は、null が戻されるので、呼び出し側で対処する。
##実行結果
800,800行 (80万とんで800行) あり、23メガくらいある二つのファイルを比較した場合(差異は全部で 8,000 行ある)の実行時間は 1秒強。
X000252211,,,yes,,,BOLT,BOLT
X000000111,,,yes,,,BOLT,BOLT
(中略)
X000268511,,,yes,,,BOLT,BOLT
X000058211,,,yes,,,BOLT,BOLT
found 8000 lines.
took + 1081 msec.
処理方式を変えてみる
コメントにアドバイスをいただいたので、両方のファイルを HashSet に取るのをやめ、片方は通常のループ処理で回してみる。変更するのは getUpdatedLines() メソッド。
下記のコードでは、一番目の引数として渡される file_A を HashSet に取り、二番目の引数の file_B を forEach で回しながら、読み込んだ一行が HashSet に存在すれば、その行に該当するエントリーを HashSet から取り除く。
protected HashSet<String> getUpdatedLines(Path file_A, Path file_B) {
HashSet<String> hash_A;
if (Files.exists(file_A)) {
hash_A = getHashSet(file_A);
} else {
return null;
}
if (Files.exists(file_B)) {
try (BufferedReader br = Files.newBufferedReader(file_B)) {
br.lines().forEach(hash_A::remove);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
return hash_A;
}
このコードで、5回実行した平均は 710ミリ秒。
一番最初のコードでの 5回実行平均が 1,093ミリ秒だったので、最初のコードの 60% の時間で処理できるようになった。