Javaで複数のソートキーを使う場合の一時ファイル作成方法

java

複数のソートキーが存在するオブジェクトの配列について、固定長のフォーマットに直すことでソートキーを1つにできます。
しかし、フォーマットを直して1つのキーでソートできるようにするよりも、独自Comparatorを定義して複数のキーでソートした方が高速なので、そのようなケースでは独自Comparatorを定義して対応するべきです。

単純にフォーマット変更でコストがかかるだけでなく、ソート処理自体も複数のキーでソートした方が高速です。
複数のキーを1つのキーにまとめてソートする場合は必ずキー全体を見る必要がありますが、複数のキーでソートする場合はキーの一部を見るだけでソートできる場合があるので、複数のキーでソートした方が高速になるのだと思います。

以下、テスト結果です。

【ソースコード】

・SortTest.java

import java.util.ArrayList;
import java.util.Collections;

public class SortTest {

    public static void main(String[] args) {

        // ソート対象のArrayListの作成
        // 一意な10,000,000個のデータを作成
        // それをランダムにソートする
        ArrayList<SortTestRecord> array1 = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            array1.add(new SortTestRecord((i / 100000) % 10,
                                          (i / 10000) % 10,
                                          (i / 1) % 10000));
        }
        Collections.shuffle(array1);
        ArrayList<SortTestRecord> array2 = new ArrayList<>(array1);

        // 3つのソートキーによりソートする場合
        long startTime1 = System.currentTimeMillis();
        SortTestComparator comparator = new SortTestComparator();
        Collections.sort(array1, comparator);
        long endTime1 = System.currentTimeMillis();

        // フォーマット変更して1つのソートキーでソートする場合
        long startTime2 = System.currentTimeMillis();
        ArrayList<String> array2tmp = new ArrayList<>();
        for (int i = 0; i < array2.size(); i++) {
            array2tmp.add(String.format("%02d", array2.get(i).getSortkey1()) +
                          String.format("%03d", array2.get(i).getSortkey2()) +
                          String.format("%05d", array2.get(i).getSortkey3()));
        }
        long startTime2inside = System.currentTimeMillis();
        Collections.sort(array2tmp);
        long endTime2inside = System.currentTimeMillis();
        array2 = new ArrayList<>();
        for (int i = 0; i < array2tmp.size(); i++) {
            array2.add(new SortTestRecord(
                    Integer.parseInt(array2tmp.get(i).substring(0,2)),
                    Integer.parseInt(array2tmp.get(i).substring(2,5)),
                    Integer.parseInt(array2tmp.get(i).substring(5,10))));
        }
        long endTime2 = System.currentTimeMillis();

        // 結果確認
        System.out.println("[サンプル出力]");
        System.out.println("(1つ目、123,457つ目、1,000,000つ目を出力)");
        array1.get(0).print();
        array1.get(123456).print();
        array1.get(999999).print();
        System.out.println();
        System.out.println("[配列の差異箇所出力]");
        int diffCount = 0;
        for (int i = 0; i < 1000000; i++) {
            if (array1.get(i).getSortkey1() != array2.get(i).getSortkey1() ||
                array1.get(i).getSortkey2() != array2.get(i).getSortkey2() ||
                array1.get(i).getSortkey3() != array2.get(i).getSortkey3()) {
                System.out.println((i + 1) + "つ目の要素の内容を出力");
                array1.get(i).print();
                array2.get(i).print();
                diffCount++;
            }
        }
        if (diffCount == 0) {
            System.out.println("差異無し。");
        }
        System.out.println();
        System.out.println("[処理時間]");
        System.out.println("・3つのソートキーによりソートを実施");
        System.out.println((endTime1 - startTime1) + " ms");
        System.out.println("・フォーマット変更を行いソートを実施");
        System.out.println((endTime2 - startTime2) + " ms");
        System.out.println(" (内、ソート部分だけの時間)");
        System.out.println(" " + (endTime2inside - startTime2inside) + " ms");

    }

}

・SortTestRecord.java

public class SortTestRecord {

    private int sortkey1 = 0;
    private int sortkey2 = 0;
    private int sortkey3 = 0;

    // コンストラクタ
    SortTestRecord(int sortkey1, int sortkey2, int sortkey3) {
        this.sortkey1 = sortkey1;
        this.sortkey2 = sortkey2;
        this.sortkey3 = sortkey3;
    }

    // getter
    public int getSortkey1() {
        return this.sortkey1;
    }

    // getter
    public int getSortkey2() {
        return this.sortkey2;
    }

    // getter
    public int getSortkey3() {
        return this.sortkey3;
    }

    // プリント用クラス
    public void print() {
        System.out.println(sortkey1 + "," + sortkey2 + "," + sortkey3);
    }

}

・SortTestComparator.java

import java.util.Comparator;

public class SortTestComparator implements Comparator<SortTestRecord> {

    @Override
    public int compare(SortTestRecord o1, SortTestRecord o2) {
        if (o1.getSortkey1() < o2.getSortkey1()){
            return -1;
        } else if (o1.getSortkey1() > o2.getSortkey1()){
            return 1;
        } else if (o1.getSortkey2() < o2.getSortkey2()){
            return -1;
        } else if (o1.getSortkey2() > o2.getSortkey2()){
            return 1;
        } else {
            return o1.getSortkey3() - o2.getSortkey3();
        }
    }

}

【結果(1回目)】

[サンプル出力]
(1つ目、123,457つ目、1,000,000つ目を出力)
0,0,0
1,2,3456
9,9,9999

[配列の差異箇所出力]
差異無し。

[処理時間]
・3つのソートキーによりソートを実施
2750 ms
・フォーマット変更を行いソートを実施
19105 ms
 (内、ソート部分だけの時間)
 3827 ms

【結果(2回目)】

[サンプル出力]
(1つ目、123,457つ目、1,000,000つ目を出力)
0,0,0
1,2,3456
9,9,9999

[配列の差異箇所出力]
差異無し。

[処理時間]
・3つのソートキーによりソートを実施
1692 ms
・フォーマット変更を行いソートを実施
13627 ms
 (内、ソート部分だけの時間)
 3350 ms

【結果(3回目)】

[サンプル出力]
(1つ目、123,457つ目、1,000,000つ目を出力)
0,0,0
1,2,3456
9,9,9999

[配列の差異箇所出力]
差異無し。

[処理時間]
・3つのソートキーによりソートを実施
2177 ms
・フォーマット変更を行いソートを実施
15333 ms
 (内、ソート部分だけの時間)
 3224 ms

【結果まとめ】


いかがでしたでしょうか。

昔に固定長フォーマットに直してまとめてソートするという手法を実務で見かけたので試してみましたが、この手法はやめた方が良さそうなことがわかりました。
ソート処理自体も複数のキーでソートした方が高速だというのは意外でした。

コメント

タイトルとURLをコピーしました