18
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

二つのファイルを比較して同一の行を削除する方法

Last updated at Posted at 2016-03-29

いろいろミスがあったので、修正。

##やりたいこと

新しく生成された 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% の時間で処理できるようになった。

18
17
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?