あなたが知っているのHashMapこれらの質問?

HashMapのは、多くの場合、Javaのインタビューでテストサイトの一つであり、<値キー、>の構造の一つは、多くの場合、開発に使用されています。おそらくあなたは、HashMapを使用していますが、これらの質問にそれを知っていますか?

  • HashMapの底层结构は何か?

あなたがされたと言うことができる場合数组+ 链表、あなたが知っている後にバージョン1.8の導入红黑树、それ?

  • 彼は言った红黑树あなたは、その構造、それを知って、?

あなたが知っている红黑树、あなたはそれが組み合わせであることを知っている平衡二叉树2-3树、それの製品の利点?それともあなたはまた、これらの2つのツリーあなたの構造を知っていますか?

  • 今、あなたは木を知っていることを索引结构、あなたはそれのさまざまなデータベースのインデックス構造を知りますか?

あなたは、おそらくそれはに類似している知っているMySqlの使用B+树あなたは、この構造を使用する理由構造、あなたは知っていますか?そして、なぜ、問題をバック巻き戻しHashMap使う红黑树代わりにB+树なぜ数据库それを使いますかB+树

  • HashMap膨張機構は、それを理解するには?なぜまた、あなたは知っているHashMapN番目のパワーを維持する能力がありますか?

  • HashMapないスレッドセーフ、その後、あなたは主にスレッドセーフに何が起こるか知っていることはありますか?

さて、ここでから、のは、これらの問題の上に行きましょう。


指数

  • 基本的な構造のHashMapとは何ですか?
  • 2-3から、木は赤黒木を見始めました
    • 2-3ツリー
    • 赤、黒の木
  • あなたはそれのデータベースのインデックス構造のすべての種類を知っていますか?
  • なぜB +ツリーデータベースのインデックスを選ぶのか?なぜ、HashMapの赤黒ツリーインデックスを選ぶのか?
  • HashMapの膨張機構は、それを理解するには?また、あなたは何乗Nを保つために、なぜHashMapの能力を知っていますか?
  • 本体ケースHashMapのは、スレッドセーフであるとは何ですか?
  • 小さな卵

基本的な構造のHashMapとは何ですか?

この問題は、その導入JDK1.7のHashMap、使用してHashMapの構造の前に長いためにJDKのバージョンから必要な数组+ 链表、この構造および使用する主な理由Hash算法の関連を。HashMapの目的は、データのみをO(1)レベルの複雑性を達成するためにアクセスできるようにすることです、それは私たちの記憶に、取得するためにハッシュアルゴリズムを使用してキー値は、<キー、値>構造でありhashcode、ハッシュコードがあるvalue中でHashMap、配列私たちは1照会するインデックス位置keyに対応するvalue時間は、一度だけ通過する必要がありHash面倒なトラバーサルを経由せずに、あなたは下付きの位置を得ることができます。

さまざまなオブジェクトがあるのでHash後に同じハッシュコードを得ることができますので、ここでは使用链表インデックスはリストを拡大する必要があるときに、我々は同じを打ったときに、構造を。

1

場合Hash関数は非常に洗練されたデザインではない、またはデータ自体を挿入する問題があり、そこになりますhashcode行く必要が、我々は配列の添字を取得した場合には、複数のヒットの後の場合遍历这个链表、特定取得しますvalueこのケースでは、HashMapののアクセス速度に影響します。

したがって、JDK1.8の効率向上のために導入され红黑树た構造を、しかしに赤黒木链表長さに達する8(默认值)時間、及びtable長さが64未満(または次膨張)でない場合、これらは赤黒木リンクされたリストに変換されます。

仮定hash冲突再度時間複雑性はO(n)は、長いリストの後ろの非常に深刻な、接地アレイ。赤黒木場合、時間計算量はO(LOGN)です。

2

あなたはここしばらく投稿次の質問を開始する前にHashMap的源码、ここであなたが知る必要があるいくつかの重要な分野です。

