より転載 https://www.cnblogs.com/fanerwei222/p/11918123.html
始まります!
HashMapのについて通常、それらの使用はいくつかの問題が発生します際に衝突ハッシュ、ハッシュテーブルの拡張、ハッシュテーブルのデータ構造の全体的な実装を、聞いて、あまりにも尋ねられたとき、より多くのインタビューを使用するハンドルのHashMapに複数のスレッドを使用少し奇妙なことが時々不可解な、起こるが、それは確かに1または2を知っているとき、私は、ソースコードの前に数回を見て、だけでなく、今記録はほとんど忘れてしまった3つの2日間を、持っていました、彼は後に読解力を促進するために繰り返しました。
JDKのバージョン:1.8
ハッシュマップデータ構造:赤黒木+ +リストの配列(JDk1.8)
まず、自家製テキストデータ構造図をみましょう。
少し荒いが、それはまた、見ることができます。
配列内の要素の構成+ +鎖は、バケット(バケツ)複数のノード、バケットチェーンを持つことができるか、赤黒木で構成することができる。リスト上で使用される場合、バケットと呼ばれる赤黒木アレイのJDk1.8のハッシュマップ、赤黒木プロパティで使用される場合について説明します。
まず、各プロパティの値を教え、そこデフォルトですが、また後半に使用する必要があるため。
★赤黒木に関連する3つの値が
/ ** * Aの最後の一覧Aツリー内の使用ビンCOUNTザ・むしろに対する閾値で *ビン。ビンは、追加AN要素木Aにに変換され 、この多くのノードで*少なくともビン。アットザグレーター値でなければなりません *最後の2及び万一以内前提と噛み合うように、少なくとも8 BE 時に平野ビンへの変換戻るについて*木の除去 *収縮。 この値未満のバケット内のノードの数が、とき、しきい値*バケツをトリーイング *赤字にリストから変換されます黒木、赤黒木ノードリストノードへの変換により、浴槽内のすべてのノード * / 静的int型TREEIFY_THRESHOLD =ファイナル8; / ** ビンA中untreeifying Aビン(分割)するための閾値の* COUNT サイズ変更は、最後の内で小さくなるよう*動作すべきです。 TREEIFY_THRESHOLD、およびAT 除去の下で検出収縮と噛み合うように* MOST 6。 *は、タブリスト縮小閾値にツリーによって変換された リンクリスト内の要素の膨張がバケツ未満である場合、この値は赤黒木浴槽変換(還元、セグメンテーション)であろう* * / 静的最終int型UNTREEIFY_THRESHOLD = 6; / ** *月にビンがtreeified BEなる最小テーブル容量の *(ビンでそれ以外の場合は表ISリサイズIF TOO多くのノード。) 競合を避けるために、少なくとも4 * * TREEIFY_THRESHOLDをしなければならない 閾値の間*サイズ変更とtreeification。 *ハッシュテーブルの容量の最小全域木 *全体のハッシュテーブルの容量は、木の樽ではなく、それ以外の場合はバレルを行うためには、この値よりも大きい場合DOMツリーの * / = 64 MIN_TREEIFY_CAPACITY最終int型の静的。
いくつかの他の分野の紹介:
/ ** *表は、最初の使用時に初期化され、としてリサイズ *必要。割り当てられた場合には、長さは常に2のべき乗です。 *(我々も可能にするいくつかの操作の長さゼロを許容する 。現在必要とされない*ブートストラップ力学) *数组表 * / 過渡HashMap.Node <K、V> []テーブル。 / ** *)(キャッシュされたのentrySetを保持します。AbstractMapのフィールドが使用されていることを注意 のkeySet()と値のための*を()。 *缓存条目集 * / 過渡の設定<のMap.Entry <K、V >>のentrySet。 / ** *このマップに含まれるキーと値のマッピングの数。 *マップ当前的大小 * / 過渡int型のサイズ。 / ** *アットザナンバーは時代のこのHashMapのは、構造的に変更されました *構造変更はでマッピングのナンバーに変更するもので はHashMapで*またはその他の変更、その内部構造(EG、 *焼き直し)。このフィールドに、コレクション・ビューONメイクイテレータに使用されています (ConcurrentModificationExceptionを参照)。失敗-FASTのHashMap *。 * ModCountはHashMapの、のために記録周波数を変更 * HashMapのプットで()、取得()、削除()、Interator() メソッドを使用して、のような特性は 反復が、反復子modCount expectedModCountプロパティに割り当てられ、その後反復される場合*のHashMapので、スレッドセーフされていない ハッシュマップが反復の過程で別のスレッドによって変更した場合、値はmodCountう*変更は expectedModCountとmodCountが等しくない、この時間は*、 *イテレータが異常)(ConcurrentModificationExceptionをスローします * / 過渡のint modCount; / ** 。AT *リサイズ(*負荷容量係数)への次のサイズ値 *調整されるべき次のしきい値サイズは、この値は、容量負荷率xに等しいであろう * @serial * / ; INT閾値 / ** *負荷率のため。ハッシュテーブルは、 負荷で使用される*膨張係数 * @serial * / 最終loadFactorフロート。
私たちは、ハッシュマップを使用します:
地図<文字列、オブジェクト> mymapという=新しいHashMapの<文字列、オブジェクト>();
そのコンストラクタを探します:
/ ** *構築AN空の<TT>デフォルトの初期容量のとのHashMap </ TT> *(16)とデフォルトの負荷係数(0.75)。 * MYMAPに与えられたデフォルトの負荷係数にデフォルトの引数なしのコンストラクタ * / パブリックHashMapの(){ this.loadFactor = DEFAULT_LOAD_FACTOR; //すべてのフィールドその他の債務不履行となっ }
このようなHashMapのインスタンスのmymapが生成され、私たちは中MYMAPに何かを追加しています。
mymap.put( "こんにちは"、 "世界");
HashMapの中で達成するために直接、メソッドMapインタフェースに行く方法を入れて、参照、外部から直接クリックして進み、
/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
先是hash()方法生成key的hash值:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
再是调用了putVal()方法, 并且最后两个参数的值传了一个false, 一个true, 进去看看:
/** * Implements Map.put and related methods. * * @param hash hash for key 传进来的key的哈希值 * @param key the key 传进来的key * @param value the value to put 传进来的值 * @param onlyIfAbsent if true, don't change existing value 如果是真, 则不改变已存在的值 * @param evict if false, the table is in creation mode. 这个值可以给插入后的操作执行的方法提供一个判断 * @return previous value, or null if none 返回原来key的value值 */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { /** * 此处我传进来的值 * key : hello * value : world * onlyIfAbsent : false * evict : true */ /** * 定义局部变量 * tab : 局部数组表 * param : 找到对应i下标的Node * tabLength : 局部数组表的长度 * i : tab的下标 */ HashMap.Node<K,V>[] tab; HashMap.Node<K,V> param; int tabLength, i; /** * 此时table是一个空数组 * tab 为null, 执行 resize() 方法(*此方法后续分析) */ if ((tab = table) == null || (tabLength = tab.length) == 0){ /** * 把重新分配的表赋值给tab */ tabLength = (tab = resize()).length; } /** * tab长度-1后与key的hash值得到一个下标赋值给i * 获取tab中该下标的值, 如果为null, * 则执行newNode()方法生成一个Node节点并且将节点赋值给tab[i] */ if ((param = tab[i = (tabLength - 1) & hash]) == null){ /** * 生成一个Node */ tab[i] = newNode(hash, key, value, null); } /** * 接下来的else分支操作是替换旧值 */ else { /** * 临时节点 */ HashMap.Node<K,V> okNode; /** * 临时key */ K tmpK; /** * 首先对比 找到的节点的hash值是否和传进来的一致; * 如果一致, 继续比较 * 找到的节点的key值是否和传入的key相等==, 相等判断的是引用地址 * 如果不相等, 判断一下传入的key是否和找到的节点的key equals, * equals方法判断的依据默认也是判断引用地址是否相等, 但是很多类像String, * 或者自定义的类, 都有可能重写equals() 方法, * 这里加上这么一个判断对传入Object类型的key做了一个是否equals校验的宽容性检查, * 如果一个自定义对象, 重写了equals方法, 重写了 hashcode方法, 并且只要该对象的两个实例的id相同, * 就判定两个实例相等, 这里就起了关键作用, 因为两个对象实例的引用地址一般都不相等, 除非直接赋值引用. * * 找到节点的hahs值和传入的hash值相等 而且 找到的节点的key和传入的key也相等,则判定该key存在 */ if (param.hash == hash && ((tmpK = param.key) == key || (key != null && key.equals(tmpK)))) { /** * 将找到的节点赋给临时节点 */ okNode = param; } /** * 继续判断找到的节点是否是树节点,也就是红黑树节点的实例 */ else if (param instanceof HashMap.TreeNode) { /** * 如果是红黑树的节点, 则调用putTreeVal()方法更新tab, hash, key, value等需要更新的值 * 并且返回旧的树节点 */ okNode = ((HashMap.TreeNode<K,V>)param).putTreeVal(this, tab, hash, key, value); } else { /** * 链表节点的判断和赋值操作 * 链表操作, 不做过多解释, * 嘿嘿, 给自己留点思考的空间吧, 留着以后自己看的时候思考一下 */ for (int binCount = 0; ; ++binCount) { if ((okNode = param.next) == null) { param.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st { /** * 链表树形化操作 */ treeifyBin(tab, hash); } break; } if (okNode.hash == hash && ((tmpK = okNode.key) == key || (key != null && key.equals(tmpK)))) break; param = okNode; } } /** * 被返回的树节点, 不是树节点就是链表节点 */ if (okNode != null) { // existing mapping for key /** * 旧节点的值 */ V oldValue = okNode.value; /** * 根据 onlyIfAbsent 值来决定返回的value * 如果值没有改变返回的值就是传进来的value自己 */ if (!onlyIfAbsent || oldValue == null) { okNode.value = value; } /** * 空方法 */ afterNodeAccess(okNode); return oldValue; } } /** * 操作数自增1 */ ++modCount; /** * 表的容量自增1之后如果大于要扩容的阈值, 则继续重新计算大小 */ if (++size > threshold) { resize(); } /** * 一个空方法, 用户可以自行实现 */ afterNodeInsertion(evict); /** * 此时的值是一个新的值, 所以没有旧的值, 返回null */ return null; }
那几个空方法:
// Callbacks to allow LinkedHashMap post-actions void afterNodeAccess(HashMap.Node<K,V> p) { } void afterNodeInsertion(boolean evict) { } void afterNodeRemoval(HashMap.Node<K,V> p) { }
到这里put操作就差不多了.
再来看看其他3个构造函数吧!
/** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. * 传入一个初始容量值, 使用默认的负载因子 */ public HashMap(int initialCapacity) { /** * 调用的下面的这个构造函数 */ this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. * 传入一个初始容量值, 使用默认的负载因子 */ public HashMap(int initialCapacity) { /** * 调用的下面的这个构造函数 */ this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive * 传入自定义的初始容量, 传入自定义的负载因子 */ public HashMap(int initialCapacity, float loadFactor) { /** * 初始容量不能小于0 * 否则抛出异常 */ if (initialCapacity < 0) { throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); } /** * 初始容量如果大于最大允许容量, 则使用最大允许容量 */ if (initialCapacity > MAXIMUM_CAPACITY) { initialCapacity = MAXIMUM_CAPACITY; } /** * 负载因子只能是大于0的浮点数 * 非法的负载因子会抛出异常 */ if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Illegal load factor: " + loadFactor); } this.loadFactor = loadFactor; /** * 调用方法计算初始容量的 2 的 幂的值 * 容量大小只允许为2的倍数 */ this.threshold = tableSizeFor(initialCapacity); } /** * Returns a power of two size for the given target capacity. * 对给定的容量, 计算出接近该值2倍大小幂的值 */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } /** * Constructs a new <tt>HashMap</tt> with the same mappings as the * specified <tt>Map</tt>. The <tt>HashMap</tt> is created with * default load factor (0.75) and an initial capacity sufficient to * hold the mappings in the specified <tt>Map</tt>. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null * 初始化时直接传入一个map, 使用默认的负载因子 */ public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; /** * 将传入的map的条目放进新的map中 */ putMapEntries(m, false); } /** * Implements Map.putAll and Map constructor. * * @param m the map * @param evict false when initially constructing this map, else * true (relayed to method afterNodeInsertion). */ final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { /** * 传入map 的大小 */ int size = m.size(); if (size > 0) { /** * 先看看自身table是不是null */ if (table == null) { // pre-size float ft = ((float)size / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } /** * 看看大小是否超过了下一个要调整的大小, 超过了则重新计算 */ else if (size > threshold) { resize(); } /** * 使用entrySet遍历并且put * entrySet 这里值得追溯一下 , 仔细点可以看到其实没有一个地方显式的设置entrySet的值, * 在put操作里面没有找到相关给entrySet设置值的代码, 这里面的值是怎么来的值得追寻 */ for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } }
上面说到entrySet()的值是怎么来的, 其实是从抽象类 AbstractCollection<E> 中的 toString()方法来的;
怎么说呢, 在需要使用entrySet()的时候就会使用到这个方法:
public String toString() { Iterator<E> it = iterator(); if (! it.hasNext()) return "[]"; StringBuilder sb = new StringBuilder(); sb.append('['); for (;;) { E e = it.next(); sb.append(e == this ? "(this Collection)" : e); if (! it.hasNext()) return sb.append(']').toString(); sb.append(',').append(' '); } }
这个方法又会调用 iterator() 方法:
final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } public final boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Node<K,V> candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } public final boolean remove(Object o) { if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true, true) != null; } return false; } public final Spliterator<Map.Entry<K,V>> spliterator() { return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super Map.Entry<K,V>> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int型MC = modCount。 for (int i = 0; i < tab.length; ++i) { (;; E = NULL!= E e.nextノード<K、V> E =タブ[i])とのための action.accept(E); } (modCount = MC!)の場合 )(新しいConcurrentModificationExceptionを投げます。 } } }
イテレータ()メソッドは、新しいEntryIteratorは、そう、彼らは実際に遅延し、次のノードが返されます。
最終的なクラスEntryIteratorはHashIterator延び 実装イテレータ<のMap.Entry <K、V >> { 公共最終のMap.Entry <K、V>次の(){リターンNEXTNODE()。} }
少し深いの所持!
今日では、最初にここにいます!
継続するには...