私はいつも木が好きでした、そのニースO(n*log(n))
とそれらのきちんとしたこと。しかし、私が今までに知っているすべてのソフトウェアエンジニアが、なぜTreeSet
を使うのかと指摘してきました。 CSの背景からすると、あなたが使うことはそれほど問題にならないと思いますし、(Java
の場合)ハッシュ関数やバケツをいじっても構いません。
どの場合にHashSet
よりTreeSet
を使うべきですか?
HashSetはTreeSetよりはるかに高速です(add、remove、containsなどのほとんどの操作では一定時間対ログ時間)が、TreeSetのような順序保証はありません。
SortedSet
)first()
、last()
、 headSet()
、および tailSet()
etcのような順序付きセットを扱うためのいくつかの便利な方法を提供しています。HashSet
とTreeSet
の中間です。リンクリストを実行するハッシュテーブルとして実装されていますが、 TreeSetで保証されているソート済みトラバーサルと同じではない挿入順反復を提供します 。したがって、使い方の選択は完全にあなたのニーズに依存しますが、たとえあなたが順序付きコレクションを必要とするとしても、あなたはまだセットを作成しそれからTreeSetに変換するためにHashSetを好むべきだと思います.
SortedSet<String> s = new TreeSet<String>(hashSet);
TreeSet
についてまだ言及されていない利点の1つは、より大きな「局所性」があることです。これは、(1)2つのエントリが順番に近い場合は、近くに配置されます。データ構造、したがってメモリ内。 (2)この配置では、ローカリティの原則を利用します。つまり、類似のデータには、類似の頻度でアプリケーションからアクセスされることがよくあります。
これはTreeSet
とは対照的です。HashSet
は、キーが何であっても、エントリをメモリ全体に広げます。
ハードドライブからの読み取りにかかる待ち時間のコストがキャッシュまたはRAMからの読み取りにかかるコストの数千倍であり、データが実際に局所性を持ってアクセスされる場合は、TreeSet
がはるかに良い選択になります。
HashSet
は要素にアクセスするためのO(1)なので、確かに重要です。しかし、セット内のオブジェクトの順序を維持することは不可能です。
TreeSet
は、(挿入の順序ではなく値の観点から)順序を維持することが重要な場合に役立ちます。しかし、すでに述べたように、要素へのアクセス時間が遅くなるような順序で取引しています。基本操作ではO(log n)です。
この実装は、基本操作(
add
、remove
、およびcontains
)のlog(n)時間コストを保証します。
1.HashSetはnullオブジェクトを許可します。
2.TreeSetはnullオブジェクトを許可しません。 null値を追加しようとすると、NullPointerExceptionがスローされます。
3.HashSetはTreeSetよりはるかに高速です。
例えば.
TreeSet<String> ts = new TreeSet<String>();
ts.add(null); // throws NullPointerException
HashSet<String> hs = new HashSet<String>();
hs.add(null); // runs fine
素敵な 視覚的な答え @shevchykによるMapsを基にしています。
╔══════════════╦═════════════════════╦═══════════════════╦═════════════════════╗
║ Property ║ HashSet ║ TreeSet ║ LinkedHashSet ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ ║ no guarantee order ║ sorted according ║ ║
║ Order ║ will remain constant║ to the natural ║ insertion-order ║
║ ║ over time ║ ordering ║ ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ Add/remove ║ O(1) ║ O(log(n)) ║ O(1) ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ ║ ║ NavigableSet ║ ║
║ Interfaces ║ Set ║ Set ║ Set ║
║ ║ ║ SortedSet ║ ║
╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣
║ ║ ║ not allowed ║ ║
║ Null values ║ allowed ║ 1st element only ║ allowed ║
║ ║ ║ in Java 7 ║ ║
╠══════════════╬═════════════════════╩═══════════════════╩═════════════════════╣
║ ║ Fail-fast behavior of an iterator cannot be guaranteed ║
║ Fail-fast ║ impossible to make any hard guarantees in the presence of ║
║ behavior ║ unsynchronized concurrent modification ║
╠══════════════╬═══════════════════════════════════════════════════════════════╣
║ Is ║ ║
║ synchronized ║ implementation is not synchronized ║
╚══════════════╩═══════════════════════════════════════════════════════════════╝
ほとんどの場合にHashSet
が使用されるのは、オペレーションが(平均して)O(log n)ではなくO(1)であるためです。セットに標準的な項目が含まれている場合は、それがあなたのために行われたように「ハッシュ関数をいじっている」ことはありません。セットにカスタムクラスが含まれている場合は(hashCode
を使用するためにHashSet
を実装する必要がありますが、有効なJavaにはその方法が示されています)、TreeSet
を使用する場合はComparable
にするかComparator
を指定する必要があります。クラスに特定の順序がない場合、これは問題になる可能性があります。
非常に小さいセット/マップ(<10項目)にTreeSet
(または実際にはTreeMap
)を使用したことがありますが、実際に利益があるかどうかを確認していません。大規模なセットの場合、違いはかなりのものになる可能性があります。
ソートが必要な場合はTreeSet
が適切ですが、それでも更新が頻繁でソート結果の必要性が低い場合でも、内容をリストまたは配列にコピーしてソートする方が速い場合があります。
頻繁な再ハッシュ(またはHashSetのサイズ変更ができない場合は衝突)を引き起こすのに十分な要素を挿入していない場合、HashSetは確かに一定時間アクセスの利点をもたらします。しかし、伸びや縮みが非常に多いセットでは、実装によってはTreesetsを使用すると実際にパフォーマンスが向上することがあります。
メモリ使用可能な場合、償却時間は機能的な赤黒木でO(1)に近くなります。 Okasakiの本は私がやってのけることができるよりもっと良い説明を持っているでしょう。 (または 彼の出版物リスト を参照)
HashSetの実装は、もちろん、はるかに高速です。順序がないため、オーバーヘッドが少なくなります。 JavaでのさまざまなSet実装の良い分析は http://Java.Sun.com/docs/books/tutorial/collections/implementations/set.html にあります。
そこでの議論はまたTree vs Hash問題への興味深い「ミドルグラウンド」アプローチを指摘しています。 JavaはLinkedHashSetを提供します。これは、「挿入指向」のリンクリストが実行されるHashSetです。つまり、リンクリストの最後の要素も最後にHashに挿入されます。これにより、TreeSetのコスト増を招くことなく、順序付けされていないハッシュの誤解を避けることができます。
TreeSet は2つのソートされたコレクションのうちの1つです(もう1つはTreeMapです)。これは赤黒の木構造を使用していますが(ただし、あなたは知っていましたが)、要素は自然順序に従って昇順で並ぶことを保証します。オプションとして、ComparableまたはComparatorを使用して、(要素のクラスで定義された順序に依存するのではなく)順序をどのようにするかについて独自の規則をコレクションに与えることができるコンストラクタを使用してTreeSetを構築できます
そして LinkedHashSet は、すべての要素にわたって二重にリンクされたリストを維持するHashSetの順序付きバージョンです。繰り返しの順序が気になる場合は、HashSetの代わりにこのクラスを使用してください。 HashSetを反復処理するとき、順序は予測不可能ですが、LinkedHashSetを使用すると、要素が挿入された順序で要素を反復処理できます。
あなたはオレンジを持つことができるのになぜリンゴがありますか?
あなたのコレクションが大きく、読み書きが膨大で、CPUサイクルにお金を払っているのであれば、コレクションの選択は、パフォーマンスを向上させる必要がある場合にのみ意味があります。しかし、ほとんどの場合、これは重要ではありません。ここ数ミリ秒の間、人間の目では気づかれないままになります。それほど問題にならないのなら、なぜアセンブラやCでコードを書いていないのでしょうか。 [別の議論を始める]。つまり、あなたが選んだコレクションを何でも使って満足しているのであれば、それが問題になります。ソフトウェアは可鍛性があります。必要に応じてコードを最適化してください。ボブおじさんは時期尚早の最適化がすべての悪の根源だと言います。 アンクルボブはそう言う
技術的な考慮事項、特にパフォーマンスに関して、多くの回答がなされています。私によると、TreeSet
とHashSet
の選択は重要です。
しかし、私はむしろその選択はによって推進されるべきだと言いたい 概念的 まず検討事項。
操作する必要があるオブジェクトに対して、自然な順序付けが意味を成さない場合は、TreeSet
を使用しないでください。SortedSet
を実装しているので、ソートセットです。そのため、関数compareTo
をオーバーライドする必要があります。これは、関数equals
を返すものと一致している必要があります。たとえば、Studentというクラスのオブジェクトのセットがある場合、生徒間には自然な順序付けがないため、TreeSet
は意味をなさないと思います。あなたはそれらの平均的な等級でそれらを注文することができます、しかし、これは「自然な順序」ではありません。 2つのオブジェクトが同じ生徒を表す場合だけでなく、2人の異なる生徒が同じ学年を持つ場合も、関数compareTo
は0を返します。 2番目のケースでは、equals
はfalseを返します(2人の生徒が同じ学年を持っているときに後者をtrueにすると決めない限り、equals
関数は誤解を招くような意味になります)。equals
とcompareTo
の間のこの一貫性はオプションですが、強くお勧めします。そうでなければ、インターフェースSet
の規約が破られ、あなたのコードが他の人々に誤解を招くようになり、その結果、予期しない動作を引き起こす可能性があります。
この link はこの質問に関する良い情報源かもしれません。
メッセージ編集( complete rewrite )順序が問題にならないときは、そのときにしてください。どちらもLog(n)を返すはずです - どちらかが他方よりも5%以上速いかどうかを確認するのに役立ちます。 HashSetはループ内でO(1)テストを行うことができるかどうかを明らかにすることができます。