前言:Map 作为key-value 数据结构存放数据,它的内部结构是怎么的,它的存放和获取又是怎样的;本文对jdk8 库中对HashMap的数据存放进行探究。

1 使用:

// 声明
Map<String,Object> map = new HashMap(10, (float) 0.8);
// 放入元素
map.put("key","value");
map.put("key","123");
// 获取元素
map.get("key");
// 移除元素
map.remove("key");
// 元素的数量
map.size();
// entry 遍历
Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {Map.Entry<String, Object> entry = it.next();System.out.println("key:" + entry.getKey() + " "+ "Value:" + entry.getValue());
}

2 过程:
参数:

public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {// 默认初始容量 16static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;// 最大容量static final int MAXIMUM_CAPACITY = 1 << 30;// 默认负载因子 0.75static final float DEFAULT_LOAD_FACTOR = 0.75f;// 当链表中的元素个数大于等于 8,并且数组的长度大于等于 64 时将链表转为红黑树static final int TREEIFY_THRESHOLD = 8;// 当链表中的元素个数大于等于 8,并且数组的长度大于等于 64 时将链表转为红黑树static final int MIN_TREEIFY_CAPACITY = 64;// 当红黑树的长度小于 6 时转为链表static final int UNTREEIFY_THRESHOLD = 6;// 第一次使用时,才进行初始化操作transient Node<K,V>[] table;// 阈(yu)值,由负载因子和容量决定:CAPACITY * LOAD_FACTOR,默认为 16 * 0.75 = 12// 当哈希桶数组内的节点数大于该值时,则扩容int threshold;/** tab 数组的长度* The number of key-value mappings contained in this map.*/transient int size;/*** The number of times this HashMap has been structurally modified* Structural modifications are those that change the number of mappings in* the HashMap or otherwise modify its internal structure (e.g.,* rehash).  This field is used to make iterators on Collection-views of* the HashMap fail-fast.  (See ConcurrentModificationException).*/transient int modCount;
}

2.1 new HashMap:

Map<String, Object> map = new HashMap(20);
// 无参构造
public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 指定初始容量
public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 指定集合转化为 Map
public HashMap(Map<? extends K, ? extends V> m) {  this.loadFactor = DEFAULT_LOAD_FACTOR;  putMapEntries(m, false);
}
// 指定初始容量和加载因子
public HashMap(int initialCapacity, float loadFactor) {// 对初始化数组的长度和负载因子进行校验if (initialCapacity < 0)// 初始化长度不能为0throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)// 长度超过int 32 位的最大长度initialCapacity = MAXIMUM_CAPACITY;// 取最大长度if (loadFactor <= 0 || Float.isNaN(loadFactor))// 负载因子数字判断throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;// 赋值负载因子this.threshold = tableSizeFor(initialCapacity);// 扩容阈值 初始化下一次需要扩容时 的长度
}
/**  返回一个大于 cap 的最小的 2 的 n 次幂,比如 cap=100,则返回 128。 cap=16,则返回 16* Returns a power of two size for the given target capacity.*/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;}

2.2 put(K key, V value) :

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}
//  得到key 的hash 数值
static final int hash(Object key) {int h;// k 的hashCode 与k 的hashCode的高16位进行异或return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/*** Implements Map.put and related methods** @param hash hash for key* @param key the 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*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {// 变量声明数组 tab = null; p= null; n;iNode<K,V>[] tab; Node<K,V> p; int n, i;// 将table 赋值给tab ;将tab 的长度赋值给nif ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// tab 长度为空,说明没有进行初始化,需要进行初始化//   获得对应tab 的位置坐标,并将改位置的值赋值给p // 如果p 节点为null ,则表示改tab 位置没有被元素占用可以直接放入数组中if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);// 初始化node 节点 ,将改节点放入tab 数组中else {// 如果发现tab 数组中改位置已经有元素占用// 声明e = null ; kNode<K,V> e; K k;// 如果tab 位置中元素的hash 值和要放入元素的hash 值相同并且// 此时tab位置中的元素和将要放入的元素key 是相同的,则将tab位置中的元素 赋值给e if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// 如果发现tab 位置中元素和要放入的元素key 不是同一个,// 并且 tab 位置中元素 是树结构则进入树结构进行节点的存入else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {// 否则为链表结构则进入链表结构数据的存入// 没有循环结束条件的 forfor (int binCount = 0; ; ++binCount) { // 将tab 位置中元素的下一个节点 赋值给e,//  如果发现下一个节点为null ,则可以将新的node 加点加入到链表中if ((e = p.next) == null) {// 初始化节点并放入到单向链表中p.next = newNode(hash, key, value, null);// 如果链表的长度大于等于7if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash);// 进入链表转换为红黑树的逻辑break;}// 如果 e 节点的key 和要存入node 节点的key 是相同的,// 则跳出循环(后续将value 直接替换 到原有node 节点的value)if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;// 等价于 p=p.next;}}// 如果e 不为null 则在原有的结构中 找到了与本次要存入key 相同的node 节点,// 则执行替换操作if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;// 替换完成返回本次要存入的value}}++modCount;// modCount 自增// 将元素的长度+1 ;如果增加元素后发现 长度已经达到扩容的条件if (++size > threshold)resize();// 进行扩容afterNodeInsertion(evict);return null;
}// Create a regular (non-tree) node
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {return new Node<>(hash, key, value, next);
}
Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;
}

