一、HashMap的诞生
1.1 数组
数组:一片物理上连续的大小确定的储存空间。
好处:根据下标快速的查找和修改里面的内容。
缺点:大小确定,无法修改。添加新的元素或者删除元素比较麻烦。
数组的静态初始化
//数组实现方式一:
//数据类型 数组名称[] = {值, 值,…}
String str[] = {"移动端","Android","iOS"};
System.out.println(str.length);//三个元素
for (int i = 0; i <str.length ; i++) {
System.out.println(str[i]);
}
//数组实现方式二:
//数据类型 数组名称[] = new 数据类型[] {值, 值,…}
String str2[] = new String[3];
str2[0] = "移动端";
str2[1] = "Android";
str2[2] = "iOS";
System.out.println(str2.length);//三个元素
for (int i = 0; i <str2.length ; i++) {
System.out.println(str2[i]);
}
运行结果是一样的。
"E:\Android\Android StudioO\jre\bin\java.exe"...
3
移动端
Android
iOS
如果我想再添加两个新元素(Java、Kotlin),那么使用数组就不合适了,这时候顺序表就出现了。
1.2 顺序表
顺序表:以数组的形式保存的线性表,物理上连续、逻辑上连续、大小可以动态增加。(如:ArrayList)
顺序表用的频率远远高于数据。用肯定都会用,咱们看看源码。
public class ArrayList extends AbstractList
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
transient Object[] elementData;//存储 ArrayList 元素的数组缓冲区。
private int size;//ArrayList 的大小
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
public E set(int index, E element) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
}
然后你会发现还是用数组进行存储的,只不过把对数组的操作处理了,而不需要我们自己处理。
原来的数组
新增数组
而顺序表的新增和删除元素都需要大量移动元素等操作,此时链表就产生了。
1.3 链表
链表:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。
新增
这里将元素1的指针由指向元素2改为指向新增元素4,然后再将元素4的指针指向元素2即可。
删除元素
将元素1的指针直接指向元素3即可。
1.4 ArrayList 和 LinkedList 对比
ArrayList(顺序表):
优点:查找快
缺点:增删慢
LinkedList(链表)
优点:增删快
缺点:查找慢
那么问题来了在什么情况下使用 ArrayList?在什么情况下使用 LinkedList? 为什么?
那么可不可以将 顺序表和链表的优点结合起来呢?-Hash表
1.5 Hash表
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希表其实本质上就是一个数组。可以根据一个key值来直接访问数据,查找速度快。
二、HashMap
HashMap采用Node(implements Map.Entry)数组来存储key-value对,每一个键值对组成了一个Node实体,Node类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Node实体。
注意:jdk1.8 HashMap中的Entry不见了,取而代之的是Node,但是Node也实现了Map.Entry接口,和之前的Entry类似。
Node类
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;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
...
}
}
从上面看出 hash、key、value 代表本节点数据,next表示指向下一个节点的数据。
JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。
JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8) 时,将链表转换为红黑树,这样大大减少了查找时间。
2.0 小试牛刀
class HashMapTest {
public static void main(String[] args) {
//创建HashMap
HashMap<String,String> hashMap = new HashMap<>();
//添加数据(key-value)
hashMap.put("name","帅次");
hashMap.put("age","20");
hashMap.put("subject","Android");
hashMap.put(null,"空Key");
System.out.println("HashMap1:"+hashMap.toString());//{null=空Key, subject=Android, name=帅次, age=20}
//添加数据(覆盖已有key)
hashMap.put("age","26");
System.out.println("HashMap2:"+hashMap.toString());//{null=空Key, subject=Android, name=帅次, age=26}
//根据key,获取value
System.out.println("Key-null:"+hashMap.get(null));//空Key
System.out.println("Key-subject:"+hashMap.get("subject"));//Android
//根据key删除元素
System.out.println("删除subject对应:"+hashMap.remove("subject"));//删除subject对应:Android
//根据key-value删除元素
System.out.println("删除age:"+hashMap.remove("age","20"));//删除age对应20:false
System.out.println(hashMap.toString());//{null=空Key, name=帅次, age=26}
System.out.println("删除age:"+hashMap.remove("age","26"));//删除age对应20:true
System.out.println("HashMap3:"+hashMap.toString());//{null=空Key, name=帅次}
//方法一:1、获得key-value的Set集合,2、循环(推荐)
Set<Map.Entry<String,String>> entrySet= hashMap.entrySet();
for (Map.Entry<String, String> strEntry : entrySet) {
System.out.println("遍历Set集合:"+strEntry.getKey()+"-"+strEntry.getValue());
}
//方法二:1、获取key的Set集合,2、循环
Set<String> strKeySet= hashMap.keySet();
for (String s : strKeySet) {
//先获取key,通过key获取value
System.out.println("遍历KeySet集合:"+s+"-"+hashMap.get(s));
}
//方法三:1、获得key-value的Set集合,2、获取iterator 3、循环
Set<Map.Entry<String,String>> itEntrySet= hashMap.entrySet();
Iterator iterator = itEntrySet.iterator();
while (iterator.hasNext()){
Map.Entry map= (Map.Entry)iterator.next();
System.out.println("遍历Iterator:"+map.getKey()+"-"+map.getValue());
}
//其他还有不少方法可以遍历数据
}
}
运行结果
"E:\Android...."
HashMap1:{null=空Key, subject=Android, name=帅次, age=20}
HashMap2:{null=空Key, subject=Android, name=帅次, age=26}
Key-null:空Key
Key-subject:Android
删除subject对应:Android
删除age:false
{null=空Key, name=帅次, age=26}
删除age:true
HashMap3:{null=空Key, name=帅次}
遍历Set集合:null-空Key
遍历Set集合:name-帅次
遍历KeySet集合:null-空Key
遍历KeySet集合:name-帅次
遍历Iterator:null-空Key
遍历Iterator:name-帅次
Process finished with exit code 0
2.1 构造方法
/**
* 默认加载因子值
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 哈希表的加载因子。
*/
final float loadFactor;
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
/**
* 构造一个具有默认加载因子 (0.75) 的空 HashMap。
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
HashMap有4个构造方法,主要初始化了三个参数:
initialCapacity:初始容量(默认16)。
loadFactor 加载因子(默认0.75)。
threshold 阈值:hashMap所能容纳的最大价值对数量,如果超过则需要扩容,计算方式:threshold=initialCapacity*loadFactor。
加载因子:在默认情况下,数组大小为16,那么当HashMap中元素个数超过 160.75=12 的时候,就把数组的大小扩展为 162=32 ,即扩大一倍,然后重新计算每个元素在数组中的位置。当然这个值是可以自己设定的,但是不推荐修改这个值。
咱们比较常用的还是无参构造方法。HashMap 创建好了,那么咱们开始存储数据吧。
2.2 put()
添加数据:
key可以为null
key唯一,如果key存在则覆盖value内容
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
这里在调用 putVal() 方法前根据key进行了hash计算。
2.2.1 hash(key)
/**
* 计算 key.hashCode() 并将散列的高位散列(XOR)到低位。由于该表使用二次幂掩码,因此仅在当前掩码之上位变化的散列集将始终发生冲突。
* 使用树来处理 bin 中的大量冲突,我们只是以最便宜的方式对一些移位的位进行异或,以减少系统损失,以及合并最高位的影响,否则由于表边界,这些最高位将永远不会用于索引计算(摘自源码)。
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public int hashCode() {
return identityHashCode(this);
}
static int identityHashCode(Object obj) {
int lockWord = obj.shadow$_monitor_;
final int lockWordStateMask = 0xC0000000; // Top 2 bits.
final int lockWordStateHash = 0x80000000; // Top 2 bits are value 2 (kStateHash).
final int lockWordHashMask = 0x0FFFFFFF; // Low 28 bits.
if ((lockWord & lockWordStateMask) == lockWordStateHash) {
return lockWord & lockWordHashMask;
}
return identityHashCodeNative(obj);
}
当 key == null 时返回0,也就是说 key 可以设置为 null。
从identityHashCode()方法看出key可以是任意类型,都可以变成int类型的hashCode。
h >>> 16 是什么?
h是hashCode。h >>> 16是用来取出h的高16,(>>>是无符号右移)
0000 0100 1011 0011 1101 1111 1110 0001
4
0000 0000 0100 1011 0011 1101 1111 1110
16
0000 0000 0000 0000 0000 0100 1011 0011
上面两个例子 一个>>>4 一个>>>16,可用于对比。
这里根据Key返回了一个Hash值。拿到哈希值咱们继续看 putVal() 方法。
2.2.2 putVal()
/**
* table 在首次使用时初始化,存储数据的Node类型 数组,并根据需要调整大小。
* 长度 = 2 的幂。(特殊情况下可为0),数组的每个元素 = 1个单链表
*/
transient Node<K,V>[] table;
/**
* @param hash 键的哈希值
* @param key 键
* @param value 要放置的值
* @param onlyIfAbsent 如果为真,则不要更改现有值
* @param evict 如果为 false,则表处于创建模式。
* @return 前一个值,如果没有则返回 null
*/
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)
//调用 resize() 方法,进行初始化并赋值
n = (tab = resize()).length;
//通过hash获取下标,如果数据为null
if ((p = tab[i = (n - 1) & hash]) == null)
//tab[i]下标没有值,创建新的Node并赋值。
tab[i] = newNode(hash, key, value, null);
else {
//tab[i] 下标的有数据,发生碰撞
Node<K,V> e; K k;
//判断tab[i]的hash值传入的hash值相当,tab[i]的的key值和传入的key值相同
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//如果是原有数据直接替换即可
e = p;
else if (p instanceof TreeNode)//判断数据结构为红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//数据结构是链表
for (int binCount = 0; ; ++binCount) {
//p的下一个节点为null,表示p就是最后一个节点
if ((e = p.next) == null) {
//创建Node并插入链表的尾部
p.next = newNode(hash, key, value, null);
//当元素>=8-1,链表转为树(红黑树)结构
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指向下一个节点,继续遍历
p = e;
}
}
//如果key在链表中已经存在,则修改其原先key的value值,并且返回老的value值
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);//替换旧值时会调用的方法(默认实现为空)
return oldValue;
}
}
++modCount;//修改次数
//根据map值判断是否要对map的大小扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);//插入成功时会调用的方法(默认实现为空)
return null;
}
2.2.3 resize() 扩容
/**
* 默认初始容量 - 必须是 2 的幂。
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 初始化或加倍 table 大小。 如果为空,则按照字段阈值中保存的初始容量目标进行分配。
* 否则,因为我们使用二次幂扩展
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//table已经初始化,且容量 > 0
if (oldCap > 0) {
//MAXIMUM_CAPACITY=1<<30=2的30次幂=1073741824
if (oldCap >= MAXIMUM_CAPACITY) {
//如果旧的容量已近达到最大值,则不再扩容,阈值直接设置为最大值
//Integer.MAX_VALUE=(1 << 31)-1=2的31次幂-1=2147483648-1
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 扩大两倍
}
else if (oldThr > 0) // 初始化容器=threshold
newCap = oldThr;
else {
//threshold 和 table 皆未初始化情况,此处即为首次进行初始化
//第一次进入什么都没有所以要初始化容器(16)
newCap = DEFAULT_INITIAL_CAPACITY;
//12 = 0.75*16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//newThr 为 0 时,计算阈值
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数据
table = newTab;
//如果之前的数组桶里面已经存在数据,由于table容量发生变化,hash值也会发生变化,需要重新计算下标
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//将指定下标数据不为null
if ((e = oldTab[j]) != null) {
//将指定下标数据置空
oldTab[j] = null;
//指定下标数据只有一个
if (e.next == null)
//直接将数据存放到新计算的hash值下标
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)//红黑树
//将树箱中的节点拆分为下树箱和上树箱,如果现在太小(<=6),则数据结构取消红黑树改为链表。
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { //链表
//重新计算hash值,根据新的下标重新分组
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;
}
因此,resize()方法 初始化了容器,并给 table 赋值,返回Node<K,V>[]。
2.2.4 putTreeVal()
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;
//获取根节点
TreeNode<K,V> root = (parent != null) ? root() : this;
//循环所有的节点,如果key冲突,返回原key对应的对象,如果不存在key冲突,则直接存放对象,并返回null对象
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
//dir:决定节点的位置
if ((ph = p.hash) > h)//当前节点的哈希值大于存放对象的哈希值
dir = -1;
else if (ph < h)//当前节点的哈希值小于存放对象的哈希值
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))//当前节点等于存放对象的哈希值,且key相同,则返回当前节点
return p;
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))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
//如果p的左节点或右节点为空,证明已经找到存放位置
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K,V> xpn = xp.next;
//创建新节点
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
//根据dir设置存放位置
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;
//balanceInsertion树化结构,设置红黑节点,是否需要旋转
//moveRootToFront重置根节点
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
2.3 get()
获取数据
这方法返回结果:
节点(Node);
null
该 key 对应的数据就是 null;
HashMap 中不存在该 key
public V get(Object key) {
Node<K,V> e;
//hash(key):根据key的hashCode计算hash值,这个跟put中的的一样。
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
这个方法就是通过 getNode() 方法来获取节点,如果节点为null则返回null,如果节点存在则返回key对应的value。
2.3.1 getNode()
/**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return 节点(node),如果没有则返回 null
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//根据hash值获取table中节点存放位置,并获取第一个元素。
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果第一个节点是我们要找的Key,则直接返回
if (first.hash == hash && //
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//获取下一个节点的信息
if ((e = first.next) != null) {
//判断数据结构是红黑树,则通过getTreeNode()获取节点并返回
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;
}
2.3.2 getTreeNode()
static final class TreeNode<K,V> extends LinkedHashMap.LinkedHashMapEntry<K,V> {
TreeNode<K,V> parent; // 红黑树节点
//调用树的find()函数
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
2.3.3 find()
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)))//当前节点等于给定哈希值,且key相同,则返回当前节点
return p;
else if (pl == null)//如果左节点为null,则进入右节点
p = pr;
else if (pr == null)//如果右节点为null,则进入左节点
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)//如果不按哈希值排序,而是按照比较器排序,则通过比较器返回值决定进入左右结点
p = (dir < 0) ? pl : pr;
else if ((q = pr.find(h, k, kc)) != null)//如果在右结点中找到该关键字,直接返回当前节点
return q;
else//进入左节点
p = pl;
} while (p != null);
return null;
}
2.4 remove()
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
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是否存在、数据元素大于>0、index位置元素是否存在
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和传入的数据一致
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//获取p的数据
node = p;
else if ((e = p.next) != null) {//和p数据不一致,获取p下一个节点数据
//判断p的数据结构是否为红黑树
if (p instanceof TreeNode)
//红黑树,调用find找到node节点
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//链表
do {
//循环找到跟e相同的数据
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)))) {
//根据红黑树删除数据
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)//node=p,然后指针指向p节点后一个节点,达到删除node的目的
tab[index] = node.next;
else//此时node=p.next,所以node就被干掉了
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
这个方法其实跟上面的 put/get 方法类似,都是先获取哈希值,然后根据哈希值和Key找到这个节点,进行删除,自己探索乐趣多多。
2.5 clear()
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;
}
}
这个简单粗暴,直接遍历table,然后将table的值设置为null。
其实HashMap常用的方法也就这么几个,你了解了吗?