HashMap
存储结构
JDK7的HashMap采用的是数组+链表的拉链表,JDK8以及之后添加了红黑树,红黑树是一种以查找时间复杂度为O(logN) 左小右大闻名的树类型的数据结构,在红黑树上增加 删除 修改 查找比遍历链表查找快得多。(但红黑树增删我记得非常复杂,好像要各种旋来旋去),如果链表的长度到达了8,那么会将链表转换为红黑树
put方法源码解读
在看HashMap的put的方法之前,先看一下HashMap中的几个成员变量
/**
* The default initial capacity - MUST be a power of two.默认数组长度为16
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;//最大数组长度,2^30
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认负载因子
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;//如果链表长度超过8,那么转换为红黑树
/**
* The number of key-value mappings contained in this map.
*/
transient int size;//HashMap中当前key-value个数
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)阈值
int threshold;
/**
* The load factor for the hash table.
*负载因子
* @serial
*/
final float loadFactor;
这里涉及到了数组扩容的问题,如果key-value个数到达一个阈值threshold,那么就会对数组进行扩容。
threshold=DEFAULT_INITIAL_CAPACITY(数组长度)*loadFactor(负载因子)
从这个公式可以看出来,如果数组长度定了的情况下,负载因子越大,能存放的键值对个数越多,但是也会造成哈希冲突更多,单链表的长度更长, CRUD时更耗时。
但如果负载因子过小的话,会很快到达阈值,并且进行数组扩容,这样会导致HashMap占有更多的内存空间。
所以loadFactor默认0.75是一个在时间复杂度和空间复杂度之间折中的选择。
HashMap中数组索引的计算方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//hash(key)是指key的哈希值
//如果key为空,哈希值为0;如果key不为空,那么对key的hashCode带符号位右移16位,再与hashCode进行
//异或运算,获得hash
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
@HotSpotIntrinsicCandidate
public native int hashCode();
//之后在put方法里还会对hash值进行计算,将hash 和 数组长度-1进行按位与计算,才算真正计算得到数组索引
p = tab[i = (n - 1) & hash]
上面是JDK8之后的计算方式,事实上JDK7是用按位与运算 代替了 取模运算%,用hashCode%(数组长度-1),因为只要数组长度=2的n次方,hashCode & (length-1) 等效于 hashCode%length。
JDK8是在hash&(length-1)之前,又对hashCode进行了无符号右移16位 再与 hashCode异或的运算。
这也解释了为什么数组的初始长度是16,并且之后每次对数组扩容都是*2,都是为了让数组长度是2的n次方。
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/* ---------------- Fields -------------- */
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
//HashMap存储结构中的数组
transient Node<K,V>[] table;
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//桶数组
Node<K,V>[] tab;
//p是链表头指针,就是数据结构例程里那个head
Node<K,V> p;
//n是指数组长度,i是指key-value在数组中的索引位置
int n, i;
//1.如果数组为空或者数组长度为0,那么对数组扩容,具体怎么扩容的呢??
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//2.首先用数组长度-1 和 hash值进行 按位与运算,获得 key-value在数组中的索引位置,
//如果链表头指针为空,那么直接new一个Node对象,将对象内存地址赋值给数组[i]
***//这是table[i]一个元素都没有的情况***
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//3.如果***table[i]已经有元素占着坑了***
else {
Node<K,V> e; K k;
//p是头指针,如果table[i]位置的key-value的key值和当前key-value的 hash值相等并且key值也相等
//那么先让e也指向头指针,之后if (e != null) 这段代码里会将头指针里的value直接替换掉
//这是***table[i]已经有元素占着坑了,并且还是hash值相等key相等,那么直接替换掉value***
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
***//如果table[i]已经是一个红黑树了,那么按照红黑树的方式去处理***
else if (p instanceof TreeNode)
//Tree version of putVal.这个是putTreeVal的注解
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
***//如果table[i]长度还没超过8,还是一个链表***
else {
//下面这段非常明显是单链表尾插
//***e = p.next;***p = e这两行代码实现了单链表不断地往next遍历,
***for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {***
***p.next = newNode(hash, key, value, null);
//如果单链表长度>=8了,那么单链表变红黑树***
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//这段是说在遍历单链表的时候,在除了单链表头指针的地方有Node的key值和 需要
//插入的key值相等,处理办法是直接break,e指针指向这个相同key值的对象,
//最后那段代码会直接替换掉value的
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//key已经存在的直接覆盖value,并将老的value返回
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果增加了一个新node的话,size++;如果size大于阈值的话,那么需要对数组扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
总结一下put的流程
1、首先判断数组table是否为空,为空的话对数组table扩容
2、之后对hash值进行按位与运算,获得key在数组中的索引位置i
3、如果table[i]为空,那么直接将key-value放入table[i]
4、如果table[i]不为空,并且table[i]的key 等于要插入的key,那么直接覆盖value
5、如果table[i]不为空,但table[i]的key不是要插入的key,并且table[i]已经变成红黑树节点了,那么按红黑树的处理方式处理(这里源码没有看,可能要旋来旋去了)
6、如果table[i]不为空,但table[i]的key不是要插入的key,,并且table[i]仍然是一个链表,那就遍历链表, 如果遍历过程中遇到相同key直接覆盖,没遇到相同key那就尾插
7、最后如果没有采用覆盖的方式,插入一个新的Node了,要看一下size(key-value)个数是否到达阈值,到了要扩容的;
Get方法源码
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* 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) {
//tab是数组
Node<K,V>[] tab;
//first是key所在的链表的头指针
Node<K,V> first, e;
int n; K k;
//如果数组不为空&&数组的长度大于0&&key所在的链表头指针不为空 实际上也就是链表不为空
//tab[(n - 1) & hash]这个是用key的hash值进行按位与运算 计算出key所在的数组索引的
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果头指针key值相等,直接返回头指针
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果头指针的next不为空,也就是链表不止一个节点
if ((e = first.next) != null) {
//如果链表已经是红黑树了,那么要用红黑树的查找
//TreeNode继承了Entry,Entry又继承了Node,所以TreeNode才可以转换成Node的
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//如果还是链表,遍历链表,如果有key值相同的,直接返回e,e是用来遍历的那个指针
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
//如果最后没找到,直接返回空
return null;
}
总结一下大致流程
1、如果数组table为空,直接返回空
2、如果数组不为空,头指针为空,返回空
3、如果数组不为空,头指针不为空,头指针key相同,那么直接返回
4、如果数组不为空,头指针不为空,头指针key不同,头指针指向一个红黑树节点,那么用红黑树的方式去处理
5、如果数组不为空,头指针不为空,头指针key不同,数组仍然是一个链表,遍历链表,找到相同key,直接返回value,如果没找到返回空。
hashCode()与equals(Object obj)
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code hashCode} method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined
* by class {@code Object} does return distinct integers for
* distinct objects. (The hashCode may or may not be implemented
* as some function of an object's memory address at some point
* in time.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
@HotSpotIntrinsicCandidate
public native int hashCode();
/**
* Indicates whether some other object is "equal to" this one.
* <p>
* The {@code equals} method implements an equivalence relation
* on non-null object references:
* <ul>
* <li>It is <i>reflexive</i>: for any non-null reference value
* {@code x}, {@code x.equals(x)} should return
* {@code true}.
* <li>It is <i>symmetric</i>: for any non-null reference values
* {@code x} and {@code y}, {@code x.equals(y)}
* should return {@code true} if and only if
* {@code y.equals(x)} returns {@code true}.
* <li>It is <i>transitive</i>: for any non-null reference values
* {@code x}, {@code y}, and {@code z}, if
* {@code x.equals(y)} returns {@code true} and
* {@code y.equals(z)} returns {@code true}, then
* {@code x.equals(z)} should return {@code true}.
* <li>It is <i>consistent</i>: for any non-null reference values
* {@code x} and {@code y}, multiple invocations of
* {@code x.equals(y)} consistently return {@code true}
* or consistently return {@code false}, provided no
* information used in {@code equals} comparisons on the
* objects is modified.
* <li>For any non-null reference value {@code x},
* {@code x.equals(null)} should return {@code false}.
* </ul>
* <p>
* The {@code equals} method for class {@code Object} implements
* the most discriminating possible equivalence relation on objects;
* that is, for any non-null reference values {@code x} and
* {@code y}, this method returns {@code true} if and only
* if {@code x} and {@code y} refer to the same object
* ({@code x == y} has the value {@code true}).
* <p>
* Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
*
* @param obj the reference object with which to compare.
* @return {@code true} if this object is the same as the obj
* argument; {@code false} otherwise.
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}
Object类里的hashCode()方法是一个native本地方法,本地方法也就是说他不是由java语言实现的,而是由c/c++其他语言实现的,常常有一种误解是hashCode不覆盖的话是内存地址,但实际上不是这样的。不同的jdk有不同的hashCode的计算方式;
JDK JRE JVM关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DDZrIlZg-1689736343320)(HashMap%20b2cd0a6acba14907a7f0e4846e5baf76/Untitled.png)]
HashCode()的作用
1、计算hash数组索引
hashCode是用来计算key在hashMap的桶数组的索引位置的,但hashCode不能直接拿来当索引位置,JDK7是hashCode & (length-1) 等价于 hashCode%length,JDK8在按位与运算之前,还进行了
(hashCode >>>16) ^ hashCode;
2、判断两个对象是否相等,提高判断时间效率
在get put方法中判断key值是否相等,先判断hash值是否相等,再判断是否equals
get
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
put
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
由于java中&&逻辑与这个运算符的特效(只要&&前面的表达式为false,&&之后的表达式将不再执行),所以hash值可以减少equals执行次数;要知道hash值是一个int基本类型的值,equals如果被覆盖了的话,不仅仅是比较两个引用是否指向同一个地址这么简单,有时候还要instanceOf运算,多个成员属性之间互相比较,如下
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ApiDataBean that = (ApiDataBean) o;
return run == that.run && contains == that.contains && status == that.status && sleep == that.sleep && Objects.equals(desc, that.desc) && Objects.equals(url, that.url) && Objects.equals(method, that.method) && Objects.equals(param, that.param) && Objects.equals(verify, that.verify) && Objects.equals(save, that.save) && Objects.equals(preParam, that.preParam);
}
tips:
Object类的equals方法上提出了如果想要覆盖equals方法,需要满足的几个特性
自反性:x.equals(x)==true
传递性:x.equals(y)==true y.equals(z)==true => x.equals(z)==true
对称性: x.equals(y)==true => y.equals(x)
一致性:只要对象不改变,equals结果应该前后保持一致
事实上,现在idea可以自动帮你生成equals hashCode,很少自己重写了
可以这么说,hash比较比equals比较快的多!
所以用hash先比较提高了时间效率;
hashCode和equals的关系
但仅仅是hashCode相等是不足以判断对象相等的,想想哈希碰撞是如何发生的,是hash(key)相等但key实际上不等,链表里链着的都是hashCode相等的但key不等的;
只能说hashCode不等,两个对象一定不相等(这也是我们用hash值作为get put方法里做第一个判断条件的原因)
但equals为true,hashCode一定相等
上面这三条是我们覆盖hashCode equals的默认规则
那我可以只重写equals不覆盖hashCode吗?只重写equal不覆盖hashCode的后果
不可以🙅♂️
举个🌰
String s=new String(”ss”);
String s1=new String(”ss”);
我们都知道String类覆盖了equals(),s.equals(s1)==true;
假设String类没有覆盖hashCode方法呢?
他的hashCode有可能是(注意是有可能是,不同jdk有不同的hashCode)String对象在堆中的内存地址(这里注意是new String方式创建的,不是“”这种方式创建的,会在堆和字符串常量池里都个字创建一个对象),这个时候hashCode就一定不相等
map.put(s, “a”);
map.put(s1, “b”);
正确的结果会是“ss“-”b” 覆盖了 “ss”-”a”
但是你没有覆盖hashCode,判断hash碰撞的时候
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
这里的p.hash == hash 是false,
后面的引用==和equals根本走不到
所以他不认为是哈希碰撞,直接将“ss“-”b” 又链入了 “ss”-”a”后面(尾插)
**这个时候有两个重复的key"ss"**
另外Object类也说明了override equals就应该hashCode
Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
只覆盖equals方法,不覆盖hashCode()
如下
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ApiDataBean that = (ApiDataBean) o;
return run == that.run && contains == that.contains && status == that.status && sleep == that.sleep && Objects.equals(desc, that.desc) && Objects.equals(url, that.url) && Objects.equals(method, that.method) && Objects.equals(param, that.param) && Objects.equals(verify, that.verify) && Objects.equals(save, that.save) && Objects.equals(preParam, that.preParam);
}
@HotSpotIntrinsicCandidate
public native int hashCode();
两个对象内容相等,hashCode不一定相等,但equals一定相等(这条违反了我们的规则)
两个对象不等,hashCode一定不等,equals也一定不等(没啥毛病)
我只覆盖hashCode不覆盖equals()呢?(自己瞎想的,不保证对)
也就是这种情况
@Override
public int hashCode() {
return Objects.hash(run, desc, url, method, param, contains, status, verify, save, preParam, sleep);
}
public boolean equals(Object obj) {
return (this == obj);
}
两个对象不等,equals一定为false, hashCode(覆盖了的是用对象的属性计算hash值的)一定也不等,符合规则。
两个对象相等,equals不一定true(有可能只是属性相等,不是同一个内存地址),hashCode一定相等,这也符合规则;
综上只覆盖hashCode可以,但只覆盖equals不行
只可以覆盖哪个方法,多从两个对象相等的角度想想,想两个方法覆盖了是什么样,不覆盖是什么样,两个方法各自的执行结果是什么?
hashMap数组扩容
//jdk 1.7的transfer方法,HashMap的扩容操作
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
public boolean equals(Object obj) {
return (this == obj);
}
扩容实际上就是rehash,将每一个key-value放到重新计算过的新的索引位置上去,可以很容易看出这里用了头插法;JDK8用的是尾插法
图解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8JLQN4Rk-1689736343321)(HashMap%20b2cd0a6acba14907a7f0e4846e5baf76/Untitled%201.png)]
HashMap线程不安全
为什么HashMap线程不安全?以及实现HashMap线程安全的解决方案_gougege0514的博客-优快云博客
JDK7下多个线程操控链表,很有可能导致死循环
JDK8用尾插法解决了这个死循环的问题,但是仍然会有可能导致数据覆盖丢失,或者实际大小已经超过阈值还没扩容的情况;
假如有两个线程同时进行put
package com.sen.api.test;
import java.util.HashMap;
public class HashMapTest {
public static void main(String[] args) throws InterruptedException {
HashMap<String, Integer> map = new HashMap<>();
new Thread("ThreadA"){
public void run(){
map.put("c01",1);
}
}.start();
new Thread("ThreadB"){
public void run(){
map.put("c02",2);
}
}.start();
Thread.sleep(1000);
System.out.println(map.toString());
}
}
假设ThreadA和ThreadB同时进行到了***if ((p = tab[i = (n - 1) & hash]) == null)
***这一步判断是否有哈希冲突
假设“c02” “c01”会发生哈希碰撞
再假设ThreadB执行到if这里进行哈希碰撞判断的时候缺乏cpu资源挂起了,
再假设线程A正常put进去了,结束了
这个时候线B继续进行put,会直接在table[i]的位置存key-value,覆盖了线程A存的值,导致数据丢失。
所以建议多线程时使用concurrentHashMap;
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//桶数组
Node<K,V>[] tab;
//p是链表头指针,就是数据结构例程里那个head
Node<K,V> p;
//n是指数组长度,i是指key-value在数组中的索引位置
int n, i;
//1.如果数组为空或者数组长度为0,那么对数组扩容,具体怎么扩容的呢??
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//2.首先用数组长度-1 和 hash值进行 按位与运算,获得 key-value在数组中的索引位置,
//如果链表头指针为空,那么直接new一个Node对象,将对象内存地址赋值给数组[i]
***//这是table[i]一个元素都没有的情况***
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//3.如果***table[i]已经有元素占着坑了***
else {
Node<K,V> e; K k;
//p是头指针,如果table[i]位置的key-value的key值和当前key-value的 hash值相等并且key值也相等
//那么先让e也指向头指针,之后if (e != null) 这段代码里会将头指针里的value直接替换掉
//这是***table[i]已经有元素占着坑了,并且还是hash值相等key相等,那么直接替换掉value***
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
***//如果table[i]已经是一个红黑树了,那么按照红黑树的方式去处理***
else if (p instanceof TreeNode)
//Tree version of putVal.这个是putTreeVal的注解
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
***//如果table[i]长度还没超过8,还是一个链表***
else {
//下面这段非常明显是单链表尾插
//***e = p.next;***p = e这两行代码实现了单链表不断地往next遍历,
***for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {***
***p.next = newNode(hash, key, value, null);
//如果单链表长度>=8了,那么单链表变红黑树***
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//这段是说在遍历单链表的时候,在除了单链表头指针的地方有Node的key值和 需要
//插入的key值相等,处理办法是直接break,e指针指向这个相同key值的对象,
//最后那段代码会直接替换掉value的
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//key已经存在的直接覆盖value,并将老的value返回
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果增加了一个新node的话,size++;如果size大于阈值的话,那么需要对数组扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}