resize() table 的初始化或者数组达到扩容需求:
此方法用来对hashMap进行 初始化(如果没有进行过初始化),或者数组长度达到阈值时进行扩容

final Node<K,V>[] resize() {// table 数组赋值给 oldTab Node<K,V>[] oldTab = table;// 如果原有的table 是null 则返回0 ,否则返回 原有的table 长度;//  第一次初始化时 oldTab为null ,oldCap =0int oldCap = (oldTab == null) ? 0 : oldTab.length;// 将扩容的阈值赋值给oldThr int oldThr = threshold;// 声明newCap = 0;newThr =0int newCap, newThr = 0;if (oldCap > 0) {// table 已经被初始化即扩容时进入// 如果 之前的table 数组长度已经大于int 最大值if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;// 扩容阈值赋值为int 的最大值return oldTab;// 直接返回原有的 table}// 将原有的table 的长度赋值给 newCap ,左移一位(相当于*2)//  即新的数组长度比老的长度扩大一倍// table 长度大于等于 16else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)// 将原有的扩容阈值 *2 赋值给newThr newThr = oldThr << 1; // double threshold}// 如果扩容的阈值大于0 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr;// 将阈值赋值给newCap else {               // zero initial threshold signifies using defaults// 如果原有的阈值为0 则进行初始化newCap = DEFAULT_INITIAL_CAPACITY;// 数组新的长度 newCap = 16// 新的阈值newThr = 0.75*16= 12newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 如果阈值为0,初始化阈值if (newThr == 0) {float ft = (float)newCap * loadFactor;// ftnewThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}// 赋值阈值threshold = newThr;// 新的数组@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];// 将新的数组赋值给tabletable = newTab;// ,第一次初始化时oldTab  为null;// 后续如果达到数组长度扩容时需要对之前table 的数据进行转移if (oldTab != null) {// 此时oldCap 已经进行一次左移操作(相当于*2)for (int j = 0; j < oldCap; ++j) {// 声明e =nullNode<K,V> e;// 依次将算作的元素赋值给 e if ((e = oldTab[j]) != null) {// 如果e 不为null 则代表有数据,需要向新的数组转移oldTab[j] = null;// 将oldtable 改位置的元素置空// 如果e 节点的下一个节点为null ,则证明改数组位置只有一个节点if (e.next == null) // 在新的newTab 重新计算数组下标并赋值newTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)// 如果该位置是树节点// 树节点数据的转移((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve order  该位置是链表结构// 声明  loHead = null, loTail = nullNode<K,V> loHead = null, loTail = null;// 声明   hiHead = null, hiTail = nullNode<K,V> hiHead = null, hiTail = null;// 声明next = nullNode<K,V> next;do {// 链表的下个节点next = e.next;//  链表节点的hash 对扩容后的2倍oldCap 取与,if ((e.hash & oldCap) == 0) {if (loTail == null)// 初始loTail 为nullloHead = e;// 将e 节点赋值给loHead  节点elseloTail.next = e;loTail = e;// 将e 节点赋值给loTail}else {//   链表节点的hash 对 之前table的length 取模下标不为0if (hiTail == null)hiHead = e;// hiHead 执行eelsehiTail.next = e;hiTail = 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;
}

扩容时红黑树的处理:
split(this, newTab, j, oldCap):

