HashMap
- 线程是否安全: HashMap 是非线程安全的
- 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。
- 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。
- 初始容量大小和每次扩充容量大小的不同 : HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值 HashMap 会将其扩充为2的幂次方大小
- 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
基本属性
// 默认初始化大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认负载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 链表转红黑树阈值
static final int TREEIFY_THRESHOLD = 8;
// 红黑树转链表阈值
static final int UNTREEIFY_THRESHOLD = 6;
// 该阈值作用是(当链表个数超过64,可以转红黑树,否则进行扩容)
static final int MIN_TREEIFY_CAPACITY = 64;
// hash表
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
// 实际大小
transient int size;
// 结构发生变化次数
transient int modCount;
// 最大容量,table.length * loadFactor
int threshold;
// 负载因子
final float loadFactor;
保证返回大于等于输入参数且最近的2的整数次幂的方法
static final int tableSizeFor(int cap) {
// 为什么要减一,请使用cap=8来测试这条语句作用
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;
}
HashMap的hash算法
解决hash冲突的四种办法
- 开放地址法(HashMap扩容)
- 链地址法 (HashMap的链表结构)
- 再hash法
- 公共溢出法
static final int hash(Object key) {
int h;
// 如果key为null,则hash值为0,否则调用key的hashCode()方法
// 并让高16位与整个hash异或,这样做是为更好的避免hash碰撞
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
扩容算法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
/***************************************给threshold赋值*********************************/
// 判断原表的实际长度
if (oldCap > 0) {
// 长度大于最大值
if (oldCap >= MAXIMUM_CAPACITY) {
// 最大容量赋值为int类型最大值
threshold = Integer.MAX_VALUE;
// 不能扩容,直接返回原表
return oldTab;
}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && // 这里将newCap空充两倍
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 扩容成原来两倍
newThr = oldThr << 1;
}
/** 基本上不会进入这个分支
* table中没有数据,且threshold大于0,进入该分支,实际上表示原来的table中没有数据
*/
else if (oldThr > 0)
// 这里实际上没有扩容,而是 threshold = threshold * loadFactor
newCap = oldThr;
else { // 其他状态,相当于空参构造方法
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;
/**************************************给threshold赋值结束*******************************/
@SuppressWarnings({"rawtypes","unchecked"})
// 创建一个新的Node<K,V>[],并将地址给table
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
/**
* 判断之前的table是否有数据
* 若不为空,则进行数据迁移
**/
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)
// 如果是树类型结构,且next不为null,则进入这个分支
((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;
}
// 位置有变化,存放在j+oldCap位置,这就是扩容后,改变的数据存放位置
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
若Node下面节点为树,则调用这个方法:split(this, newTab, j, oldCap);
// 和上面扩容方法逻辑一样,这里只不过是遍历树结构,增加了tree转table,以及变化后的tree,table的转换
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
TreeNode<K,V> b = this;
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
// 位置不改变的,长度是否大于8
if (lc <= UNTREEIFY_THRESHOLD)
// 直接转为Node链表
tab[index] = loHead.untreeify(map);
else {
// 生成新的树
tab[index] = loHead;
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
// 同上,只不过位置不一样,上面已经讲解过
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
生成TreeNode的方法treeify()
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
// 每次循环 x=x.next
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
// 根节点复制
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
else { // 非根节点
K k = x.key;
int h = x.hash;
Class<?> kc = null;
// 循环树,从根节点开始
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h) // 左边
dir = -1;
else if (ph < h) // 右边
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) // 发生hash碰撞
/**
* 底层调用C语言来做hash比较,
* 如果两个对象有任意一对象为null,
* 或者两个对象指的同一地址,则调用C语言的hash处理
* 否则直接返回 0
*/
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
// 通过前面的比较dir值,确定加入树末尾的那边
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 调整树的平衡,左旋,右旋
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
树的调整方法balanceInsertion()
红黑树的特性:
1. 节点是红色或黑色。
2. 根节点是黑色。
3. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
4. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true;
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
if ((xp = x.parent) == null) {
// 若是根节点,则置黑,返回根节点
x.red = false;
return x;
}
else if (!xp.red || (xpp = xp.parent) == null)
// 若x的父节点为黑色,或者x父节点为根节点(tree根节点必须是黑色),则直接返回该tree
return root;
if (xp == (xppl = xpp.left)) {
// 若x的父节点,是某节点的左子叶
if ((xppr = xpp.right) != null && xppr.red) {
// 若xpp的有有右节点,且右节点为红色
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else { //当xp是某节点的左子叶,且右子叶为空,或者右子叶不为空,但为黑色
if (x == xp.right) {
// x为某节点的右节点,则左旋
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {//若xp,xpp值都不为空,则右旋
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
else {
// 若x的父节点,是某节点的右子叶
if (xppl != null && xppl.red) {
// 若xpp的有有左节点,且右节点为红色
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}// 与上面类似,就不多说了
else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
右旋方法
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
// 左旋判断p节点是否为空,以及p节点的左子节点
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null)
// 这一步就是把l的右节点变为p的左节点
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
// 判断p是否是根节点,并将p.parent 赋值给l.parent
(root = l).red = false;
else if (pp.right == p)
// 若p是右子叶,那么l将代替他的位置
pp.right = l;
else
// 若p是左子叶,那么l将代替他的位置
pp.left = l;
// 将p和l关联起来
l.right = p;
p.parent = l;
}
return root;
}
左旋方法
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) {
if ((rl = p.right = r.left) != null)
rl.parent = p;
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
r.left = p;
p.parent = r;
}
return root;
}
hashMap的get方法
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不为空,通过tabtab[(n - 1) & hash] 找到改key的数组位置赋值给first
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//若first的key与查找的key相同,则返回first
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查找方法
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;
}
// tree查找方法
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;
//树的遍历
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q;
if ((ph = p.hash) > h)
p = pl;
else if (ph < h)
p = pr;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
else if ((kc != null ||
// 如果k是Comparable的子类则返回其真实的类,否则返回null
(kc = comparableClassFor(k)) != null) &&
// 如果k和pk不是同样的类型则返回0,否则返回两者比较的结果
(dir = compareComparables(kc, k, pk)) != 0)
// 通过compare方法比较key值的大小决定使用左子树还是右子树
p = (dir < 0) ? pl : pr;
else if ((q = pr.find(h, k, kc)) != null)
// 如果以上条件都不通过,则尝试在右子树查找
return q;
else
// 都没找到就在左子树查找
p = pl;
} while (p != null);
return null;
}
HashMap的put方法
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;
if ((tab = table) == null || (n = tab.length) == 0)
// 初始化table
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// 该table节点没Node节点
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// hash,key判断插入的位置是根Node节点
e = p;
else if (p instanceof TreeNode)
// 该节点是tree结构
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// p不是根Node节点,这里主要作用是找到,插入的位置节点Node
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
// 若p.next节点为空,则创建一个Node节点,并赋值
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1)
// 链表长度大于8,则需要判断是是否扩容,或者转红黑树
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 如果待插入的key在链表中找到了,则退出循环
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;
}
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
// 标记是否找到key
boolean searched = false;
// 给根节点赋值
TreeNode<K,V> root = (parent != null) ? root() : this;
for (TreeNode<K,V> p = root;;) {
// dir标记左右边,ph是节点的hash值,pk是p的key
int dir, ph; K pk;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
// 找到p节点
return p;
else if ((kc == null &&
// 如果k是Comparable的子类则返回其真实的类,否则返回null
(kc = comparableClassFor(k)) == null) ||
// 如果k和pk不是同样的类型则返回0,否则返回两者比较的结果
(dir = compareComparables(kc, k, pk)) == 0) {
// 这个条件表示两者hash相同但是其中一个不是Comparable类型或者两者类型不同
// 比如key是Object类型,这时可以传String也可以传Integer,两者hash值可能相同
// 在红黑树中把同样hash值的元素存储在同一颗子树,这里相当于找到了这颗子树的顶点
// 从这个顶点分别遍历其左右子树去寻找有没有跟待插入的key相同的元素
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))
return q;
}
// 如果两者类型相同,再根据它们的内存地址计算hash值进行比较
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
// 从跟向下遍历
if ((p = (dir <= 0) ? p.left : p.right) == null) {
// 如果最后确实没找到对应key的元素,则新建一个节点
Node<K,V> xpn = xp.next;
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
// 插入树节点后平衡
// 把root节点移动到链表的第一个节点
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}