63


9

List <String>に一意の文字列が含まれているかどうかを確認する最も速い方法

基本的に、約1,000,000個の文字列があります。リクエストごとに、文字列がリストに属しているかどうかを確認する必要があります。

パフォーマンスが心配なので、最善の方法は何ですか? ArrayList? ハッシュ?

10 Answer


93


あなたの最善の策はhttps://docs.oracle.com/javase/6/docs/api/java/util/HashSet.html [HashSet]を使用し、` containsを介してセットに文字列が存在するかどうかを確認することです() `メソッド。 HashSetは、Objectメソッドの `hashCode()`と `equals()`を使用して高速にアクセスできるように構築されています。 `HashSet`のJavadocの状態:

_ このクラスは、基本操作(追加、削除、包含、およびサイズ)に対して一定時間のパフォーマンスを提供します。 _

HashSet http://en.wikipedia.org/wiki/Hash_table [ハッシュバケットにオブジェクトを格納]は、 `hashCode`メソッドによって返される値によって、オブジェクトがどのバケットに格納されるかを決定します。 このようにして、 `equals()`メソッドを介して `HashSet`が実行する必要のある等価性チェックの量は、同じハッシュバケット内の他のオブジェクトのみに削減されます。

HashSetとHashMapを効果的に使用するには、https://docs.oracle.com/javase/6/docs/api/java/lang/Object.html [javadoc ]。 `java.lang.String`の場合、これらのメソッドは既に実装されています。


11


一般に、HashSetは、ArrayListのように各要素を調べて比較する必要がないため、パフォーマンスが向上しますが、通常、ハッシュコードが等しい場合、多くても数個の要素を比較します。

ただし、1M文字列の場合、hashSetのパフォーマンスは依然として最適ではない可能性があります。 キャッシュミスが多いと、セットの検索が遅くなります。 すべての文字列が等しく発生する可能性がある場合、これは避けられません。 ただし、一部の文字列が他の文字列よりも頻繁に要求される場合は、共通の文字列を小さなhashSetに配置し、大きなセットをチェックする前に最初にチェックできます。 小さなハッシュセットは、キャッシュに収まるサイズにする必要があります(例: 最大で数百K)。 小さいハッシュセットへのヒットは非常に高速になり、大きいハッシュセットへのヒットはメモリ帯域幅によって制限された速度で進行します。


8


先に進む前に、これを考慮してください:なぜパフォーマンスが心配なのですか? このチェックはどのくらいの頻度で呼び出されますか?

可能な解決策として:

  • リストが既にソートされている場合は、使用できます java.util.TreeSet`と同じパフォーマンス特性を提供する java.util.Collections.binarySearch`。

  • それ以外の場合は、パフォーマンスとして「java.util.HashSet」を使用できます O(1)の特性。 まだ計算されていない文字列のハッシュコードの計算は、m = `string.length()`のO(m)操作であることに注意してください。 また、ハッシュテーブルは、所定の負荷係数、つまり ハッシュテーブルはプレーンリストよりも多くのメモリを使用します。 HashSetが使用するデフォルトの負荷係数は0.75です。つまり、内部的に1e6オブジェクトのHashSetは1.3e6エントリの配列を使用します。

  • HashSetが機能しない場合(例: たくさんあるから Trieの使用を検討するよりも、メモリが不足しているため、または多くの挿入があるため、ハッシュ衝突)。 トライでのルックアップは、m = `string.length()`であるO(m)の最悪の複雑さを持ちます。 トライには、あなたに役立つかもしれないいくつかの特別な利点もあります:例えば、それはあなたに検索文字列の_最も近い適合_を与えることができます。 ただし、最良のコードはコードではないため、メリットがコストを上回る場合にのみ、Trieの実装をロールバックしてください。

  • より複雑なクエリが必要な場合は、データベースの使用を検討してください。 一致 部分文字列または正規表現の場合。


5


Set`を使用します。ほとんどの場合、 HashSet`で問題ありません。


2


膨大な数の文字列があるので、すぐにhttp://en.wikipedia.org/wiki/Trie[Trie]を思い浮かべます。 より限定された文字セット(文字など)や、多くの文字列の先頭が重複する場合に、より適切に機能します。


2


ここで演習を実行したことが私の結果です。

private static final int TEST_CYCLES = 4000;
private static final long RAND_ELEMENT_COUNT = 1000000l;
private static final int RAND_STR_LEN = 20;
//Mean time
/*
Array list:18.55425
Array list not contains:17.113
Hash set:5.0E-4
Hash set not contains:7.5E-4
*/

数字はそれを物語っていると思います。 ハッシュセットのルックアップ時間は、wayyyyより高速です。


1


このような大量の文字列がある場合、データベースを使用するのが最善の機会です。 MySQLを探します。


1


おそらくこれはあなたのケースには必要ではありませんが、スペース効率の高い確率的アルゴリズムがあることを知っておくと便利だと思います。 たとえば、https://en.wikipedia.org/wiki/Bloom_filter [Bloom filter]。


0


文字列だけでなく、一意のアイテムが必要な場合には* Set *を使用できます。

アイテムのタイプがプリミティブまたはラッパーの場合、気にする必要はありません。 ただし、クラスの場合は、2つのメソッドをオーバーライドする必要があります。

  1. ハッシュコード()

  2. equals()


0


オブジェクトがリスト/セットに含まれているかどうかを確認すると同時に、リスト/セットを並べ替えたい場合があります。 列挙またはイテレータを使用せずにオブジェクトを簡単に取得したい場合は、「ArrayList」と「HashMap」の両方を使用することを検討してください。 リストは地図に支えられています。

最近やった仕事の例:

public class NodeKey implements Serializable, Cloneable{
private static final long serialVersionUID = -634779076519943311L;

private NodeKey parent;
private List children = new ArrayList();
private Map childrenToListMap = new HashMap();

public NodeKey() {}

public NodeKey(Collection<? extends K> c){
    List childHierarchy = new ArrayList(c);
    K childLevel0 = childHierarchy.remove(0);

    if(!childrenToListMap.containsKey(childLevel0)){
        children.add(childLevel0);
        childrenToListMap.put(childLevel0, children.size()-1);
    }

    ...

この場合、パラメーター「K」は「String」になります。 マップ( childrenToMapList)は、リスト(` children`)にキーとして挿入された `Strings`を格納し、マップ値はリスト内のインデックス位置です。

リストとマップの理由は、 `HashSet`を反復する必要なく、リストのインデックス付きの値を取得できるようにするためです。