/**
** map HashMap 自身, tab 新的数组,index 当前数组下标,bit 扩容后的数组长度
**/
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {// 将数组的e 节点 赋值给b TreeNode<K,V> b = this;// Relink into lo and hi lists, preserving order// 声明节点TreeNode<K,V> loHead = null, loTail = null;TreeNode<K,V> hiHead = null, hiTail = null;int lc = 0, hc = 0;// 将节点赋值给e ,声明next 。循环条件 e不为null;下次遍历,将next 赋值给e// e.hash & bit) == 0 构建为条件;构建出2个双向链表for (TreeNode<K,V> e = b, next; e != null; e = next) {// 获取节点的下一个节点next = (TreeNode<K,V>)e.next;// 下一个节点 置nulle.next = null;// 计算e。hash 的数组下标if ((e.hash & bit) == 0) {// loTail 赋值给e 的前置节点,第一次 e.prev = nullif ((e.prev = loTail) == null)loHead = e;// loHead节点指向eelseloTail.next = e;loTail = e;// loTail  指向e++lc;}else {//  计算e。hash 的数组下标 不为0// hiTail赋值给e 的前置节点,第一次 e.prev = nullif ((e.prev = hiTail) == null)hiHead = e;elsehiTail.next = e;hiTail = e;// hiTail 指向e++hc;}}// loHead 不为null 说明此双向链表是有元素的if (loHead != null) if (lc <= UNTREEIFY_THRESHOLD)// 元素的长度如果小于等于6// 转换为单向链表,并赋值到原有数组下标的位置tab[index] = loHead.untreeify(map);else {// 如果元素长度大于6tab[index] = loHead;// 将首节点 放入tab 数组中if (hiHead != null) // (else is already treeified)loHead.treeify(tab);// 重新构建红黑树}}// 散列到扩容后的数组位置 hiHead  不为nullif (hiHead != null) {if (hc <= UNTREEIFY_THRESHOLD)// 长度小于等于6 构建单向链表tab[index + bit] = hiHead.untreeify(map);else {tab[index + bit] = hiHead;//  构建红黑树if (loHead != null)hiHead.treeify(tab);}}
}
/*** Returns a list of non-TreeNodes replacing those linked from* this node.*/
final Node<K,V> untreeify(HashMap<K,V> map) {// 声明hd 首节点为null,tl  尾节点指针为nullNode<K,V> hd = null, tl = null;// 循环,开始条件为双向链表首节点,// 条件为q 节点不为null,依次对双向链表向后遍历for (Node<K,V> q = this; q != null; q = q.next) {// 构建 node 节点   Node<K,V> p = map.replacementNode(q, null);// 第一次tl 为null 随后循环tl不为nullif (tl == null)hd = p;// 将当前构建的p 节点赋值给hd 节点elsetl.next = p;// 将尾节点的下一节点指向新创建的p 节点tl = p;// 将构建的p 节点赋值给tl}// 返回链表首节点return hd;
}
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {return new Node<>(p.hash, p.key, p.value, next);
}
Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;
}

当我们不断的向Map 中放入元素,当table 中某个数组位置的链表长度 大于等于7 时 进入 treeifyBin(tab, hash) 进入链表转红黑树 :

final void treeifyBin(Node<K,V>[] tab, int hash) {// 声明 n ,index ,声明e 节点int n, index; Node<K,V> e;// 如果没有进行初始化,需要先进初始化并返回;// 或者链表的长度大于7但是数组的长度小于64  也进行resize()if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();else if ((e = tab[index = (n - 1) & hash]) != null) {// 链表的长度大于7 并且数组的长度大于等于64// 得到tab 数组对应坐标下的节点并赋值给e,如果节点不为空// 声明hd,tl 节点TreeNode<K,V> hd = null, tl = null;// node 双向链表构建do {// 初始化 treeNode TreeNode<K,V> p = replacementTreeNode(e, null);// 第一次tl 为null,之后循环tl 为上一次的treeNode 节点if (tl == null)hd = p; // 初始的p 节点赋值给hdelse {p.prev = tl;// 将当前节点的前置节点指向上一节点tl.next = p;// 上一节点的下一节点指向p}tl = p;// 将p 赋值给tl} while ((e = e.next) != null);// 将链表中的下一个节点赋值给e,并且e 不为null 进入下一次循环// 双向链表构架完成 hd 指向首节点// 将首节点放入到 tab 数组中 ,并返回链表首节点if ((tab[index] = hd) != null)hd.treeify(tab);// 构建树形结构}
}
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {return new TreeNode<>(p.hash, p.key, p.value, next);
}
TreeNode<K,V> parent;  // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;    // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {super(hash, key, val, next);
}
Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);
}
Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;
}