/**
 * The default initial capacity - MUST be a power of two.
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

DEFAULT_INITIAL_CAPACITYそれは私たちの直接のあるデフォルトの初期容量を指し、new HashMap()後に指定された配列のサイズ。

/**
 * The load factor used when none specified in constructor.
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

DEFAULT_LOAD_FACTOR負荷率と呼ばれる、负载因子*当前容器的大小このような電流容量と、膨張容器のタイミングを決定し、次に、負荷率が0.75であり、16 负载因子*当前容器的大小 = 16*0.75 = 1212を超える場合、膨張が血管であろう。

/**
 * The maximum capacity, used if a higher value is implicitly specified
 * by either of the constructors with arguments.
 * MUST be a power of two <= 1<<30.
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

MAXIMUM_CAPACITYこれは、容量の最大の拡張です。

/**
 * The bin count threshold for using a tree rather than list for a
 * bin.  Bins are converted to trees when adding an element to a
 * bin with at least this many nodes. The value must be greater
 * than 2 and should be at least 8 to mesh with assumptions in
 * tree removal about conversion back to plain bins upon
 * shrinkage.
 */
static final int TREEIFY_THRESHOLD = 8;

TREEIFY_THRESHOLDこの値は赤黒木の値に変換されるとき、鎖長が達成されます。

/**
 * The bin count threshold for untreeifying a (split) bin during a
 * resize operation. Should be less than TREEIFY_THRESHOLD, and at
 * most 6 to mesh with shrinkage detection under removal.
 */
static final int UNTREEIFY_THRESHOLD = 6;

UNTREEIFY_THRESHOLD赤黒木ノードリストがこの値未満の分解されたときの動作をリサイズ。

/**
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
 * between resizing and treeification thresholds.
 */
static final int MIN_TREEIFY_CAPACITY = 64;

MIN_TREEIFY_CAPACITYツリーが変換される前に、決意があるだろう、唯一のキー64は、変換の数が発生するよりも大きいです。これは、ハッシュテーブルの早期確立を避けるために、不要な変換とまったく同じリンクリストとリードに配置される複数のキーです。

/**
 * The table, initialized on first use, and resized as
 * necessary. When allocated, length is always a power of two.
 * (We also tolerate length zero in some operations to allow
 * bootstrapping mechanics that are currently not needed.)
 */
transient Node<K,V>[] table;

tableいわゆる配列。

2-3から、木は赤黒木を見始めました

2-3ツリー

あなたは、私たちは、バイナリツリーのキーの複数のノードを保存することができ、二分探索木を知っているし、それを呼び出す必要があります2-节点以上の2つのキーを追加し、それを呼び出します3-节点

jiedian

2-结点、(その値に対応する)、キーと2つのリンクを含むツリーキー2-3の左へのリンクはツリーの右側に2~3リンクがノードキーより大きいノード未満です。

3-结点、ツリー内の二つの鍵(および対応する値)と3つのリンク、2-3の左へのリンクを含むことは、ノードのキーよりも小さい、2-3キーへのリンクは、ノードのツリーに位置しています二つの鍵の間に、右リンクポイント2-3はノードのツリーのキーよりも大きいです。

ヌルリンクと呼ばれる空の木を指すリンク。

ルートからのすべての木を見つけるために、すべてのエアリンク2-3の完璧なバランスが同じである必要があります。

質問があるので、ポイント2-3木は何ですか?

それはすぐにノードを探しますが、私は、あなたが、バイナリ検索ツリーの欠点を発見していないかわからないが、それは大きな欠点は、新しいノードを挿入すると、全体のバイナリツリーを調整する必要があるということである持っています。

検索は、2-ノードで終了した場合、我々は、新しいノードを挿入したいとき、バイナリ検索ツリーは、同じではありません可以将一个2-节点转换为3-节点ので、バランスの操作を避け、。

charu

ノード-検索では、3に終わった場合可以将一个3-节点转换为3个2-节点

3-charu

親ノードに3-ノードが新しいノードの値を挿入するために2である場合可以将这个3-节点转换为3个2-节点,然后将其中一个2-节点与父节点的2-节点合并为3-节点

4-charu

2-3木はまた、挿入された効果的かつ効率的に見つけなければならた場合に見出すことができます。

赤、黒の木

红黑树背后的基本思想是用标准的二叉查找树(完全由2-节点构成)和一些额外的信息(替换3-节点)来表示2-3树。树种的链接分为两种类型:红链接将两个2-节点连接起来构成一个3-节点,黑连接则是2-3树中的普通链接。确切来说,将3-节点表示为由一个左斜的红色链接连接两个2-节点。这种情况下,我们的红黑树就可以直接使用标准二叉树的get方法来查找节点,在插入节点时,我们可以对节点进行转换,派生出一颗对应的2-3树。

