HashMap

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的博客-CSDN博客

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;
    }

ref:
https://tech.meituan.com/2016/06/24/java-hashmap.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值