treeify(tab) 树形结构构建(并没有将原有的双向链表结构打断):

 /** 将构建好的双向链表节点转换为红黑树,注意转换完成之后,双向链表中节点之间的prev 和next 关系依然存在
* Forms tree of the nodes linked from this node.
* @return root of tree
*/
final void treeify(Node<K,V>[] tab) {// 声明root 节点TreeNode<K,V> root = null;// 循环双向链表,初始值x = 链表的首节点;// 循环终止条件 x 等于null;x=next 依次向后遍历for (TreeNode<K,V> x = this, next; x != null; x = next) {// 获取链表的下一个节点next = (TreeNode<K,V>)x.next;// 声明 x 节点的做节点为x本身,右节点 为nullx.left = x.right = null;// 第一次循环 root 为null,后续循环 root 不为nullif (root == null) {x.parent = null;// 将x 节点的父节点 置nullx.red = false;// x 的颜色为黑色root = x;// 将x 节点赋值给root 节点}else {// 得到节点的keyK k = x.key;// 得到节点的hashint h = x.hash;// 声明kc 为nullClass<?> kc = null;// 没有循环终止条件的for 将当前x 节点放入到 treeNode 中for (TreeNode<K,V> p = root;;) {// 循环开始 p 为root 节点// 声明dir ,phint dir, ph;// 得到 p 的keyK pk = p.key;// 如果p 节点的hash 值要大于 本次要加入的x 的hash 值if ((ph = p.hash) > h)dir = -1;//  else if (ph < h)dir = 1;//  如果p 节点的hash 值要小于 本次要加入的x 的hash 值else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0)//  如果p 节点的hash 值要等于 本次要加入的x 的hash 值dir = tieBreakOrder(k, pk);// 将p 节点赋值xp 。第一次循环 xp 为root 节点TreeNode<K,V> xp = p;// 判断要放在p 的位置 dir小于等于0 p的左子节点赋值给p ,// 否则将右子节点赋值给p// 如果节点为空代表没有被元素占用可以放入,否则进入下一次循环if ((p = (dir <= 0) ? p.left : p.right) == null) { // 当前要放入的节点的父节点 指向xpx.parent = xp;if (dir <= 0)// 放入xp 的左子节点 xp.left = x;else // 放入xp 的右子节点xp.right = x;// 放入成功进行重平衡,插入和删除节点的时候通过旋转和改变节点颜色,// 让其符合红黑树的定义root = balanceInsertion(root, x);break; // 跳出循环}}}}// 确保root 节点是tab 数组中元素位的首节点moveRootToFront(tab, root);
}
/*** Ensures that the given root is the first node of its bin.*/
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {int n;if (root != null && tab != null && (n = tab.length) > 0) {int index = (n - 1) & root.hash; // root  的table 下标位置// 得到改位置的节点TreeNode<K,V> first = (TreeNode<K,V>)tab[index];if (root != first) {// 如果改位置不为root // 声明rn 节点Node<K,V> rn;// 将root 节点放入改位置tab[index] = root;//  获取root 节点的前置节点TreeNode<K,V> rp = root.prev;// root 节点的下一个节点不为空if ((rn = root.next) != null)((TreeNode<K,V>)rn).prev = rp;// 将root 下一个节点的前置节点指向 root 的前置节点if (rp != null)// 前置节点不为nullrp.next = rn; // 前置节点的下一节点指向 root 的下一节点 将root 节点从链表中剔除if (first != null)// 如果first 节点不为nullfirst.prev = root; // 将first 节点的前置节点置为 rootroot.next = first;// 将root 节点的下一节点置为 firstroot.prev = null;// 将root 的前置节点 置为null}// 校验assert checkInvariants(root);}}

当发生hash 冲突且table数组中改位置的元素为树形节点则将改节点放入到红黑树中:
putTreeVal(this, tab, hash, key, value):

/**
* map hashMap 本身,tab node 数组,h 为当前放入key-value 的key hash  ,k 为key, v 为value* Tree version of putVal.*/
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v) {Class<?> kc = null;boolean searched = false;// root 根节点赋值TreeNode<K,V> root = (parent != null) ? root() : this;for (TreeNode<K,V> p = root;;) {// 循环插入节点// 声明int dir, ph; K pk;// 将p 节点的hash 赋值给 phif ((ph = p.hash) > h)// 要插入的key hash 小于 p 的hashdir = -1;else if (ph < h)// 要插入的key hash 大于 p 的hashdir = 1;// 要插入的key hash 等于 p 的hash 并且key 是相同的,则直接返回改节点else if ((pk = p.key) == k || (k != null && k.equals(pk)))return p;// 要插入的key hash 等于 p 的hash  else if ((kc == null &&(kc = comparableClassFor(k)) == null) ||(dir = compareComparables(kc, k, pk)) == 0) {if (!searched) {TreeNode<K,V> q, ch;searched = true;if (((ch = p.left) != null &&(q = ch.find(h, k, kc)) != null) ||((ch = p.right) != null &&(q = ch.find(h, k, kc)) != null))// 遍历p的左子树和右子树 查找改相同的key 如果找到直接返回改节点 return q;}dir = tieBreakOrder(k, pk);}// p 节点赋值 给xpTreeNode<K,V> xp = p;// 如果dir <=0 将p的做子节点赋值给p ,否则将右子节点赋值给p ,// 如果p 为null 则代表改位置没有被占用可以放入元素if ((p = (dir <= 0) ? p.left : p.right) == null) {// 将节点插入到红黑树中,同时将该节点 插入到 p 和 p.next 双向链表的中间位置// p的 next 节点赋值给xpnNode<K,V> xpn = xp.next;//  初始化treeNode 节点TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);if (dir <= 0)// 放入p 的左子节点xp.left = x;else// 放入p 的右子节点xp.right = x;// 将x 节点插入到p 和p的下一个节点中间xp.next = x;// p 的下一个节点指向 初始化的x// x 节点的上一节点指向p ,x 的父节点指向px.parent = x.prev = xp;if (xpn != null)// 之前p 的下一节点((TreeNode<K,V>)xpn).prev = x; // 将之前p 的下一节点的前节点指向刚插入的x节点// 重平衡红黑树 并保证root 节点为首节点moveRootToFront(tab, balanceInsertion(root, x));return null;// 插入成功返回null}}
}

2.3 获取元素get(“key”):

public V get(Object key) {Node<K,V> e;// 声明节点ereturn (e = getNode(hash(key), key)) == null ? null : e.value;
}/**
* Implements Map.get and related methods** @param hash hash for key* @param key the key* @return the node, or null if none*/
final Node<K,V> getNode(int hash, Object key) {// 声明tab 数组,声明首节点first,声明 节点e;声明n,kNode<K,V>[] tab; Node<K,V> first, e; int n; K k;// table 数组赋值给tab ,数组不为空并且数组的长度大于0,//  并且通过key 的hash 对应的table 的下标元素不为空if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// 如果找到的数组原始的hash 和 key 的hash 相同if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;if ((e = first.next) != null) {// 取得下个节点// 下个节点是 tree 结构,则从tree 中找到对应key的节点if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);do {//  从链表找到对应key 的节点if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;
}
final TreeNode<K,V> getTreeNode(int h, Object k) {// 从根节点进行遍历查找对应key 的节点      return ((parent != null) ? root() : this).find(h, k, null);
}final TreeNode<K,V> find(int h, Object k, Class<?> kc) {// 将根节点赋值给pTreeNode<K,V> p = this;do {// 声明 ph ,dir,pk int ph, dir; K pk;// 将p 的左子节点赋值给pl;右子节点赋值给pr,声明q 节点TreeNode<K,V> pl = p.left, pr = p.right, q;// 如果要查找的key 对应的hash 小于p 节点的hashif ((ph = p.hash) > h)p = pl;// 证明要查找的节点在左子树else if (ph < h)// 如果要查找的key 对应的hash 大于p 节点的hashp = pr;// 证明要查找的节点在右子树else if ((pk = p.key) == k || (k != null && k.equals(pk)))return p;//如果要查找的key 对应的hash 等于p 节点的hash 则对比key // key 相同则找到节点直接返回p 节点else if (pl == null)// 如果p 的左子节点是nullp = pr;// 将p 指向右子节点else if (pr == null)// 如果p 的右子节点是nullp = pl;// 将p 指向左子节点else if ((kc != null ||(kc = comparableClassFor(k)) != null) &&(dir = compareComparables(kc, k, pk)) != 0)p = (dir < 0) ? pl : pr;// 在此判定p 执行左子节点或右子节点else if ((q = pr.find(h, k, kc)) != null)// 从p 的右子节点寻找return q;// 找到直接返回elsep = pl;// 否则将p 的左子节点赋值给p 继续循环} while (p != null);return null;// 没有找到则返回null
}

2.4 从Map 中移除元素 remove(key):

public V remove(Object key) {Node<K,V> e;// 声明节点ereturn (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;
}
// hash:对应key 的hash 值;key :对应的key,value= null;matchValue= false
// movable= true
final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {// 声明tab 数组,p 节点,n,indexNode<K,V>[] tab; Node<K,V> p; int n, index;if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {// 如果对应key 的hash 对应的数组位置上有元素// 声明node,e 节点,声明k ,vNode<K,V> node = null, e; K k; V v;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))node = p;// 如果改数组的元素就是要找到元素将p 赋值给nodeelse if ((e = p.next) != null) {// 改数组下标的元素有后续的元素if (p instanceof TreeNode)// 如果p 是红黑树,则从红黑树中获取对应key 的节点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 不为空,既找到了改节点,则对改节点进行移除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)// 如果当前数组中对应下标位置就是node tab[index] = node.next;// 将node 的下一节点放入数组elsep.next = node.next;// 将p 从链表中移除++modCount;--size;// map 中元素的个数-1afterNodeRemoval(node);return node;// 返回node 节点}}return null;
}
// 从红黑树中获取改节点
final TreeNode<K,V> getTreeNode(int h, Object k) {return ((parent != null) ? root() : this).find(h, k, null);
}
//  map:hashMap 本身,tab 为数组,movable= true
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,boolean movable) {int n;// 声明nif (tab == null || (n = tab.length) == 0)return;// 如果hashMap 没有进行初始换则直接返回// 找到改节点key hash对应tab 数组的下标int index = (n - 1) & hash;// 将tab 数组中对应位置的元素赋值给first 节点, //声明root 节点将first赋值给root ,// 声明rl节点TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;// 将node 节点的下一节点赋值给succ ,// 声明pred 节点将node 节点的前置节点进行赋值TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;if (pred == null)tab[index] = first = succ;//将node 节点的下一节点赋值给first 节点,//并将node 节点下一节点放入到数组中else// node 节点不为root 跟节点pred.next = succ;// 将node 节点的前置节点的下一节点指向node 的下一节点if (succ != null)// node 的下一节点不为空succ.prev = pred;// 将node 下一节点的前置节点指向node 的上一节点// 此时已经将node 节点从双向链表中进行了移除if (first == null)// 如果root 节点是空的则直接返回return;// 如果root 节点的parent 不为空则说明改root 节点不为根节点if (root.parent != null)root = root.root();// 找到红黑树的根节点并赋值给root// 如果root节点为null 或者root 没有右子节点,// 或者root 没有左子节点,或者root 的左子节点// 的左子节点为null 红黑树大小,则将红黑树转换成成链表结构if (root == null || root.right == null ||(rl = root.left) == null || rl.left == null) {// 将移除了改node 节点之后的红黑树next 遍历 // 组合成单向链表并返回链表的首节点tab[index] = first.untreeify(map);  // too smallreturn;// 完成转换直接返回}// 声明p 节点为当前要移除红黑树的node节点,pl 为当前节点的左子节点,// pr为当前节点的右子节点// 声明replacement 节点TreeNode<K,V> p = this, pl = left, pr = right, replacement;// 如果改节点即有左节点又有右节点,需要找到可以上位 顶贴要删除节点的位置if (pl != null && pr != null) {// 将要移除的node 节点的右子节点赋值给s ,声明sl 节点TreeNode<K,V> s = pr, sl;//  将node 节点的右子节点的左子节点 赋值给sl ,// 如果sl 不为null ,即node 节点的右子树// 同样拥有左节点while ((sl = s.left) != null) // find successors = sl;// 继续将sl 节点赋值s 节点//  上面while 循环完成后,s,sl 指向node 右子树,最深的左节点// 将改s 节点的颜色赋值给c,将p 节点的颜色赋值给节点颜色,// 将p节点严重赋值为s 节点颜色boolean c = s.red; s.red = p.red; p.red = c; // swap colors// 声明sr 节点为s 的右子节点TreeNode<K,V> sr = s.right;// 声明pp 节点为 p 的父节点TreeNode<K,V> pp = p.parent;// p 的右子节点的左子节点为空,即p 的右子节点只有右子树if (s == pr) { // p was s's direct parent p.parent = s;// 将p 的父节点指向 s节点 s.right = p;// s 的右子节点指向p节点}else {TreeNode<K,V> sp = s.parent;if ((p.parent = sp) != null) {if (s == sp.left)sp.left = p;elsesp.right = p;}if ((s.right = pr) != null)pr.parent = s;}// 将p 的左子节点置空p.left = null;if ((p.right = sr) != null)sr.parent = p;if ((s.left = pl) != null)pl.parent = s;if ((s.parent = pp) == null)root = s;else if (p == pp.left)pp.left = s;elsepp.right = s;if (sr != null)replacement = sr;elsereplacement = p;}else if (pl != null)// 如果只有左子节点replacement = pl;// 左子节点赋值给replacement 节点else if (pr != null)// 如果只有右子节点replacement = pr;// 右子节点赋值给replacement 节点elsereplacement = p;// 改节点的左右节点都为空,将当前节点赋值给replacement 节点if (replacement != p) {// replacement 不是p 节点// 将replacement节点的父节点指向 p 节点的父节点,并将p 节点的父几点赋值ppTreeNode<K,V> pp = replacement.parent = p.parent;if (pp == null)// pp 节点为null 则原有p 节点为root 节点root = replacement;// 将root 节点执行替换的节点else if (p == pp.left)// p 节点不为root 节点,如果p 节点是p 父节点的左节点pp.left = replacement;// pp 的左节点指向要替换的节点else// p 节点是pp 的右子节点pp.right = replacement;// pp 的右节点指向要替换的节点// 将p 节点从树中移除p.left = p.right = p.parent = null;}// 如果p 节点是红色 直接返回root 否则执行 balanceDeletion// 即如果移除的节点是红色 则不影响红黑树的定义,否则需要重新为红黑树进行调整TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);if (replacement == p) {  // detachTreeNode<K,V> pp = p.parent;p.parent = null;if (pp != null) {if (p == pp.left)pp.left = null;else if (p == pp.right)pp.right = null;}}if (movable)moveRootToFront(tab, r);// 确保root 节点为首节点
}
// 获取红黑树的根节点
final TreeNode<K,V> root() {for (TreeNode<K,V> r = this, p;;) {if ((p = r.parent) == null)return r;r = p;}
}
final Node<K,V> untreeify(HashMap<K,V> map) {Node<K,V> hd = null, tl = null;for (Node<K,V> q = this; q != null; q = q.next) {Node<K,V> p = map.replacementNode(q, null);if (tl == null)hd = p;elsetl.next = p;tl = p;}return hd;
}

