web-dev-qa-db-ja.com

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

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

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

65
Ben

最善の策は、 HashSet を使用し、contains()メソッドを使用して文字列がセットに存在するかどうかを確認することです。 HashSetは、ObjectメソッドhashCode()およびequals()を使用して高速にアクセスできるように構築されています。 HashSetのJavadocの状態:

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

HashSet ハッシュバケットにオブジェクトを格納 つまり、hashCodeメソッドによって返される値によって、オブジェクトが格納されるバケットが決定されます。このように、等しい量はHashSetは、equals()メソッドを介して実行する必要がありますが、同じハッシュバケット内の他のオブジェクトに限定されます。

HashSetsとHashMapsを効果的に使用するには、概要説明されているequalsおよびhashCodeコントラクト javadoc に準拠する必要があります。の場合 Java.lang.Stringこれらのメソッドは、これを行うためにすでに実装されています。

96
krock

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

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

11
mdma

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

可能な解決策として:

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

  • それ以外の場合は、O(1)のパフォーマンス特性として_Java.util.HashSet_を使用できます。まだ計算されていない文字列のハッシュコードの計算は、O(m) m = string.length()を使用した演算です。ハッシュテーブルも覚えておいてください。 HashSetが使用するデフォルトの負荷係数は.75であり、1e6オブジェクトのHashSetは内部的に1.3e6エントリの配列を使用することを意味します。

  • HashSetが機能しない場合(たとえば、ハッシュの衝突が多いため、メモリが不足しているため、または挿入が多いため)、 Trie を使用することを検討してください。 Trieでのルックアップには、最悪の場合のO(m) where m = string.length())の複雑さがあります。Trieには、次のような利点もあります。例えば、検索文字列にclosest fitを与えることができますが、最良のコードはコードなしであることに注意してください。メリットがコストを上回る場合の実装。

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

8
nd.

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

5
unbeli

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

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より高速です。

2
awiebe

このような膨大な数の文字列で、私はすぐに Trie を考えます。より限定された文字セット(文字など)や、多くの文字列の先頭が重複する場合に、より適切に機能します。

2
ILMTitan

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

1
oopbase

おそらくこれはあなたの場合には必要ではありませんが、スペース効率の高い確率的アルゴリズムがあることを知っておくと便利だと思います。たとえば、 ブルームフィルター

1
simplylizz

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

最近やった仕事の例:

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

private NodeKey<K> parent;
private List<K> children = new ArrayList<K>();
private Map<K, Integer> childrenToListMap = new HashMap<K, Integer>();

public NodeKey() {}

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

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

    ...

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

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

0
ghostNet

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

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

  1. ハッシュコード()
  2. equals()
0
Truong Ha