[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GrZxJTzR-1691832408829)(java_source_imgs\1.png)]
HashMap
- key 允许null value 也可以为 Null
- HashTable 也是实现了map接口
- HashMap 和 HashTable 类似,只是HashTable 时同步的,并且不允许null (that it is unsynchronized and permits nulls)
- 不能保证映射的顺序
- hashMap 和初始化容量和load factor 相关,如果,hashMap中的元素超过了初始化容量和load factor,就会重新分配,并且容量为以前的两倍
- 取模最好的公式: hash % n = hash & (n - 1) -> 但 n 必须是 2 的次幂,看看为啥
- hashmap 是数组 + 链表 + 红黑树构成,尽管 hashmap key 和 value 都可以为null,但并不是 node 数组为 null,而是 node 的 key 和 value 属性为 null
- containsValue 为啥没关注树结构
tableSizeFor
根据入参,返回大于入参&2的次方(但是不能大于 1<<30)
n |= n >>> 1 表示 n 的后一位变成 1 1000000 | 0100000 = 1100000
n |= n >>> 2; 表示 n 的第3、4位变成1 1100000 | 0011000 = 1111000
n |= n >>> 4; 表示 n 的第5~8位变成 1 1111000 | 0000111 = 1111111
如果 n 小于 0 ,则返回 1,另外需要判断是否大于 1 << 30,
n + 1 就是要保证是 2 的次方
/**
* 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;
}
getNode
/**
* 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) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// table 不为空 & table.size 不为空 & tab[(n - 1) & hash] != null(链表不为空),执行内容,否则直接返回 null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 是否第一个元素
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 判断第一个元素后续元素
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;
}
containsKey
判断是否包含当前key,应该用 containsKey ,而不是通过 get(key) == null 进行判断,HashTable 可以
/**
* Returns <tt>true</tt> if this map contains a mapping for the
* specified key.
*
* @param key The key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
containsValue
为啥没关注树
/**
* Returns <tt>true</tt> if this map maps one or more keys to the
* specified value.
*
* @param value value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value
*/
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
clear
/**
* Removes all of the mappings from this map.
* The map will be empty after this call returns.
*/
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
构造函数
putMapEntries 这个函数比较有意思,
1. float ft = ((float)s / loadFactor) + 1.0F; ==为啥==
2. int s = m.size(); 这儿是要获取原先map的元素个数,并不是原先元素的容量。====
/**
* 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
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
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) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
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);
}
}
}
红黑二叉树
特性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z8hYEDDM-1691832408830)(java_source_imgs/red_black_tree_features.jpg)]
红黑二叉树旋转小技巧
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2lg4a1II-1691832408831)(java_source_imgs\red_black_1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MxZhj3zF-1691832408832)(java_source_imgs\red_black_2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCGlRccC-1691832408833)(java_source_imgs\red_black_3.png)]
情况 4
当前节点为父结点的右子树,而父结点为祖父节点的左子树(三者不在一条直线上)
解决办法
:当前节点、父结点、祖父节点不在一条线上,需要先以父结点进行左转,然后再以祖父节点进行右转,并将父结点变成黑色,而祖父节点变成红色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rcp79Inn-1691832408835)(java_source_imgs\red_black_4.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-txzuBuCY-1691832408836)(java_source_imgs\red_black_5.png)]
情况5
当前节点为父结点的左子树,父结点为祖父节点的左子树(三者在一条直线上)
解决办法
:首先以父结点进行右转,将父结点设置为黑色节点,而将祖父节点设置为红色节点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G5XPDf2F-1691832408837)(java_source_imgs\red_black_6.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T28o7lPR-1691832408838)(java_source_imgs\red_black_7.png)]
情况6
当前节点为父结点的左子树,父结点为祖父节点的右子树(三者不在一条直线上)
解决办法
:首先以父结点进行右旋,然后以祖父节点进行左旋,接着将父结点变成黑色,而祖父节点变成红色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Bm2jxC9-1691832408839)(java_source_imgs/beforechange.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SNQHhCae-1691832408840)(java_source_imgs/change_step.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4VTQ4v1-1691832408842)(java_source_imgs/change_step2.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DXzzt6XX-1691832408843)(java_source_imgs/change_step3.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RE7kxXi-1691832408844)(java_source_imgs/change_step4.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9j27g3Y-1691832408845)(java_source_imgs/change_step5.jpg)]
情况7
当前节点为父结点的右子树,父结点为祖父节点的右子树(三者在同一条直线上)
解决办法
:直接以父结点进行左旋,并将父结点设置为黑色,祖父节点设置为红色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1OqCK7WI-1691832408846)(java_source_imgs/right_right_before.png)]
rotateLeft
左转的可能性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILykvjqe-1691832408847)(java_source_imgs/rotateLeft .png)]
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) {
// r 结点表示 p 结点的右结点
// p 结点存在,p 结点的右结点存在
if ((rl = p.right = r.left) != null)
// 如果 r的左结点存在,要把 左节点放置在 p 的右结点
rl.parent = p;
if ((pp = r.parent = p.parent) == null)
// p.parent 父结点为空, root = r(不知道那种情况)
(root = r).red = false;
// pp 表示 p 结点的父结点
else if (pp.left == p)
// 如果 p 为 pp 的左孩子
pp.left = r;
else
// 如果 p 为 pp 的右孩子
pp.right = r;
// 一定要让 r.left = p; p 的父结点 == r
r.left = p;
p.parent = r;
}
return root;
}
rotateRight
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IiTQVlwr-1691832408848)(java_source_imgs/rotateRight.png)]
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
if (p != null && (l = p.left) != null) {
// 判断 p 和 p 的左结点是否为空
if ((lr = p.left = l.right) != null)
// p 的左结点等于 l 的右结点
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
// 如果,父结点为 空, 就将 root == r; 并把 r.red = false
(root = l).red = false;
// pp 表示 p 的父结点
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}
balanceInsertion
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;;) {
// 1
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
// 2
else if (!xp.red || (xpp = xp.parent) == null)
return root;
// 3
if (xp == (xppl = xpp.left)) {
// 3.1
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else { // 3.2
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
//4
else {
// 4.1
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else { // 4.2
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);
}
}
}
}
}
}
- xp == x.parent
- xpp == xp.parent
- xppl == xpp.left
- xppr == xpp.right
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DKGlIHcr-1691832408850)(java_source_imgs\balanceInsertNode.jpg)]
-
表示 x 就是根节点,直接插入,然后 修改 red == false
-
如果 父节点 不是 红色 或者
xpp == null ;// 表示 xp 就是根节点,xp 的颜色肯定就是 黑色 -
xp == xppl == xpp.left; 表示 x 的父节点 xp 是 祖父结点的左子树
3.1 (xppr = xpp.right) != null && xppr.red 表示 右子树存在,并且是红色。直接 父节点和叔叔结点变黑,祖父结点变红
·3.2 表示叔叔结点为空或者为 黑色·
(这里有一步 x = xp),在旋转之后,x 转到最下面,然后方便 xp == x.parent; xpp = xp.parent
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hk4bCjyV-1691832408851)(java_source_imgs\balanceInsert_right_node.jpg)]
// 表示当前结点 x 是 xp 的右子树,那么就需要左转 if (x == xp.right) { root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; }
// xp != null; 变成黑色,如果祖父结点不为空, 祖父结点变成红色,并且右转 if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateRight(root, xpp); } }
split
/**
* Splits nodes in a tree bin into lower and upper tree bins,
* or untreeifies if now too small. Called only from resize;
* see above discussion about split bits and indices.
*
* @param map the map
* @param tab the table for recording bin heads
* @param index the index of the table being split
* @param bit the bit of hash to split on
*/
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
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;
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
// e.hash & bit == 0 说明 e.hash < bit,就会连接到 小段
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) {
if (lc <= UNTREEIFY_THRESHOLD) // 小于 threshold 解数
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead; // 将小段还放在 tab[index]
if (hiHead != null) // (else is already treeified) 如果 hiHead != null, 说明
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);
}
}
}
treeifyBin
如果 当前结点 个数大于 8个,就可以将链表转换成树,但是 treeifyBin 比较有意思的是,首先查看数组的长度是否小于64; 如果是这样,现在 数组扩容,而不是直接将链表转换成红黑二叉树
。// 然后最后在调用 hd.treeify(tab);
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
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);
}
}
d = 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);
}
}