home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

Java/Collections/Hash, Linked, Treeの違い

Java/Collections/Hash, Linked, Treeの違い

Java / Collections / Hash, Linked, Treeの違い
id: 1202 所有者: msakamoto-sf    作成日: 2013-07-07 18:04:05
カテゴリ: Java 

Javaプログラミングに10年近く携わっていて、今までまじめにHashMap/TreeMap/LinkedHashMapの違いを勉強したことがなかったので、簡単にまとめてみました。Oracle JDK 1.7.0.25 のソースも参照しています。
なお、あんまり学術的に突っ込んで調べてないので、話半分程度に考えて、正確な情報についてはJDKのJavaDocやソースコードを参照してください。

HashMap, HashSet

  • キーとなるオブジェクトのhashCode()をハッシュテーブルに振り分けて格納してく実装。
  • JDK 1.7.0.25の内部実装としては、hashCode()を元にして配列アクセス出来る"table"が用意されてる。配列アクセス=Javaの配列として用意されていて、どの配列要素にも一定のコスト(あんまり厳密に書いてないので、パフォーマンスというか感覚的な話)でアクセス出来る。hashCode()の計算量に引きずられるのかな?
  • デフォルトのコンストラクタではそれほど広大なハッシュテーブルを用意しているわけではなく、埋まり具合に応じて拡張・再構築される。一応、ハッシュテーブルの初期容量と、拡張・再構築を実施する埋まり具合を指定するコンストラクタも用意されている。
  • HashSetは内部的にはHashMapをそのまま使っていて、単に値としてダミーのオブジェクトを入れて、キーとしての操作だけをSetインターフェイスの実装として公開してるようです。

HashMapでput()にかかる時間、get()にかかる時間を見てみるサンプル:

  • https://github.com/msakamoto-sf/javasnack/blob/master/src/main/java/javasnack/snacks/perfs/map/PerfHashMapFinePutGet.java
    • put()では、最初から14回目位で若干時間がかかってます。バケット数16, スレッショルド 0.75 に設定してますので、多分ココらへんでハッシュテーブルの拡張と再構築が発生してると思われます。以降も、時々時間がかかるput()があります。
    • 全体傾向としてはput()もget()も大体コンスタントな計算量になってるようです。ただ、若干後ろの方にいくとハッシュテーブルのアクセス時間が伸びるのか、なんとなーく時間がかかり気味な感じもします。

TreeMap, TreeSet

  • 赤黒木・・・つまり平衡2分探索木の一種で、それを使ってソートされた状態でキー(と値)を保存出来る。
  • キーを指定する containsKey(), get(), put(), remove() メソッドは、使用されているアルゴリズムの特性から時間コストはlog(n)になる・・・つまり、平衡2分探索になりますよーということらしいです・・・。
  • やはり特徴としては、要素を追加すると自動的に自然順序 or コンストラクタで指定するComparatorによってソートされ、iteratorなどで要素を取り出すときもソートされた順序で取り出される点でしょうか。
  • 赤黒木の実装はそれなりに大変で、JDK 1.7.0.25 の内部実装でもコメント含めておよそ2,400行になっています。HashMapがおよそ1,100行ですから、その2倍のソースコードが必要になっています。
  • 内部の赤黒木に対して複雑な操作が必要になってくるため、要素の追加ではHashMapより計算量が多くなってしまっています。
  • HashSetと同様、TreeSetも内部的にはTreeMapを使っていて、単に値としてダミーのオブジェクトを入れて、キーとしての操作だけをSetインターフェイスの実装として公開してるようです。

参考:

TreeMapでput()にかかる時間、get()にかかる時間を見てみるサンプル:

LinkedHashMap, LinkedHashSet

  • HashMapですと、hashCode()を元に順番が並べ替えられてしまいますが、LinkedHashMapを使うと、要素を追加した順番で値を取り出すことが出来ます。
  • 仕掛けとしては要素(エントリ)に対して二重リンク・リストを保持しておき、Iteratorなどで取り出すときに二重リンク・リストを辿るようにしている。
  • なお、要素を追加した順序ではなく、要素に「アクセス」(get()含む)した順を保持するよう指定できるコンストラクタが提供されている。これを使うと、単にget()するだけでも、要素が入っていればアクセス順として内部のリンクリストの構成が更新される。
  • LinkHashSetについては、上と同様、LinkedHashMapを使って実現してます。

LinkedHashMapでput()にかかる時間、get()にかかる時間を見てみるサンプル:

Java配列, ArrayList, LinkedList

ちょっと脇道にそれますが、リスト管理の仕組みとしてJavaの配列, ArrayList, LinkedListを比べてみました。

Java配列のデモ:

ArrayListのデモ:

LinkedListのデモ:

  • https://github.com/msakamoto-sf/javasnack/blob/master/src/main/java/javasnack/snacks/perfs/list/PerfLinkedListFinePutGet.java
    • リンクリストによるListの実装になります。配列要素による管理はしてないため、初期容量がどうのこうのとか、容量が足りなくなった場合の拡張が~とかのコストがありません。そのため、put()の時間がかなりコンスタントになっています。
    • また、面白いのがget()の特性で、アクセスするindexが全体サイズで前から辿ったほうが速いか、後ろから辿ったほうが速いかチェックされています。そのため、0-499までのindexにアクセスしてみると真ん中の0 -> 250までは徐々に時間がかかってくるんですが、250 -> 499までは徐々に時間が減ってきてます。恐らく250以降になってくると、最後からたどっているためと思われます。

簡単なベンチマークプログラムを組んでみますと、HashMap/TreeMap/LinkedHashMapの実装による差異が目で見えるようになり、面白かったです。
今回は時間だけに着目したベンチマークプログラムを作ってみましたが、メモリ容量に着目してみるのも面白いかもしれません。



プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2013-07-07 18:05:06
md5:d7b15fe194475e8cc5e51f3eebb32644
sha1:cbdb8b500f614f638409f21752c5b654b8ea0a89
コメント
コメントを投稿するにはログインして下さい。