2.4 遍历HashMap:

Map<String, Object> map = new HashMap(32);
Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {Map.Entry<String, Object> entry = it.next();System.out.println("key:" + entry.getKey() + " "+ "Value:" + entry.getValue());
}

entrySet().iterator():
调用EntrySet 的 iterator()

public final Iterator<Map.Entry<K,V>> iterator() {return new EntryIterator();
}
final class EntryIterator extends HashIteratorimplements Iterator<Map.Entry<K,V>> {public final Map.Entry<K,V> next() { return nextNode(); }
}
// 调用HashIterator 的构造方法:
HashIterator() {expectedModCount = modCount;Node<K,V>[] t = table;current = next = null;index = 0;// 初始化下标// next 赋值 初始化时 next = t[0]if (t != null && size > 0) { // advance to first entrydo {} while (index < t.length && (next = t[index++]) == null);}
}
// HashIterator nextNode
final Node<K,V> nextNode() {Node<K,V>[] t;Node<K,V> e = next;// 元素赋值if (modCount != expectedModCount)throw new ConcurrentModificationException();if (e == null)throw new NoSuchElementException();// next 下一元素赋值if ((next = (current = e).next) == null && (t = table) != null) {do {} while (index < t.length && (next = t[index++]) == null);}return e;
}
// 遍历时元素判断
public final boolean hasNext() {return next != null;
}
// 遍历时下一元素获取 it.next()
final class EntryIterator extends HashIteratorimplements Iterator<Map.Entry<K,V>> {public final Map.Entry<K,V> next() { return nextNode(); }
}

