HashMap源码分析
一、准备工作
1、HashMap的几种遍历方式
package com.liu.map_;
import jdk.nashorn.internal.ir.CallNode;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Map_ {
public static void main(String[] args) {
Map<String, Staff> map = new HashMap<>();
map.put("1",new Staff("liu1",10000));
map.put("2",new Staff("liu2",20000));
map.put("3",new Staff("liu3",21200));
//法一:迭代器,获取所有key的集合
Set<String> set = map.keySet();
/* Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("key="+next+",value="+map.get(next));
}*/
//法二:加强for循环
/* for(Object o : set){
System.out.println("key="+o+",value="+map.get(o));
}*/
//法三:获取enterSet对象
Set<Map.Entry<String, Staff>> entrySet = map.entrySet();
/* Iterator<Map.Entry<String, Staff>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
//向下转型为Map.Entry 使用方法:getKey() getValue()
Map.Entry<String, Staff> next = iterator.next();
System.out.println("key="+next.getKey()+",value="+next.getValue());
}*/
//发四:enterSet加强for循环
for(Object o : entrySet){
Map.Entry entry = (Map.Entry) o;
System.out.println("key="+entry.getKey()+",value="+entry.getValue());
}
}
}
class Staff{
String name;
int salary;
public Staff(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Staff{" +
"name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
2、调试使用代码
package com.liu.map_;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.w3c.dom.ls.LSOutput;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
//HashMap调试用例
public class MapDebug {
public static void main(String[] args) {
Map<Object, Object> map = new HashMap<>();
for (int i = 1; i <= 12; i++) {
map.put(i,"value"+i);
}
map.put(13,"value13");
System.out.println(map);
}
}
class Book{
private String name;
public Book(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Objects.equals(name, book.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
'}';
}
}
二、调试
进入HashMap的构造函数
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
//初始化扩容因子(0.75) 大于容量*0.75时扩容
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
跳出HashMap
Integer装箱
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
跳出Integer
进入put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
进入hash
重新计算hash值,并返回给put方法的hash(key)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
进入hashCode()
计算hashCode值
public int hashCode() {
return Integer.hashCode(value);
}
跳出hashCode
跳出hash()
进入putVal方法
若满足条件,则进入扩容resize()方法,或是将当前元素放入到数组或链表或红黑树中
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//若table为空,则进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
//进行扩容
n = (tab = resize()).length;
//若当前位置没有元素
if ((p = tab[i = (n - 1) & hash]) == null)
//新建结点,并赋值给tab[i]
tab[i] = newNode(hash, key, value, null);
//若当前位置有元素(key值相等的情况)
else {
Node<K,V> e; K k;
//若当前加入hash值相等且key值相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//记录当前结点
e = p;
//若当前节点为TreeNode
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//若加入的元素key不同,且不为树
else {
//相当于while(true) binCount记录链表中结点数量,从0开始
for (int binCount = 0; ; ++binCount) {
//遍历p的next结点
if ((e = p.next) == null) {
//直到遍历到为null的结点,就将该结点加一个newNode(hash, key, value, null)
p.next = newNode(hash, key, value, null);
//若链表长度为8,则进行树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//若在链表中找到了与当前遍历到结点key值相等的结点,则跳出,在外面进行覆盖
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//将指针指向下一结点
p = e;
}
}
//若e有元素,进行覆盖操作
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//此哈希映射在结构上被修改的次数
++modCount;
//size当前数组需要容量
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
进入resize(HashSet中以做详细说明,此处不详细解释)
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (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];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
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;
}
newNode
// 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类
table表中结点的种类
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
//静态内部类Node实现了Map.Entry<K,V>接口
static class Node<K,V> implements Map.Entry<K,V> {
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;
}
Map.Entry接口
两个常用方法:
K getKey() :获取所有的Key
V getValue(): 获取所有的Value
遍历可用这个接口
entrySet:里面存放的是一个个Map.Entry<K, V>,Node实现了Map.Entry<K, V>接口,也可以说存放了一个个Node
Set<Map.Entry<K, V>> entrySet();
//@see Map#entrySet()
interface Entry<K,V> {
/**
* Returns the key corresponding to this entry.
*
* @return the key corresponding to this entry
* @throws IllegalStateException implementations may, but are not
* required to, throw this exception if the entry has been
* removed from the backing map.
*/
K getKey();
/**
* Returns the value corresponding to this entry. If the mapping
* has been removed from the backing map (by the iterator's
* <tt>remove</tt> operation), the results of this call are undefined.
*
* @return the value corresponding to this entry
* @throws IllegalStateException implementations may, but are not
* required to, throw this exception if the entry has been
* removed from the backing map.
*/
V getValue();
/**
* Replaces the value corresponding to this entry with the specified
* value (optional operation). (Writes through to the map.) The
* behavior of this call is undefined if the mapping has already been
* removed from the map (by the iterator's <tt>remove</tt> operation).
*
* @param value new value to be stored in this entry
* @return old value corresponding to the entry
* @throws UnsupportedOperationException if the <tt>put</tt> operation
* is not supported by the backing map
* @throws ClassCastException if the class of the specified value
* prevents it from being stored in the backing map
* @throws NullPointerException if the backing map does not permit
* null values, and the specified value is null
* @throws IllegalArgumentException if some property of this value
* prevents it from being stored in the backing map
* @throws IllegalStateException implementations may, but are not
* required to, throw this exception if the entry has been
* removed from the backing map.
*/
V setValue(V value);
/**
* Compares the specified object with this entry for equality.
* Returns <tt>true</tt> if the given object is also a map entry and
* the two entries represent the same mapping. More formally, two
* entries <tt>e1</tt> and <tt>e2</tt> represent the same mapping
* if<pre>
* (e1.getKey()==null ?
* e2.getKey()==null : e1.getKey().equals(e2.getKey())) &&
* (e1.getValue()==null ?
* e2.getValue()==null : e1.getValue().equals(e2.getValue()))
* </pre>
* This ensures that the <tt>equals</tt> method works properly across
* different implementations of the <tt>Map.Entry</tt> interface.
*
* @param o object to be compared for equality with this map entry
* @return <tt>true</tt> if the specified object is equal to this map
* entry
*/
boolean equals(Object o);
/**
* Returns the hash code value for this map entry. The hash code
* of a map entry <tt>e</tt> is defined to be: <pre>
* (e.getKey()==null ? 0 : e.getKey().hashCode()) ^
* (e.getValue()==null ? 0 : e.getValue().hashCode())
* </pre>
* This ensures that <tt>e1.equals(e2)</tt> implies that
* <tt>e1.hashCode()==e2.hashCode()</tt> for any two Entries
* <tt>e1</tt> and <tt>e2</tt>, as required by the general
* contract of <tt>Object.hashCode</tt>.
*
* @return the hash code value for this map entry
* @see Object#hashCode()
* @see Object#equals(Object)
* @see #equals(Object)
*/
int hashCode();
/**
* Returns a comparator that compares {@link Map.Entry} in natural order on key.
*
* <p>The returned comparator is serializable and throws {@link
* NullPointerException} when comparing an entry with a null key.
*
* @param <K> the {@link Comparable} type of then map keys
* @param <V> the type of the map values
* @return a comparator that compares {@link Map.Entry} in natural order on key.
* @see Comparable
* @since 1.8
*/
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());
}
/**
* Returns a comparator that compares {@link Map.Entry} in natural order on value.
*
* <p>The returned comparator is serializable and throws {@link
* NullPointerException} when comparing an entry with null values.
*
* @param <K> the type of the map keys
* @param <V> the {@link Comparable} type of the map values
* @return a comparator that compares {@link Map.Entry} in natural order on value.
* @see Comparable
* @since 1.8
*/
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());
}
/**
* Returns a comparator that compares {@link Map.Entry} by key using the given
* {@link Comparator}.
*
* <p>The returned comparator is serializable if the specified comparator
* is also serializable.
*
* @param <K> the type of the map keys
* @param <V> the type of the map values
* @param cmp the key {@link Comparator}
* @return a comparator that compares {@link Map.Entry} by the key.
* @since 1.8
*/
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());
}
/**
* Returns a comparator that compares {@link Map.Entry} by value using the given
* {@link Comparator}.
*
* <p>The returned comparator is serializable if the specified comparator
* is also serializable.
*
* @param <K> the type of the map keys
* @param <V> the type of the map values
* @param cmp the value {@link Comparator}
* @return a comparator that compares {@link Map.Entry} by the value.
* @since 1.8
*/
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());
}
}
树化临界值
当链表长度>=8时,且Node[]数组的长度>=64时,进行树化
树化临界代码
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//MIN_TREEIFY_CAPACITY=64
//链表长度到达8,但Node[]数组小于64,则resize()(先进行数组2倍扩容,直到达到64)
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
//若Node[]数组大于等于64则树化
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
回到putVal
回到put方法
添加成功
三、总结(重点)
1、hashmap中的元素存储到Node[]数组中,为k-v键值对形式
2、hashmap中的key唯一,不能重复,且key中只可以有一个null键,value值没有要求
3、hashmap扩容机制:开始时,直接扩容为16,达到16乘0.75=12时,扩容为原来的2倍,变为32,第二次时,达到 24时(24=32乘0.75或是12乘2),进行扩容,扩容为64,扩容系数变为48。
4、hashmap底层机制:数组+链表+红黑树
5、当hashmap中元素的个数size>容量*0.75时,进行扩容,扩容为原来的2倍
6、当Node[]数组中,计算出的hash值相同,但key值不同,则将当前k-v加入到链表中,或是满足条件树化。
7、当链表数量>=8且数组长度>=64时,将链表进行树化
8、s若链表中元素数量达到8,但Node[]的长度length大于64,则将Node[]数组进行扩容,变为原来的2倍。