一、概述:
● 基于HashMap实现,不存在重复元素
● 无序无索引但存在null值
● 底层数据结构:数组+链表(红黑树)
● 扩容机制:初始通量为16,加载因子:0.75,当存储元素容量达到:16*0.75时,扩容为:原容量的2倍。
二、底层数据结构图示
三、boolean add(E e)添加元素:
// 虚拟值占据位置,标识该对象在映射中存在
private static final Object PRESENT = new Object();
//
private transient HashMap<E,Object> map;
public boolean add(E e) { //接受参数
return map.put(e, PRESENT)==null; //调用 HashMap 的put 方法添加元素
}
public V put(K key, V value) {
// 调用hash方法计算出哈希值找到对应存储的位置
return putVal(hash(key), key, value, false, true);//调用putVal方法
}
static final int hash(Object key) {
int h;
// 获取key的哈希码,将其进行异或运算
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
transient Node<K,V>[] table;
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) //如果为真,则条用resize()方法扩容
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// 根据计算得到的哈希值和数组长度,找到存储位置的索引 i。如果该位置为空,则直接创建新节点并插入。
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))))
// 若当前节点 p 的哈希值与给定哈希值相等,并且键 key 和当前节点的键相等(或引用相等),则将当前节点赋值给变量 e。
e = p;
else if (p instanceof TreeNode)
// 若当前节点是红黑树节点,则调用红黑树的插入方法 putTreeVal。
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 遍历链表,查找与给定键相等的节点
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果链表长度超过阈值 TREEIFY_THRESHOLD - 1,即链表长度达到树化阈值的上限,将链表转化为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 如果大小超过负载因子阈值,调用 resize() 方法进行扩容。
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
● 说明:向HashSet对象中添加元素时,首先通过hash函数计算除hash码值,根据计算得到的哈希值和数组长度,找到存储位置的索引 i。如果该位置为空,则直接创建新节点并插入。如果该位置已经存在节点,判断当前节点的哈希值与给定哈希值是否相等,并且键 key 和当前节点的键是否相等(或引用相等),若相等则将当前节点赋值给变量。如果该条件不满足,则判断该节点是不是红黑树的叶子节点,若是则插入,如果不是则遍历链表,查找与给定键相等的节点,判断链表长度超过阈值 (size>8),即链表长度达到树化阈值的上限,将链表转化为红黑树.如果未找到与给定键相等的节点,在链表末尾添加新节点。
四、boolean remove(Object o) 移除元素
private static final Object PRESENT = new Object();
public boolean remove(Object o) {
// 调用HashMap 的 remove 方法
return map.remove(o)==PRESENT;
}
● 说明:map.remove(o) 用于移除键为 o 的映射,如果 remove() 返回 PRESENT,即已定义的常量对象,在 HashSet 中表示元素存在且成功移除,则 remove() 方法返回 true。如果 remove() 返回其他值,表示元素不存在或者移除失败,则 remove() 方法返回 false。
public V remove(Object key) {
Node<K,V> e;
// 计算key 的hash值,并调用removeNode方法移除节点
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
● 说明:首先,根据传入的 key 计算哈希值,并确定对应的桶(bucket)索引值。然后,调用 removeNode() 方法进行节点的删除操作。removeNode() 方法用于删除指定桶中的节点,并返回被删除的节点。如果 removeNode() 返回的节点 e 为 null,则表示指定键对应的节点不存在,即要删除的元素不存在,此时返回 null。如果 removeNode() 返回的节点 e 不为 null,则表示指定键对应的节点存在且成功删除,通过 e.value 可以获取到被删除节点中的值,并将其返回。
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值计算出对应桶的索引,并获得桶的头节点p
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;
//判断 p 节点是否为要删除的节点,如果是,则将p 节点赋值给 node
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
// 如果节点p 不匹配,则遍历红黑树或链表,找到要删除匹配的节点,并赋值给 node
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);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
// 如果节点为 红黑树节点,调用removeTreeNode方法进行移除
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
// 如果 node 是链表头节点,将下一个节点作为新的头节点
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
// 调用该方法进行删除后的处理
afterNodeRemoval(node);
return node;
}
}
return null;
}
● 说明:判断table是否为空,若不为空,根据hash值计算出对应桶的索引,并获得桶的头节点p,然后判断 p 节点是否为要删除的节点,如果是,则将p 节点赋值给 node。如果节点p 不匹配,则遍历红黑树或链表,找到要删除匹配的节点,并赋值给 node。如果节点为 红黑树节点,调用removeTreeNode方法进行移除。如果 node 是链表头节点,将下一个节点作为新的头节点。并返回node节点。
五、void clear() 移除所有元素
public void clear() {
map.clear();
}
public void clear() {
Node<K,V>[] tab;
// 修改程序计数器
modCount++;
if ((tab = table) != null && size > 0) {
// 如果table 不为空,并且长度大于0,则将长度置为0,表示该对象不在拥有任何元素
size = 0;
for (int i = 0; i < tab.length; ++i)
// 通过循环遍历将每个桶都置为 null。
tab[i] = null;
}
}
说明:首先,声明一个 Node<K,V> 类型的数组变量 tab 用于存储哈希表。同时,增加修改计数器 modCount 的值来标识哈希表的修改。判断哈希表 table 是否不为 null,且当前哈希表中存在元素(size > 0)。如果满足条件,则将 size 设置为 0,表示哈希表中不再有任何元素。然后,使用循环遍历整个哈希表数组 tab,将每个桶置为空(即将桶中的引用设置为 null)。完成清空操作后,HashMap 中的键值对都被彻底移除。但不会释放数组和节点占用的内存空间,只是将桶中的引用置为 null,以便后续的垃圾回收。