3 总结:
3.1 HashMap 的数据结构使用的是数组+单向链表+红黑树(红黑树结构中包包含了双向链表)的数据结构,HashMap 数据结构是在put 元素的时候进行的初始化;
3.2 在向HashMap 存放元素时,如果发生hash 碰撞会先在对应碰撞元素的数组位置形成单向链表,如果单向链表的长度大于7并且数组的长度大于64 则触发单向链表转换为红黑树;
3.3 在HashMap 中数组的长度达到阈值时回触发resize() ,先将现有的数组容量扩大一倍,然后将原有的数据向新的数组中转移;转移过程中,如果发现之前时红黑树的节点在转移时元素数量小于等于6则会触发红黑树转链表结构;
3.4在对HashMap 进行get 时,会先找到对应数组下标位置的元素进行比对,如果key 相同则返回,否则判断改数组下标位置是红黑树则进入红黑树从根节点进行查找;是链表则从链表的首节点依次向后查找;
3.5 在对HashMap 进行remove时,会先找到对应数组下标位置的元素进行比对,如果该位置只有一个元素且key 是相同的则直接进行移除;如果发现改节点右其他元素,改节点是红黑树,则将改节点从红黑树中进行移除,移除后如果发现红黑树过小则触发红黑树向链表结构转变;如果是链表则从链表中移除改元素;

