HashSet vs ArrayListにおけるcontains()のパフォーマンス

HashSetとArrayListのcontains()のパフォーマンス

1. 前書き

このクイックガイドでは、we’re going to discuss the performance of the contains() method available in java.util.HashSet and java.util.ArrayList。 どちらも、オブジェクトを保存および操作するためのコレクションです。

HashSetは、一意の要素を格納するためのコレクションです。 HashSet,の詳細については、this linkを確認してください。

ArrayListは、java.util.Listインターフェイスの一般的な実装です。

利用可能なArrayListhereに関する拡張記事があります。

2. HashSet.contains()

内部的には、HashSetの実装はHashMap インスタンスに基づいています。 contains()メソッドはHashMap.containsKey(object)を呼び出します。

ここでは、objectが内部マップにあるかどうかを確認しています。 内部マップは、バケットと呼ばれるノード内にデータを保存します。 各バケットは、hashCode() methodで生成されたハッシュコードに対応します。 したがって、contains()は実際にはhashCode() メソッドを使用してobject’s の場所を見つけています。

次に、ルックアップ時間の複雑さを判断しましょう。 先に進む前に、Big-O notationに精通していることを確認してください。

平均して、the contains() of HashSet runs in O(1) timeGetting the object’s bucket location is a constant time operation. Taking into account possible collisions, the lookup time may rise to log(n) because the internal bucket structure is a TreeMap.

これは、内部バケット構造にLinkedListを使用したJava7からの改善です。 一般に、ハッシュコードの衝突はまれです。 したがって、要素のルックアップの複雑さをO(1)と見なすことができます。

3. ArrayList.c ontains()

内部的には、ArrayList uses the indexOf(object) method to check if the object is in the listindexOf(object)メソッドは配列全体を反復処理し、各要素をequals(object)メソッドと比較します。

複雑さの分析に戻ると、ArrayListcontains()メソッドにはO(n)の時間が必要です。 So the time we spend to find a specific object here depends on the number of items we have in the array.

4. ベンチマークテスト

それでは、パフォーマンスベンチマークテストでJVMをウォームアップしましょう。 JMH(Java Microbenchmark Harness)OpenJDK製品を使用します。 セットアップと実行の詳細については、useful guideを確認してください。

まず、簡単なCollectionsBenchmarkクラスを作成しましょう。

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5)
public class CollectionsBenchmark {

    @State(Scope.Thread)
    public static class MyState {
        private Set employeeSet = new HashSet<>();
        private List employeeList = new ArrayList<>();

        private long iterations = 1000;

        private Employee employee = new Employee(100L, "Harry");

        @Setup(Level.Trial)
        public void setUp() {

            for (long i = 0; i < iterations; i++) {
                employeeSet.add(new Employee(i, "John"));
                employeeList.add(new Employee(i, "John"));
            }

            employeeList.add(employee);
            employeeSet.add(employee);
        }
    }
}

ここでは、HashSetオブジェクトとEmployeeオブジェクトのArrayListを作成して初期化します。

public class Employee {

    private Long id;
    private String name;

    // constructor and getter setters go here
}

両方のコレクションの最後の要素としてemployee = new Employee(100L, “Harry”) instanceを追加します。 そのため、employeeオブジェクトのルックアップ時間を可能な限り最悪の場合についてテストします。

@OutputTimeUnit(TimeUnit.NANOSECONDS)は、ナノ秒単位の結果が必要であることを示します。 この場合、デフォルトの@Warmupの反復回数は5回です。 @BenchmarkModeMode.AverageTimeに設定されています。これは、平均実行時間を計算することに関心があることを意味します。 最初の実行では、iterations = 1000アイテムをコレクションに配置します。

その後、ベンチマークメソッドをCollectionsBenchmarkクラスに追加します。

@Benchmark
public boolean testArrayList(MyState state) {
    return state.employeeList.contains(state.employee);
}

ここでは、employeeListemployeeオブジェクトが含まれているかどうかを確認します。

同様に、employeeSetのよく知られたテストがあります。

@Benchmark
public boolean testHashSet(MyState state) {
    return state.employeeSet.contains(state.employee);
}

最後に、テストを実行できます。

public static void main(String[] args) throws Exception {
    Options options = new OptionsBuilder()
      .include(CollectionsBenchmark.class.getSimpleName())
      .forks(1).build();
    new Runner(options).run();
}

結果は次のとおりです。

Benchmark                           Mode  Cnt     Score     Error  Units
CollectionsBenchmark.testArrayList  avgt   20  4035.646 ± 598.541  ns/op
CollectionsBenchmark.testHashSet    avgt   20     9.456 ±   0.729  ns/op

testArrayListメソッドの平均ルックアップスコアは4035.646 nsであるのに対し、testHashSetのパフォーマンスは平均9.456 nsの方が速いことがはっきりとわかります。

それでは、テストの要素数を増やして、反復= 10.000アイテムで実行してみましょう。

Benchmark                           Mode  Cnt      Score       Error  Units
CollectionsBenchmark.testArrayList  avgt   20  57499.620 ± 11388.645  ns/op
CollectionsBenchmark.testHashSet    avgt   20     11.802 ±     1.164  ns/op

ここでも、HashSetcontains()は、ArrayListよりもパフォーマンスが大幅に向上しています。

5. 結論

この簡単な説明では、HashSetおよびArrayListコレクションのcontains()メソッドのパフォーマンスについて説明します。 JMHベンチマークの助けを借りて、各タイプのコレクションのcontains()のパフォーマンスを示しました。

結論として、the contains() method works faster in HashSet compared to an ArrayList.

いつものように、この記事の完全なコードはover on GitHub projectです。