jdk1.8 HashMap的源码解析

Hashmap

    基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等。本文将结合源码,具体分析HashMap底层的数据结构和实现原理。

    下面笔者将针对HashMap的一些重要的方法,从源码的角度,逐一进行详解。如果有不严谨和错误的地方,欢迎指点~
    这里,笔者参考一篇文章http://blog.csdn.net/u012673089/article/details/51815844,推荐学习。
   关于红黑树的一些具体方法实现,笔者没有做详细的描述,如果有兴趣,可以参考笔者的另外一篇文章http://blog.csdn.net/qq_29864971/article/details/79151345

package com.oracle.test2;

public class Practice {

    public class HashMap<K,V> extends AbstractMap<K,V>

    implements Map<K,V>, Cloneable, Serializable {

        private static final long serialVersionUID = 362498820763181265L;

        /**

         *初始化大小为16

         */

        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

        /**

         *设置最大扩容容量2^30次方

         */

        static final int MAXIMUM_CAPACITY = 1 << 30;

        /**

         *加载因子

         */

        static final float DEFAULT_LOAD_FACTOR = 0.75f;

        /**

         *链表存放的元素>=8,并且数组length>=64则改为红黑树

         */

        static final intTREEIFY_THRESHOLD = 8;


         /**

         *当红黑树中的元素容量<=6,恢复到链表

         */

        static final int UNTREEIFY_THRESHOLD = 6;

        /**

         *链表存放的元素>=8,并且数组length>=64则改为红黑树

         */

        static final intMIN_TREEIFY_CAPACITY = 64;

 

        /**

         *静态内部类作为保存Node结点的容器,和HashtableEntry<K,V>类似

         */

        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) {

                if (o == this)

                   return true;

                if (o instanceof Map.Entry) {

                   Map.Entry<?,?> e = (Map.Entry<?,?>)o;

                   if (Objects.equals(key, e.getKey()) &&

                           Objects.equals(value, e.getValue()))

                       return true;

                }

                return false;

            }

        }

 

        /**

         *hash值做散列化处理,尽量减少碰撞的次数,分为两步

         *第一步:计算h>>>16,整体向右移16位,高位补0这样做的好处:原先的高16位移动到低16位,高16位全为0.

         *第二步:h^(h>>>16),移动后和原先的hashCode异或运算。结果:高16位不变,低16位为异或运算结果。

         */

         //注:hashtable的散列化处理是: (h&0x0000ffff), 运算的结果发现它只对低16位做“散列”处理,而高16位全为0,所以相比之下,hashtable发生“碰撞”的概率可能更高。

        static final int hash(Object key) {

            int h;

            return (key == null) ? 0 : (h = key.hashCode()) ^(h >>> 16);

        }

 

        /**

         *判断X.Class是否实现了Comparable接口。

         *如果实现了,返回x.getClass();否则为null

         */

        static Class<?> comparableClassFor(Object x) {

            if (x instanceof Comparable) {

                Class<?> c; Type[] ts, as; Type t; ParameterizedType p;

                if ((c = x.getClass()) == String.class) // bypass checks

                   return c;

                if ((ts = c.getGenericInterfaces()) != null) {

                   for (int i = 0; i < ts.length; ++i) {

                       if (((t = ts[i]) instanceof ParameterizedType)&&

                               ((p = (ParameterizedType)t).getRawType() ==

                               Comparable.class) &&

                               (as = p.getActualTypeArguments()) != null &&

                               as.length == 1 && as[0] == c) // 获得Comparable<T[] t>接口中,泛型数组的大小和类型

                           return c;

                   }

                }

            }

            return null;

        }

 

        /**

         *如果xkcClass一致,返回k.compareTo(x)的结果,否则为0

         */

        @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable

        static int compareComparables(Class<?> kc, Object k, Object x) {

            return (x == null || x.getClass() != kc ? 0 :

                ((Comparable)k).compareTo(x));

        }

 

        /**

         *该方法的作用是计算下一个阈值(该值为2的幂)

         *例如:7 -------8

         *     15 -------16

         *     27 -------32

         *     63 -------64

         *     65 -------128    

         */

        static final int tableSizeFor(int cap) {

            int n = cap - 1;

            n |= n >>>1;

            n |= n >>>2;

            n |= n >>>4;

            n |= n >>>8;

            n |= n >>>16;

        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

        }


        /**

         *Node[]数组

         */

        transient Node<K,V>[] table;

        /**

         *entrySet集合

         */

        transient Set<Map.Entry<K,V>> entrySet;

        /**

         *元素的数量

         */

        transient int size;

        /**

         *数组容量被修改的次数

         */

        transient int modCount;


        /**

         *数组容量的阈值,当size大于该值则扩容。每次扩容的大小为之前的两倍,后面会介绍这种做法的好处。

         */

        int threshold;


        /**

         *加载因子

         */

        final float loadFactor;


        /**

         *构造方法1

         *会发现,HashMap的构造方法中并没有像Hashtable那样先初始化table的容量。所以导致在下面的

         *resize()方法中,做了许多if-else逻辑判断

         */

        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;

            //这里的threadhold保存了下一次扩容后数组的大小的值,并会在resize方法中赋值,即 newCap = oldThr;

            this.threshold = tableSizeFor(initialCapacity);

        }

 

        /**

         *构造方法2,本质和1一样

         */

        public HashMap(int initialCapacity) {

            this(initialCapacity, DEFAULT_LOAD_FACTOR);

        }

 

        /**

         *构造方法3

         *注意:如果选择该方法作为初始化方法,那么tablenull,并且threadhold=0

         */

        public HashMap() {

            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted

        }

 

 

        public HashMap(Map<? extends K, ? extends V> m) {

            this.loadFactor = DEFAULT_LOAD_FACTOR;

            putMapEntries(m, false);

        }

 

        /**

         *作为putAll方法的实现

         */

        final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {

            int s = m.size();

            if (s > 0) {

                //如果table为空,说明还没有元素

                if (table == null) {

                   //更新计算threadhold的大小,这样保证添加后数组元素的size仍然低于threadhold

                   float ft = ((float)s / loadFactor) +1.0F;

                   int t = ((ft < (float)MAXIMUM_CAPACITY) ?

                           (int)ft : MAXIMUM_CAPACITY);

                   //计算下一次扩容是,数组大小

                   if (t > threshold)

                       threshold = tableSizeFor(t);

                }

                //如果新增大小超出阈值,则扩容

                else if (s > threshold)

                   resize();

                //遍历map集合内的元素,依次插入

                for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {

                   K key = e.getKey();

                   V value = e.getValue();

                   putVal(hash(key), key, value, false, evict);

                }

            }

        }

 

        /**

         *返回数组元素的大小

         */

        public int size() {

            return size;

        }

 

        /**

         *判断数组是否为空

         */

        public boolean isEmpty() {

            return size == 0;

        }

 

        /**

         *通过key,获取value

         *如果存在返回value,否则返回null

         */

        public V get(Object key) {

            Node<K,V> e;

            return (e = getNode(hash(key), key)) == null ? null : e.value;

        }

 

        /**

         *通过key,寻找value

         *首先,通过keyhash值经过散列化,并进行取模运算后得到数组的索引值。

         *       这里需要强调的是,如果数组的length2的次幂,那么 (length-1)&h 等价于 h%length,这样的优点运算速度快。

         *  然后,通过索引找到数组中的起始结点。如果起始结点就是期望的结点,则返回。如果不是,对起始结点进行判断,如果

         *       它是红黑树,则调用红黑树中的查询方法,如果不是说明为链表。那么则进行遍历查询。

         */

        final Node<K,V> getNode(int hash, Object key) {

            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;

            if ((tab = table) != null && (n = tab.length) > 0 &&

                   (first = tab[(n - 1) & hash]) != null) {

                //如果起始结点为期望结点,直接返回

                if (first.hash == hash && // always check first node

                       ((k = first.key) == key || (key != null && key.equals(k))))

                   return first;

                //如果为红黑树,调用红黑树定义的getTreeNode方法

                if ((e = first.next) != null) {

                   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;

        }

 

        /**

         *判断是否包含该节点,本质还是调用getNode查询方法

         */

        public boolean containsKey(Object key) {

            return getNode(hash(key), key) != null;

        }

 

        /**

         *插入结点的方法

         */

        public V put(K key, V value) {

            return putVal(hash(key), key, value, false, true);

        }

                                                              图1. HashMap的put方法逻辑图

                             

        /**

         *作为put方法的具体实现。它的逻辑分支比较复杂。

         *首先,通过索引计算找到数组中对应的起始结点Node node = table[i]

         *     如果node为空,则直接插入。如果不为空,先判断node结点的key是否重复

         *     如果起始结点的key重复,则覆盖,如果不重复在对node进行判断

         *     如果node为红黑树结点即TreeNode,那么调用红黑树的插入方法,

         *     如果不是红黑树说明是链表,进行遍历。

         *     遍历时如果发现key重复,覆盖之并退出。如果没有发现key重复,则在链表的尾部插入(这和Hashtable不一样,

         *     Hashtable是从头部插入)。插入后,还需对链表容量判断,如果>=8且table的长度>=64,则链表改为红黑树

         */                    

        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)

                n = (tab = resize()).length;

            //如果起始结点为空,直接插入

            if ((p = tab[i = (n - 1) & hash]) == null)

                tab[i] = newNode(hash, key, value, null);

            else {

                Node<K,V> e; K k;

                //头结点的key重复,后面需要覆盖

                if (p.hash == hash &&

                       ((k = p.key) == key || (key != null && key.equals(k))))

                   e = p;

                //如果头结点为红黑树结点,调用红黑树结点的putTreeVal方法

                else if (p instanceof TreeNode)

                   e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

                else {

                   //如果为链表,进行遍历

                   for (int binCount = 0; ; ++binCount) {

                       //如果e为链表的尾结点

                       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;

                       }                  

                       if (e.hash == hash &&

                               ((k = e.key) == key || (key != null && key.equals(k))))

                           break;

                       //指向p的下一个结点

                       p = e;

                   }

                }

                //做覆盖操作,这里的onlyIfAbsentfalse

                if (e != null) { // existing mapping for key

                   V oldValue = e.value;

                   if (!onlyIfAbsent || oldValue == null)

                       e.value = value;

                   afterNodeAccess(e);

                   //注意,如果覆盖的话,put方法返回的是旧值

                   return oldValue;

                }

            }

            //元素数量变化,所以修改指针+1

            ++modCount;

            //如果增加后,元素数量大于阈值,需要resize

            if (++size > threshold)

                resize();

            afterNodeInsertion(evict);

            return null;

        }

     在介绍resize方法之前,需要补充点知识点,详细请看文章http://blog.csdn.net/u012673089/article/details/51815844。笔者在这里将其精华的部分摘取过来:

下面我们讲解下JDK1.8做了哪些优化。经过观测可以发现,我们使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。看下图可以明白这句话的意思,n为table的长度,图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。

元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:

因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:

这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置

下面,从源码角度具体分析,JDK1.8的resize源码,写的很赞

        /**

         *这里的resizeHashtablerehash方法的逻辑类似,但是实现和结果不一样!它们的本质都是:1、扩容  2、重排列

         *但是区别在于:hashmap2倍扩容,hashtable2+1

         *             hashmap的重排列更均匀,内存利用合理,hashtable有一定的随机性,不规律;

         *             hashmap不需要计算旧数组中每个元素的新的索引值,hashtable需要

        *            hashmap重排列后,新数组中的元素具有整体性,并且前后顺序不变,hashtable较为分散,并且从头往尾遍历,元素会倒置。            

         */

        /*

         *因为构造方法的不同,所以这里的oldCapoldThr会有几种情况:

         *首先,第一次调用resize方法的两种情况:

         *      Case 1: new HashMap(int initValue,float loadFactor)

         *         oldCap=0, oldThr>0

         *      Case 2: new HashMap()

         *         oldCap=0, oldThr=0

         *其次,不止一次调用resize方法

         *       Case 3: oldCap>0, oldThr>0

         */

        final Node<K,V>[] resize() {

            Node<K,V>[] oldTab = table;

            int oldCap = (oldTab == null) ? 0 : oldTab.length;

            int oldThr = threshold;

            int newCap, newThr = 0;

            //对应的Case 3

            if (oldCap > 0) {

                if (oldCap >= MAXIMUM_CAPACITY) {

                   threshold = Integer.MAX_VALUE;

                   return oldTab;

                }

                //两倍扩容

                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&

                       oldCap >= DEFAULT_INITIAL_CAPACITY)

                   newThr = oldThr << 1; //double threshold

            }

            //对应的Case 1

            else if (oldThr > 0) // initial capacity was placed in threshold

                newCap = oldThr;

            //对应的Case 2

            else {               // zero initial threshold signifies usingdefaults

                newCap = DEFAULT_INITIAL_CAPACITY;

                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

            }

            //对应的Case 1

            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 = newTab;


            //这里,已经完成了扩容,定义了新容量的table,下面就要开始重排列

            if (oldTab != null) {

                //开始遍历数组,从0开始

                for (int j = 0; j < oldCap; ++j) {

                   Node<K,V> e;

                   if ((e = oldTab[j]) != null) {

                       oldTab[j] = null;

                       //如果该链表只有1个元素

                       if (e.next == null)

                           //将元素e放置到新数组对应的新的索引位置

                           newTab[e.hash & (newCap - 1)] = e;

                       //如果不止一个元素,并且数据结构为红黑树

                       else if (e instanceof TreeNode)

                           //整体搬移红黑树到新的位置,这里很方便

                           ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);

                       else {

                           //如果数据结构为链表

                           /**

                         * 注:由于tablelength始终为2的次幂,那么扩容之后,旧数组的所有元素在重排序时存在一定的规律:

                            *     当移位的高位为“1”时,元素放置在table[index+oldCap]的位置;

                            *     当移位的高位为“0”时,元素依然在原索引位置。

                            *    所以:这里把旧链表分成lohi链表。

                            *     lo是依然保留在旧索引位置的链表;hi是即将放置到新索引位置(oldIndex+oldCap)的链表

                            *     定义好之后,在一次性将lohi链表,通过 newTab[j] = loHead;newTab[j + oldCap] = hiHead;

                            * 就完成了一个链表的重排序,不需要再像jdk1.7hashmap或者hashtable那样,计算每个元素的新的 索引值。所以说jdk1.8hashmap设计得很精妙。

                            */

                           Node<K,V> loHead = null, loTail = null;

                           Node<K,V> hiHead = null, hiTail = null;

                           Node<K,V> next;

                           do {

                               next = e.next;

                               //将移位的高位为“0”的结点集结成lo链表,会发现彼此的前后顺序没变!

                               if ((e.hash & oldCap) == 0) {

                                   if (loTail == null)

                                       loHead = e;

                                   else

                                       loTail.next = e;

                                       loTail = e;

                               }

                               //将移位的高位为“1”的结点集结成hi链表,会发现彼此的前后顺序没变!

                               else {

                                   if (hiTail == null)

                                       hiHead = e;

                                   else

                                       hiTail.next = e;

                                       hiTail = e;

                               }

                           } while ((e = next) != null);

                           //lo链表移动到newTab数组中

                           if (loTail != null) {

                               loTail.next = null;

                               newTab[j] = loHead;

                           }

                           //hi链表移动到newTab数组中

                           if (hiTail != null) {

                               hiTail.next = null;

                               newTab[j + oldCap] = hiHead;

                           }

                       }

                   }

                }

            }

            return newTab;

        }

        /**

         *替换原先的链表,将其就改成红黑树的数据结构

         */

        final void treeifyBin(Node<K,V>[] tab, int hash) {

            int n, index; Node<K,V> e;

            //如果table为空或者table.length<64,先进行扩容

            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)

                resize();

            //计算索引位置,并获得起始结点不为空

            else if ((e = tab[index = (n - 1) & hash]) != null) {

                TreeNode<K,V> hd = null, tl = null;

                do {

                   //Node结点替换成TreeNode结点

                   TreeNode<K,V> p = replacementTreeNode(e, null);

                   if (tl == null)

                       hd = p;

                   else {

                       //指向下一个

                       p.prev = tl;

                       tl.next = p;

                   }

                   tl = p;

                } while ((e = e.next) != null);

                if ((tab[index] = hd) != null)

                   hd.treeify(tab);

            }

        }

 

        /**

         *一次性插入一个Map

         */

        public void putAll(Map<? extends K, ? extends V> m) {

            putMapEntries(m, true);

        }

 

        /**

         *删除方法

         */

        public V remove(Object key) {

            Node<K,V> e;

            return (e = removeNode(hash(key), key, null, false, true)) == null ?

                   null : e.value;

        }

 

        /**

         *remove方法的具体实现

         *finddelete

         */

        final Node<K,V> removeNode(int hash, Object key, Object value,

                boolean matchValue, boolean movable) {

            Node<K,V>[] tab; Node<K,V> p; intn, 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;

                //如果起始结点就是需要删除的结点

                if (p.hash == hash &&

                       ((k = p.key) == key || (key != null && key.equals(k))))

                   node = p;

                else if ((e = p.next) != null) {

                   //如果内部结构为红黑树,调用红黑树的getTreeNode方法

                   if (p instanceof TreeNode)

                       node = ((TreeNode<K,V>)p).getTreeNode(hash, key);

                   else {

                       //如果内部结构为链表,遍历查找,p类似于e的父节点

                       do {

                           if (e.hash == hash &&

                                   ((k = e.key) == key ||

                                   (key != null && key.equals(k)))) {

                               node = e;

                               break;

                           }

                           p = e;

                       } while ((e = e.next) != null);

                   }

                }

                //此时,node为需要删除的结点

                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为起始结点,那么起始结点为node的下一个结点

                   else if (node == p)

                       tab[index] = node.next;

                   else

                       //如果node不是起始结点,则父节点的next指向nodenext,即node被删除了

                       p.next = node.next;

                   ++modCount;

                   --size;

                   afterNodeRemoval(node);

                   return node;

                }

            }

            return null;

        }

 

        /**

         *Removes all of the mappings from this map.

         *The map will be empty after this call returns.

         */

        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;

            }

        }

 

        /**

         *是否包含该值

         */

        public boolean containsValue(Object value) {

            Node<K,V>[] tab; V v;

            if ((tab = table) != null && size > 0) {

                for (int i = 0; i < tab.length; ++i) {

                   for (Node<K,V> e = tab[i]; e != null; e = e.next) {

                       if ((v = e.value) == value ||

                               (value != null && value.equals(v)))

                           return true;

                   }

                }

            }

            return false;

        }

    }

}

 


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMap是Java中常用的数据结构之一,它基于哈希表实现。在JDK 1.8中,HashMap的底层实现主要包括数组和链表(或红黑树)两部分。 首先,HashMap内部维护了一个Entry数组,每个Entry对象包含了键值对的信息,包括键、值和指向下一个Entry的指针。数组的长度是固定的,但可以根据需要进行扩容。 当我们向HashMap中插入一个键值对时,首先会根据键的hashCode()方法计算出一个哈希值。然后,通过哈希值与数组长度取模的方式确定该键值对在数组中的位置。如果该位置上已经存在其他键值对,就会发生冲突。 解决冲突的方法是使用链表或红黑树。在JDK 1.8中,当链表长度超过一定阈值(默认为8)时,链表会转换为红黑树,以提高查找效率。这样,在插入、删除和查找操作时,可以通过哈希值快速定位到对应的链表或红黑树,然后再在链表或红黑树中进行操作。 当我们需要查找一个键对应的值时,HashMap会根据键的哈希值找到对应的位置,然后遍历链表或红黑树来找到具体的键值对。 需要注意的是,HashMap并不保证元素的顺序,即插入和遍历的顺序不一定相同。如果需要有序的集合,可以考虑使用LinkedHashMap。 总结一下,JDK 1.8中HashMap的底层原理主要是通过数组和链表(或红黑树)来实现,通过哈希值快速定位到对应的位置,然后在链表或红黑树中进行操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值