4 参考:

JAVA数据结构篇--5理解HashMap相关推荐

  1. Java入门算法(数据结构篇)丨蓄力计划

    本专栏已参加蓄力计划,感谢读者支持 往期文章 一. Java入门算法(贪心篇)丨蓄力计划 二. Java入门算法(暴力篇)丨蓄力计划 三. Java入门算法(排序篇)丨蓄力计划 四. Java入门算法 ...

  2. 红黑树在java中的作用_带你真正理解Java数据结构中的红黑树

    红黑树是平衡的二叉树,它不是一个完美的平衡二叉树,但是在动态插入过程中平衡二叉搜索树的代价相对较高,所以红黑树就此出现,下面就让爱站技术频道小编带你一起进入下文了解一下吧! 一.红黑树所处数据结构的位 ...

  3. Java 集合系列10: HashMap深入解析(1)

    戳上面的蓝字关注我们哦! 精彩内容 精选java等全套视频教程 精选java电子图书 大数据视频教程精选 java项目练习精选 QQ群:766946816 概要 这一章,我们对HashMap进行学习. ...

  4. 深入理解HashMap和TreeMap的区别

    文章目录 简介 HashMap和TreeMap本质区别 排序区别 Null值的区别 性能区别 共同点 深入理解HashMap和TreeMap的区别 简介 HashMap和TreeMap是Map家族中非 ...

  5. java基础学习——5、HashMap实现原理

    一.HashMap的数据结构 数组的特点是:寻址容易,插入和删除困难:而链表的特点是:寻址困难,插入和删除容易.那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的, ...

  6. Java集合框架之三:HashMap源码解析

    Java集合框架之三:HashMap源码解析 版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! HashMap在我们的工作中应用的非常广泛,在工作面试中也经常会被问到,对于这样一个重要的集 ...

  7. 程序员基础内功夯实——数据结构篇

    结构并不固定和死板,应该在实际情况中做最贴切的设计和应用,意思就是咱怎么高兴怎么用,个人认为数据结构重要,但是更重要的是实现结构的算法,不能知其然,不知其所以然. 数据结构和算法参考(算法和数据结构- ...

  8. JAVA提高十二:HashMap深入分析

    首先想说的是关于HashMap源码的分析园子里面应该有很多,并且都是分析得很不错的文章,但是我还是想写出自己的学习总结,以便加深自己的理解,因此就有了此文,另外因为小孩过来了,因此更新速度可能放缓了, ...

  9. 【Java面试小短文】HashMap是如何解决Hash冲突的?

    欢迎关注Java面试系列,不定期更新面试小短文.欢迎一键三连! 文章目录 什么是Hash算法? 什么是Hash表? HashMap是如何解决Hash冲突的? 什么是Hash算法?   Hash 算法, ...

最新文章

  1. 安装laravel框架
  2. Spring Webflux: Kotlin DSL [片断]
  3. [转]Windows Shell 编程 第十一章 【来源:http://blog.csdn.net/wangqiulin123456/article/details/7987992】...
  4. 算法专家解读 | 开放搜索教育搜题能力和实践
  5. PHP 常用函数及其它功能
  6. 【iOS-Cocos2d游戏开发之二十】精灵的基础知识点总汇(位图操作/贴图更换/重排z轴等)以及利用CCSprite与CCLayerColor制作简单遮盖层!...
  7. 安卓手机上跑_直接在电脑上浏览操作安卓手机 #效率App #scrcpy
  8. ABP之Javascript生成
  9. ZeroClipboard实现复制
  10. Boss说:你要是能搞懂这六个分布式技术栈,我给你薪资翻倍
  11. 销售自用计算机损益计入哪里,用友创业者4.0下的ERP沙盘模拟经营规则中,销售所需紧急采购产品时,按成品直接成本的(    )倍直接扣除现金,付款即到货,紧急采购多付出的成本计入费用表损失项。...
  12. 外贸找客户软件:Top Lead Extractor
  13. 荷兰版《口袋妖怪GO》:不找小精灵,找免费啤酒!
  14. 如何在文本中添加多条线
  15. android相册幻灯片功能,玩机教程 篇四十五:「MIUI玩机技巧63」MIUI相册新增“幻灯片播放”功能...
  16. PC端页面在手机端完整显示
  17. STM32学习(二)
  18. 【教你赚钱】5分钟成为副业致富的独立开发者
  19. jeesite4 图片上传总结
  20. Win 7/Win 8/Win 10/Windows Server 下安装和使用OpenSSH客户端

热门文章

  1. JAVA实现一致性Hash算法
  2. 微信小程序 JS 字符串string与utf8编码的arraybuffer的相互转换
  3. Android Studio 选项菜单和动画结合_Android性能测试③-发现和定位内存泄露amp;卡顿...
  4. opencv 绘图 cvLine cvRectangle cvCircle cvEllipse cvEllipseBox cvFillPoly cvConvexPoly cvPolyLine
  5. 批量修改文件后缀-shell脚本
  6. 网上可以开通期货账号
  7. pixhawk mc_pos_control.cpp源码解读
  8. Splint C语言代码检测
  9. 全志A40i开发板Android应用开发指导
  10. sgx是什么要开吗_属牛女人97年出生2021年做生意好吗,血本无归