基本的に、約1,000,000個の文字列があります。リクエストごとに、文字列がリストに属しているかどうかを確認する必要があります。
パフォーマンスが心配なので、最善の方法は何ですか? ArrayList
?ハッシュ?
最善の策は、 HashSet
を使用し、contains()
メソッドを使用して文字列がセットに存在するかどうかを確認することです。 HashSetは、ObjectメソッドhashCode()
およびequals()
を使用して高速にアクセスできるように構築されています。 HashSet
のJavadocの状態:
このクラスは、基本操作(追加、削除、包含、サイズ)で一定の時間パフォーマンスを提供します。
HashSet ハッシュバケットにオブジェクトを格納 つまり、hashCode
メソッドによって返される値によって、オブジェクトが格納されるバケットが決定されます。このように、等しい量はHashSet
は、equals()
メソッドを介して実行する必要がありますが、同じハッシュバケット内の他のオブジェクトに限定されます。
HashSetsとHashMapsを効果的に使用するには、概要説明されているequals
およびhashCode
コントラクト javadoc に準拠する必要があります。の場合 Java.lang.String
これらのメソッドは、これを行うためにすでに実装されています。
一般に、HashSetを使用するとパフォーマンスが向上します。ArrayListのように各要素を調べて比較する必要はありませんが、通常、ハッシュコードが等しい場合、多くても数個の要素を比較するだけです。
ただし、1M文字列の場合、hashSetのパフォーマンスは依然として最適ではない可能性があります。キャッシュミスが多いと、セットの検索が遅くなります。すべての文字列が等しく発生する可能性がある場合、これは避けられません。ただし、一部の文字列が他の文字列よりも頻繁に要求される場合は、共通の文字列を小さなhashSetに配置し、大きなセットをチェックする前に最初にチェックできます。小さなハッシュセットは、キャッシュに収まるようにサイズを調整する必要があります(たとえば、最大で数百K)。小さいハッシュセットへのヒットは非常に高速になり、大きいハッシュセットへのヒットはメモリ帯域幅によって制限された速度で進行します。
先に進む前に、これを考慮してください。なぜパフォーマンスが心配なのですか?このチェックはどのくらいの頻度で呼び出されますか?
可能な解決策として:
リストがすでにソートされている場合は、_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を与えることができますが、最良のコードはコードなしであることに注意してください。メリットがコストを上回る場合の実装。
より複雑なクエリが必要な場合は、データベースの使用を検討してください。部分文字列または正規表現に一致します。
Set
を使用します。ほとんどの場合、HashSet
で問題ありません。
ここで演習を実行したことが私の結果です。
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より高速です。
このような膨大な数の文字列で、私はすぐに Trie を考えます。より限定された文字セット(文字など)や、多くの文字列の先頭が重複する場合に、より適切に機能します。
このような大量の文字列がある場合、データベースを使用するのが最善の機会です。 MySQLを探します。
おそらくこれはあなたの場合には必要ではありませんが、スペース効率の高い確率的アルゴリズムがあることを知っておくと便利だと思います。たとえば、 ブルームフィルター 。
オブジェクトがリスト/セットに含まれているかどうかを確認すると同時に、リスト/セットを並べ替えたい場合があります。列挙またはイテレータを使用せずにオブジェクトを簡単に取得する場合は、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);
}
...
この場合、パラメータK
はString
になります。マップ(childrenToMapList
)は、リスト(Strings
)にキーとして挿入されたchildren
を格納し、マップ値はリスト内のインデックス位置です。
リストとマップの理由は、HashSet<String>
を反復する必要なく、リストのインデックス付き値を取得できるようにするためです。
Stringだけでなく、一意のアイテムが必要な場合にはSetを使用できます。
アイテムのタイプがプリミティブまたはラッパーの場合、気にする必要はありません。ただし、クラスの場合は、2つのメソッドをオーバーライドする必要があります。