rbtree

可以发现:

  • 红链接均为左链接
  • 没有任何一个结点同时和两条红链接相连

你可以将红黑树画平,就可以发现其中奥妙。

ピン

红黑树会有一个所谓的难点,就是旋转,想必你曾经因为这个问题很是恼火,那么从2-3树的角度来看看旋转的本质吧。

左旋 右旋
o_zuoxuan1 o_youxuan1
o_zuoxuan2 o_youxuan1

左旋右旋的本质目的,就是为了保证红色链接均为左链接。

你知道各类数据库的索引结构吗?

这里要介绍有二叉查找树,平衡二叉树,B-Tree,B+-Tree,Hash结构。

  • 二叉查找树

每个节点最多只有两个子树的结构。对于一个节点来说,他的左子树节点小于他,右子树节点大于他。

EC

  • 平衡二叉树

在二叉树的基础上,他的任意一个节点的左子树高度均不超过1。

但是二叉树因为每个节点只有两个子节点,所以树的高度非常高,IO次数也会增大,有时候效率并没有全表扫描高。所以这时候就需要B-Tree了。

  • B-Tree

IMG

每个节点最多有m个孩子,m阶B树。根节点至少包括两个孩子,树中每个节点最多包含有m个孩子,所有叶子节点都位于同一层。目的是为了让每一个索引块尽可能多的存储更多的信息,尽可能减少IO次数。

  • B+-Tree

树中节点指针与关键字数目一样,且数据均在叶子节点中。

IMG

所以B+Tree更适合用来做索引存储,磁盘读写代价低,查询效率稳定。这也是Mysql所使用的索引,而且Mysql为了增加查询速度,引入了DATA指针,可以直接访问底层数组。

  • Hash索引

通过Hash运算直接定位到目标。

ハッシュ

  • BitMap位图索引

修改数据时对其他数据影响极大。

IMG

这类索引目前只有Oracle使用了。

数据库为什么选择B+树索引?HashMap为什么选择红黑树索引?

这个问题的答案是因为磁盘

数据库的查询是位于磁盘,读取到数据之后存储到索引结构中。

HashMap是位于内存中。

磁盘内存的数据读取有很大差异,磁盘每次读取的最小单位是一簇,他可以是2、4、8、16、32或64个扇区的数据。而内存我们可以按照位来读取。

这种情况下我们在数据库中使用红黑树,建立的索引可能会庞大到无法想象,而在HashMap中使用B+树,对于HashMap频繁的插入操作,B+树无疑是要频繁进行修改的。

HashMap的扩容机制了解吗?另外你知道为什么HashMap容量要保持2的N次方吗?

HashMap扩容的主要情况是当前的容量达到负载因子*容器容量

负载因子的默认值是0.75,使用这个值的原因是太小时没有扩容的必要,太大时才扩容会影响性能,所以选择了0.75这个值。

另一个问题是HashMap为什么要保持容量为2的N次方的容量。

可以当作是为了防止hash求值碰撞的问题。在使用2的N次方容量时,数组下标的求取拥有很高的散列程度。

这个是之前看到的一篇文章

1

左边两组是数组长度为16(2的4次方),右边两组是数组长度为15。两组的hashcode均为8和9,但是很明显,当它们和1110的时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到同一个链表上,那么查询的时候就需要遍历这个链表,得到8或者9,这样就降低了查询的效率。

同时,我们也可以发现,当数组长度为15的时候,hashcode的值会与14(1110)进行,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!

所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。

HashMap线程不安全的主要情况是什么?

HashMap线程不安全的主要情况是在扩容时,调用resize()方法里的rehash()时,容易出现环形链表。

这样当获取一个不存在的key时,计算出的index正好是环形链表的下标时就会出现死循环。

rehash操作是重建内部数据结构,从而哈希表将会扩容两倍。通常,默认加载因子(0.75)在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。

1

小彩蛋

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这是HashMap的hash函数,不知道你有没有发现^ (h >>> 16)这个操作。

^ (h >>> 16)その目的は、実際にはハッシュコード内のhashCodeが高い16、順番に、役割の多くはないが、それは16にも役割を果たして作るために、独自の高と16ビットのハッシュがあるでしょうか高い16も関与ハッシュ計算が可能になるためであります。

おすすめ

転載: www.cnblogs.com/LexMoon/p/HashMapDep.html