ディレクトリ
何HashMapの?
地図は、データを格納するための一般的なJavaキーと値の構造であるための鍵
HashMapのマップ構造は、アクセスキー値使用されるハッシュアルゴリズムの基礎となります
HashMapの:Mapインタフェースのハッシュテーブルを実装します。
- この実装は、オプションのマップ操作の全てを提供し、使用ヌルヌル値及びキーを可能にする。
(ヌルおよび非同期加算の使用を可能にすることに加えて、HashMapのハッシュテーブルのクラスがほぼ同じです。)- このクラスは、マッピングの順序を保証するものではありません。特に、それは順序が永遠に続くことを保証するものではありません。
- この実装は、基本的な操作(getおよびput)のための安定した性能を提供するために、ハッシュ関数の要素を適切に浴槽の間に分散されると仮定します。
- 反復的なパフォーマンスが高すぎる初期容量を設定しないことが重要である場合、 - (値のマッピングキー)ので、比例。コレクションHashMapのインスタンスに「容量」(バケット数)とサイズを表示するために必要な反復時間(又は、負荷率が低すぎます)
HashMapのは、基礎となるデータ構造は何ですか?
補助データ構造のエントリマップは、基礎となる構造は、マップエントリアレイで
リンクされたリストメモリ素子競合用いるが矛盾使用ハッシュリンクリスト処理方法
競合がある程度の位置に関するデータを蓄積し、ブラックツリーに変換します構造
// 必定是2的倍数
transient Node<K,V>[] table;
データ構造の基礎となるHashMapのリストを格納するための要素のアレイとなる
場合も大きいような位置における要素の数よりも等しいか8(及び表64のサイズよりも大きいに変換されたリスト)場合赤黒木構造TREEIFY_THRESHOLD = 8
if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash)
要素の数は赤黒木6よりも小さいです= 6連結リスト構造UNTREEIFY_THRESHOLDに変換
if (lc <= UNTREEIFY_THRESHOLD) tab[index] = loHead.untreeify(map)
アレイ機能は以下のとおりです。アドレッシング簡単に、挿入および削除の困難
リスト:機能はしているのアドレッシング、困難を挿入し、削除する簡単な
赤黒木:平衡二分探索木、検索効率がリストから、効率を探して、非常に高いですO(n)はO(LOGN)に還元されます
- リストの複雑な構造より赤黒木構造は、数回のノードのリストは、それが全体的なパフォーマンスから思われる、アレイの構造は、+ +リスト赤黒木は、必ずしもリンクリスト+のアレイの構造性能よりも高くないかもしれません
- HashMapの頻繁な拡張は、分割を引き起こすし続け、非常に時間がかかる赤黒木の底部を再構築します。
したがって、リストの長さは、赤黒木に長い時間であり、大幅に効率を向上させます
なぜテーブル容量は2の倍数でなければなりませんか?
データへのアクセスを容易にするために、代わりのモジュラ算術演算によってビットに
ハッシュ・(N-1)が記憶された位置にあります
// index 为元素在table数组中存放位置
// n = table.length
// hash 为key的hash
index = (n - 1) & hash;
// 有可能1都集中在前16位中
// 而导致明明相差很大的数据 因为后16位相同而发生冲突
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 右位移16位, 正好是32bit的一半, 自己的高半区和低半区做异或,
// 就是为了混合原始哈希码的高位和低位, 以此来加大低位的随机性.
// 而且混合后的低位掺杂了高位的部分特征, 这样高位的信息也被变相保留下来.
2の倍数を行う方法のテーブル容量?
ビット操作によって2の最小倍数又は適切な方法を得るために、入力パラメータに等しいより大きいと
/**
* 返回大于等于给定参数的值(2的倍数)
* 首位为1 其余为0
* cap最大为: 1 << 30
*
* 先求全1 再加1 --> 1111 + 1 = 1 0000
*/
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;
}
- なぜあなたは?1つの操作減じたものキャップたいです
これを防止するためである後、キャップは、キャップが2の力を持っている場合は2の力を持っており、この操作マイナス1を実行しませんし、いくつかの符号なし右シフト演算の実装の後ろに終了この容量は2倍キャップが返されます。
- 入力パラメータは確かではないので、1ビット1 0最上位ビットが1でいる
最高レベルと最高レベルは1にハイ時間であろう:右に初めて
第2の右:最初の二つ3~4及び上位2つの意志1
第三右:トップ4の前に8トップ4と5は、1のためになります
...
すべて1 MSB以下のとおりであります
- 最終的な閾値は、初期化するために)、リサイズ(所与のみ一時的に記憶されます
// initial capacity was placed in threshold
else if (oldThr > 0)
newCap = oldThr;
エントリ構造は何ですか?
エントリ単一のキーと値のペア、操作の簡単なキー、値を提供します
interface Entry<K,V> {
// 返回该实例存储的Key
K getKey();
// 返回该实例存储的Value
V getValue();
// 替换该实例存储的value
// 返回原有value值
V setValue(V value);
// 判断两实例相等的方法
// 一般指定两者的Key, Value均要相等
boolean equals(Object o);
// 获取该实例的hashCode
// 一般为该实例的唯一标识
int hashCode();
// 返回一个比较Entry key值的比较器
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
// 返回一个比较Entry value值的比较器
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
// 通过给进比较key值的比较器 来获得一个比较Entry的比较器
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
// 通过给进比较value值的比较器 来获得一个比较Entry的比较器
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
}
ノード:HashMapの特定の実装のエントリ
場合軽減する手段として主目的処理ハッシュ衝突、ノードは、リンクリストノード構造である
(計算されたハッシュメモリ位置が素子上の次のノードのような要素次に、素子上に描かれています)
static class Node<K,V> implements Map.Entry<K,V> {
// hash, key一般赋值后不能被修改
final int hash;
final K key;
V value;
// 存放下一节点
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
// 该Node实例的hashCode是key的hashCode和value的hashCode相异或
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
// key,value都相等
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
競合をハッシュへのアプローチ
- アドレス可能なオープン
紛争一度限り、ハッシュテーブルは十分な大きさであるように、空のハッシュアドレスは常に見つけ、レコード店ができ、次の空のハッシュアドレスを探して行ってきました - チェーンアドレス方法は、
ノードのリンクリストとして、ハッシュテーブルの各セルのヘッダには、インデックス要素の同義語のリストを構成する全てのハッシュアドレス
、すなわち競合をチェーン部にキーを置くためには、ヘッドノードでありますリストの末尾 - 再びハッシュ
衝突までの他のアドレスの競合が発生するまでされていないときに、別のハッシュ関数ハッシュアドレス計算機能 - 共通のオーバーフロー領域の確立は、
二つの部分、ハッシュテーブルの基本テーブルとオーバーフローテーブルに分割され、紛争の要素は、オーバーフローテーブルに配置されています
HashMapの初期化または拡張リサイズ()
- それは初期化を開始した起動するまでのHashMapは、データを追加した後(しきい値によって)追加の容量が必要とされているかどうかを判断するために開始されます
- パラメータがデフォルトマップ容量閾値として設定されていない場合は(この時点で提供される)16 9
- 膨張、及び閾値は容量を倍増している場合
/**
* 初始化或数组容量翻倍
*/
final Node<K, V>[] resize() {
// 获得原有全局表 table
Node<K, V>[] oldTab = table;
// 获得原有表的容量
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 获得原有表的阈值 容量*负载因子
// 如果设置了初始容量 threshold等于设置的初始容量(大于等于输入参数的最小的二倍数)
// 如果没有则为0
int oldThr = threshold;
// 设置新表的容量和阈值
int newCap, newThr = 0;
// 如果原有表不为空
if (oldCap > 0) {
// 如果原有表的容量大于等于最大容量 不用扩展
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
// 如果旧表的容量 大于16 翻倍后小于最大容量
} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
// 新表的阈值也是旧表阈值的两倍
newThr = oldThr << 1;
// 如果旧表为空 并且事先设置了参数 threshold不为空
} else if (oldThr > 0)
// 使用初始化的旧表阈值做新表的容量
newCap = oldThr;
// 旧表为空 没有设置参数 threshold为空
else {
// 新表容量 使用默认初始容量 16
newCap = DEFAULT_INITIAL_CAPACITY;
// 新表阈值为 16*0.75 = 12
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 以上就处理完 新表的参数 容量newCap
// 此时 旧表为空 并且设置了参数 threshold不为空
if (newThr == 0) {
// 使用新表的容量计算新表的阈值
float ft = (float) newCap * loadFactor;
// 新表的容量小于最大容量 计算的新表阈值也小于最大容量 则获得新表阈值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
// 以上就处理完 新表的参数 容量newCap和阈值newThr
threshold = newThr;
@SuppressWarnings({"rawtypes", "unchecked"})
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
table = newTab;
// 旧表不为空 开始扩容
if (oldTab != null) {
// 遍历旧表
for (int j = 0; j < oldCap; ++j) {
Node<K, V> e;
// 获取旧表不为空元素
if ((e = oldTab[j]) != null) {
// 将该位置置为空
oldTab[j] = null;
// 如果该位置只有一个Node 没有下一Node
if (e.next == null)
// 通过indexFor存入新表中
newTab[e.hash & (newCap - 1)] = e;
// 判断该位置Node的链接Node是什么结构?
// 树形结构 红黑树
else if (e instanceof TreeNode)
((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
// 链状结构 链表
else {
// head 指向链表头部 tail 构建整个链表
Node<K, V> loHead = null, loTail = null;
Node<K, V> hiHead = null, hiTail = null;
Node<K, V> next;
do {
// 先取到该位置的下一节点
next = e.next;
// e.hash & oldCap = 0 则该Node不需要移位
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
} else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 低位链表不需要移动 在新表中也是原有位置
// 直接放置链表
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 高位链表移动旧表的容量步数
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
計算されたハッシュHashMapの要素
そして、それらの16ビットの高いハッシュコードまたは異なる
ハッシュ以上の離散した要素は、より均一な分布というように
、我々は比較的小さな容量を有する一般に、2 ^ 16は、
位置の値を算出する重要な要素であるhash&(n-1)
、実質的にごくわずかに低いので、情報は、有効なハッシュの衝突であります大きな
衝突緩和は、操作に関与している情報は、ある程度高くすることができれば
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
高鍵ハッシュ値計算のデザイナーも行われる(16ビットXOR演算高いと、これを行うと、このときの計算は、高い結合に実際に低く、低い)確率を増大させます、衝突の競合の可能性を減らします
HashMapの追加/更新の要素
- テーブルはサイズを変更するために初期化されていないかどうかをまずチェック()を初期化します
- (N-1)取得した格納位置記憶素子の位置をハッシュすることによって空の場合&
- 重要な要素は、ハッシュ衝突の値に等しい発生、尾/リーフノードに要素を追加しない場所を見つけます
- 古い値を返し、その値の値を更新するための要素の値を等しくするための鍵を探します
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab;
Node<K,V> p;
int n, i;
// 如果当前表为空 还没创建 或者创建了 但表的大小为0
if ((tab = table) == null || (n = tab.length) == 0)
// 初始化表格
n = (tab = resize()).length;
// i = (n - 1) & hash 通过位运算得到需要存放的位置
// 如果该位置为空 则直接存储
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 发生hash冲突
else {
// 获取存放位置的元素
Node<K,V> e;
K k;
// p 是已存放元素
// 判断p是否与将要存放的元素key相等
// 如果key相等 表示是更新元素
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// key不相等 并且p为树状结构节点
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// key不相等 并且p为链状结构节点
else {
for (int binCount = 0; ; ++binCount) {
// 找到最后一个节点
if ((e = p.next) == null) {
// 添加节点
p.next = newNode(hash, key, value, null);
// 如果该链表长度大于等于7 则转化为红黑树结构
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 继续判断查询节点是否与将要存放的元素key相等
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果存放的位置不为空
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 如果表格元素个数大于阈值 则扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
HashMapの値
- テーブルが空で分析すると、空またはnullの場合
- ハッシュ・(n-1)のチェックヌルの位置を空に空に戻されているか否か
- 復帰要素の要件を満たすために、最初の要素かどうかを判断します
- ハッシュ衝突の発生は、要素が返されヌル見つからない返し、他のノードの位置を参照します
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab;
Node<K,V> first, e;
int n;
K k;
// 当table不为空 并且hash位置上存有元素
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 检查该位置的第一个元素
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 如果第一个元素 不符合查询条件
// 则表示有可能是hash冲突
if ((e = first.next) != null) {
// 如果首位元素是树节点
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// 首位元素是链表节点 遍历
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
HashMapの要素を削除します
- テーブルが空で分析すると、空またはnullの場合
- getNode(ハッシュキー)と同じプロセスでキーの値に基づいて、資格の要素を検索します
- 要素を探し、それを削除し、その要素を返します。
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab;
Node<K,V> p;
int n, index;
// 判断table是否为空 并且hash位置上存有元素
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e;
K k;
V v;
// 以下为找出符合要求的元素 根据key值 "getNode(hash, key)"
// 首位元素
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
// 发生hash冲突 该位置其他节点
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
// 找打符合要求的元素node
// matchValue 表示是否需要匹配value值 false表示不用 即使输入value不对 也可以删除该元素
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
HashMapのはなぜスレッドセーフではありませんか?
それはですのでメソッドはスレッドセーフではありません
HashMapの同時実行のシナリオでどのような問題が存在する可能性がありますか?
データ損失データは、死のサイクルを繰り返し、
- ダグ・リーは書いています:
上記を通じてJava7ヌルデータの損失があり、同時にこのステートメントテーブルを実行する2つのスレッドがある場合は[I] =エントリ二つのスレッドメトロポリタンエリアを作成する理由ので、ソースコード解析に格納されたデータの損失が存在することになります。
2つのスレッドがある場合は自分自身を見つけるキーが存在せず、2つのスレッドが実際にリストに自分の電子入力のために設けられた第一のスレッドに書き込まれた同じキー、および第二のスレッドの実行されていますe.nextに、この時間はまだ保有データが現れリストに挿入されて所有することになり、最後のノードを得ました
数据重复
。
ソースコードは、データが最初のマップに書き込まれ、プットを通じて見つけることができ、その後、要素のサイズを変更するためによると数を行うかどうかを決定する。
プロセスで無限ループのサイズを変更し、よりトリッキーな問題があるでしょう。
ハッシュマップは、リンクされたリスト処理の逆の順序で処理をリサイズその理由は主に
2つのスレッドが同時にリサイズと仮定される、プロセスの最初のスレッドのA-> Bが比較的遅い、第二のスレッドは逆のプログラミングが完了しましたCPU使用率の急増が存在することになるので、BAは、その後のサイクル、B-> A-> Bが現れました。
PS:このプロセスでは見つけることができ、主にジャワ8にリストするための逆過程で、無限ループの結果では、使用逆リストで、無限ループの問題が大幅に改善されていないされています。
デバッグをさらにハッシュマップによって理解されます
環境
- IntelliJ IDEA 2018 Professional Editionの
- DeepinのLinux 15.9のデスクトップ
プロセス
テストコード
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
/**
* 通过debug查看HashMap存储数据时的结构
*
* @author lingsh
* @version 1.0
* @date 19-9-25 下午4:29
*/
public class TestHashMap {
public static void main(String[] args) {
// size 存放数据量
// cap HashMap初始设置容量 10 --> 16
int size = 10000, cap = 10;
Map<THMString, Integer> map = new HashMap<>(cap);
// 存放数据
for (int i = 0; i < size; i++) {
map.put(THMString.getRandomString(), i);
}
// 用于定点调试(只是该main函数的终止位置, 可以用于暂停查看map结构)
System.out.println(map.size());
}
}
/**
* TestHashMapString
* hashCode分布极其集中的自定义类
* 底层是LEN大小的字符串
*/
class THMString {
/**
* 底层真实数据
*/
private String str;
/**
* SIZE 用于加强实例碰撞的可能性
*/
private final static int SIZE = 1024;
/**
* 底层数据大小
*/
private final static int LEN = 5;
/**
* 使用随机数来确定底层数据内容
*/
private static Random random = new Random();
public THMString(String str) {
this.str = str;
}
@Override
public int hashCode() {
// 集中hashCode 通过不断的取余来加强碰撞可能
return str.hashCode() % SIZE % SIZE % SIZE % SIZE % SIZE;
}
@Override
public boolean equals(Object obj) {
THMString thms = (THMString) obj;
return Objects.equals(this.str, thms.str);
}
@Override
public String toString() {
return "[String:" + str + "\thashCode:" + hashCode() + "]";
}
/**
* 获取随机的实例作为HashMap的Key
*
* @return 随机的实例
*/
static THMString getRandomString() {
char[] chars = new char[LEN];
for (int i = 0; i < chars.length; i++) {
int word = random.nextInt('z' - 'a');
chars[i] = (char) (word + 'a');
}
return new THMString(new String(chars));
}
}
目標達成
基礎となるデータ構造
- ノード
- TreeNode
拡張プロセス
冒頭に拡張
拡張モバイルノードに新しいテーブル
デバッグのヒント
デバッグIDEAクローズドクラス構造の最適化
次回、newThrなど:このオプションは、などIDEA隠しパラメータをオンにすると、
見つかったルートテーブルストレージジャー
ソースコードのデバッグには、私たちの個人的な使用のHashMapに、システムがそれを使用するプログラムを実行するだけでなく、私たちは、初期化時のHashMapのアクセスシステムコール値をキャッチする可能性があるため、
したがって、提案
- 自分の手順を与えるブレークポイントを設定し、自分のプログラムを起動すると判断して、ブレークポイントそのHashMapのソースを設定