HashMap 的产生与原理

简介:大家好,我是枫哥🌟一线互联网的IT民工、📝资深面试官、🌹Java跳蚤网课堂创始人。拥有多年一线研发经验,曾就职过科大讯飞、美团网、平安等公司。在上海有自己小伙伴组建的副业团队,目前业余时间专注Java技术分享,春招/秋招/社招/跳槽,一对一学习辅助,项目接活开发。

🎉🎉扫码左侧二维码,加入群聊,一起学习,一起进步!

🌟 欢迎关注 🌟 收藏 🌹留言 🌹

🍊🍊:文末送福利

目录

一、HashMap的诞生

1.1 数组

1.2 顺序表

1.3 链表

1.4 ArrayList 和 LinkedList 对比

1.5 Hash表

二、HashMap

2.0 小试牛刀

2.1 构造方法

2.2 put()

2.2.1 hash(key)

2.2.2 putVal()

2.2.3 resize() 扩容

2.2.4 putTreeVal()

2.3 get()

2.3.1 getNode()

2.3.3 find()

2.4 remove() 

2.5 clear()


 

一、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<E> extends AbstractList<E>

        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中元素个数超过 16*0.75=12 的时候,就把数组的大小扩展为 16*2=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常用的方法也就这么几个,你了解了吗?

🌹 🌹感谢大家,坚持看完,既然选择了这条路,那就一起加油,一起学习!如果需要学习资源,实战面试资料,项目资源。关注公众号:IT枫斗者,🌟根据关键字领取对应的资料福利🌟!咨询解决问题,公众号私聊枫哥,备注来意。

🍊回复:java全套学习资源

🍊回复:面试资料

🍊回复:枫哥简历

🍊回复:程序员表白神器               

(从此告别程序员单身狗!)

🍊回复:程序员兼职网站

🍊回复:枫哥666                             

( 获取66套项目实战资料,大厂面试视频)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT枫斗者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值