Java集合之HashMap


在总集篇中我大概梳理了一下整个集合类的关系,这篇文章是对总集篇的扩展,本文将详细讨论HashMap的实现原理。所有涉及到的源码都是基于JDK11。顺便插一句,大家要学就学新的嘛,毕竟11可是长期支持版本,可以用好几年的那种。

HashMap简介

HashMap是一种不需要维持键值对有序的基于Hash提供高效存取功能的集合类。不需要维持键值对有序是相对于SortedMap来说的,并不是指HashMap中的键值对不可以排序。基于Hash是指这种Map通过Hash的方式实现相关功能。防止有人将Hash和Map绑在一起看。

HashMap的类描述信息

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

实现了克隆和序列化接口,支持相关功能。

HashMap的成员变量

// 默认容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量,2的30次方,没啥太大的意义
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认负载因子0.75,详细讨论见下文
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 转变成树的阈值,大于8开始变成树,详细讨论见下文
static final int TREEIFY_THRESHOLD = 8;
// 转变成链表的阈值,小于6开始退化为链表,详细讨论见下文
static final int TREEIFY_THRESHOLD = 8;
// 变树的另一个条件
static final int MIN_TREEIFY_CAPACITY = 64;
// HashMap存储结构中的数组
transient Node<K,V>[] table;
// HashMap中三大幻神之EntrySet,keySet,values其中keySet和values来自于父类
transient Set<Map.Entry<K,V>> entrySet;
transient Set<K>        keySet;
transient Collection<V> values;
// 大小
transient int size;
// 更改次数
transient int modCount;
// threshold=capacity*loadFactor,当size大于这个值时,需要扩容
int threshold;
// 负载因子
final float loadFactor;

支撑HashMap的内部类

先给张图,体会下。
内部类

我们知道HashMap中有三个集合,分别为entry集合,key集合,value集合,简称三幻神。它们有点像数据库中的view,我们可以通过它们遍历并操作数据,但是它们并没有保存数据,HashMap中的数据只在数据结构table和其延申链表或者树中保存。在上面的内部类中,可以分为四类,第一类entry相关的,Node,EntrySet,EntryIterator,EntrySpliterator。key集合与value集合与之类似。第四类时Node,TreeNode,其中Node被划分了两次,因为Node不仅是EntrySet中的元素,同时我们知道HashMap底层依赖于数组和红黑树实现,Node还是这个数组中的元素。同理,当Hash的冲突到一定程度的时候,冲突元素将被组织成一棵红黑树,而TreeNode就是这棵树中的节点。这些类之间的关系如下,由于三幻神都是Collection的实现类,而Collection继承了Iterable接口,该接口规定需要实现iterator(),spliterator(),forEach(Consumer<? super T> action),三个函数,前二者规定需要提供并行和串行迭代器,后面一个函数时函数式编程思想的体现。而XXXIterator和XXXSpliterator具体实现的串行和并行迭代器。

EntrySet,KeySet,Values的实现

Entry中放的元素,Node的定义如下:

static class Node<K,V> implements Map.Entry<K,V> {
        // 注意这个hash
        final int hash;
        final K key;
        V value;
        // Hash冲突后支持链表结构
        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; }

        // 重点,重写了hashCode
        // 生成新hash的方式为按位异或
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        // 重点,重写了equals方法
        public final boolean equals(Object o) {
            // 这是Object类中equals方法,表示含义是,当o与this的内存地址相等的时候,表示是同一个对象。
            if (o == this)
                return true;
            /* 下面对相等在HashMap中的语义进行扩展,具体的是,如果两个对象的key和value相等,则它们也被认为为相等。
其中key和value的比较使用的是equals的原始语义
            */
            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;
        }
    }

重要的东西已经写了注释,还需要提出的是,这个重写后的equals就是HashMap与IdentityHashMap之间的区别,即IdentityHashMap没有进行语义扩展而仅仅取决于equals的原始语义,也就是说IdentityHashMap判断相等的条件更为苛刻。
EntrySet,KeySet,Values的实现,建议只看EntrySet的实现,其他的都差不多:

// 注意EntrySet本质上是一个Set,原因是EntrySet不允许重复元素
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        // 与HashMap的size同步
        public final int size()                 { return size; }
        // 对EntrySet的清空操作实际上操作的是HashMap
        public final void clear()               { HashMap.this.clear(); }
        // Iterable规定需要实现的方法
        public final Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator();
        }
        // 本质上是Node的equals
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(hash(key), key);
            return candidate != null && candidate.equals(e);
        }
        // 同样调用的是HashMap的remove方法
        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Object value = e.getValue();
                return removeNode(hash(key), key, value, true, true) != null;
            }
            return false;
        }
        public final Spliterator<Map.Entry<K,V>> spliterator() {
            return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        // 使用modCount感知多线程操作
        public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (Node<K,V> e : tab) {
                    for (; e != null; e = e.next)
                        action.accept(e);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

// KeySet也是一个Set
final class KeySet extends AbstractSet<K> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<K> iterator()     { return new KeyIterator(); }
        public final boolean contains(Object o) { return containsKey(o); }
        public final boolean remove(Object key) {
            return removeNode(hash(key), key, null, false, true) != null;
        }
        public final Spliterator<K> spliterator() {
            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super K> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (Node<K,V> e : tab) {
                    for (; e != null; e = e.next)
                        action.accept(e.key);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

// values是一个Collection,对元素重复没有限制
final class Values extends AbstractCollection<V> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<V> iterator()     { return new ValueIterator(); }
        public final boolean contains(Object o) { return containsValue(o); }
        public final Spliterator<V> spliterator() {
            return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super V> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (Node<K,V> e : tab) {
                    for (; e != null; e = e.next)
                        action.accept(e.value);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

可以看到三幻神的大量功能是依赖于HashMap本身的,也就是说其功能是委托给HashMap来实现的,其实不难理解,因为它们都不持有数据,所以对数据的操作都要委托给HashMap,这在详细方法分析的时候会做详细说明。

串行迭代器

final class KeyIterator extends HashIterator
       implements Iterator<K> {
       public final K next() { return nextNode().key; }
   }

final class ValueIterator extends HashIterator
       implements Iterator<V> {
       public final V next() { return nextNode().value; }
   }

final class EntryIterator extends HashIterator
       implements Iterator<Map.Entry<K,V>> {
       public final Map.Entry<K,V> next() { return nextNode(); }
}
abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail,重点
        int index;             // current slot,这个是为了在table中找到下一个元素

        // 我们知道,HashMap底层是数组+链表+红黑树的组合,元素经过Hash运算散列之后,
        // 分布在数组当中,当产生hash冲突的时候,通过链表或者红黑树的方式对数组当前节点进行扩展以解决。
        // 所以在初始化的时候需要找到数组中第一个不为空的元素
        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }

        // 直接判断next是否为空
        public final boolean hasNext() {
            return next != null;
        }

        /* 获取下一个节点的逻辑为:
           1.看当前节点的next域有没有后继节点,也就是说hash冲突后形成的链式结构上还有没有元素。
           2.如果链式结构上没有元素或者未冲突,则在HashMap的数组结构中寻找下一个不为空的元素
         */
        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        // 实际的remove功能还是执行的HashMap的removeNode,这个函数在后面分析。
        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            removeNode(p.hash, p.key, null, false, false);
            expectedModCount = modCount;
        }
    }

串行迭代器(iterator)指的是线程不安全的迭代器,也就是我们通常说的,这个迭代器会抛出快速失败的异常。这也就是为什么我们在使用的迭代器的时候,如果需要修改集合元素,需要使用迭代器提供的功能,而不是调用集合类的方法,通常如果遵循这个原则,使用串行迭代器是比较安全的。但是有的时候,看起来违反了这个准则的代码也可以很好的工作,比如:

    // 代码
    Map<String, String> map = new HashMap<>();
    map.put("k1", "k");
    map.put("k2", "k");
    map.put("k3", "k");
    map.put("k4", "k");
    map.put("k5", "k");
    Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
    while (iterator.hasNext()){
      if(iterator.next().getKey().equals("k5"))
        map.remove("k5");
    }
    map.forEach((k, v) -> System.out.println(k + " " + v));
    // 执行结果
    k1 k
    k2 k
    k3 k
    k4 k

在while中不仅使用了迭代器,而且调用了原集合的remove方法,这似乎违背了我们的准则,但是代码居然很好的工作了,现在我对代码做一点修改:

    Map<String, String> map = new HashMap<>();
    map.put("k1", "k");
    map.put("k2", "k");
    map.put("k3", "k");
    map.put("k4", "k");
    map.put("k5", "k");
    Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
    while (iterator.hasNext()){
      // 注意,这里是修改部分
      if(iterator.next().getKey().equals("k4"))
        map.remove("k4");
    }
    map.forEach((k, v) -> System.out.println(k + " " + v));
    // 执行结果
    Exception in thread "main" java.util.ConcurrentModificationException

这个改动会导致程序抛出快速失败异常,这会让人感到很困惑。后来我去查询了一些关于iterator快速失败的文章,主要有两个讨论方向,第一个思考角度时这样的,他们认为Iterator工作在另一个线程,并持有排他锁,通过这种方式实现了Iterator并发修改时抛出异常的功能。另一个思考角度是通过分析源码得出Iterator的这个功能是依赖于modCount这个成员变量来实现的。我认为第二个思考角度是合理的(当然你也可以有其他看法),主要原因在于,从源码(jdk11)的角度来讲,Iterator的其它实现有没有启动了额外的线程,并且上了锁我不知道,但是至少在集合类中是没有这么干的。另外集合类的所有涉及到元素变动都维护了modCount的一致性。所以从这个角度来说,明显第二种观点是正确的。所以有必要讲一下是怎么通过modCount来感知并发操作并且及时抛出异常的。简单来说,就是CAS思想,CAS保证了线程安全的修改。在生成一个Iterator的时候,会持有HashMap当前的modCount值,当通过Iterator的API对集合元素进行变更,它会自己维持其持有的modCount和HashMap的modCount一致,以Iterator的remove为例:

// 这调用的是HashMap的函数,会修改HashMap的modCount值
removeNode(p.hash, p.key, null, false, false);
// expectedModCount是Iterator持有的,马上将该值与modCount同步
expectedModCount = modCount;

// 所以在下一次执行该函数的时候,这个判断可以通过
if (modCount != expectedModCount)

有了这个解释之后我们来看之前给出的例子,我们在迭代器中对最后一个元素调用了map的remove方法,这个时候已经造成了Iterator其内部持有的expectedModCount和HashMap持有的modCount不一致,此时我们如果再调用一次Iterator中依赖于expectedModCount的方法,比如remove,那么毫无疑问,会抛出异常,但恰恰就在于第一种情况我们在Iterator其内部持有的expectedModCount和HashMap持有的modCount不一致时完成了所有的迭代,而第二种则还没有完成,所以第一种情况下代码表现正常,而第二种则抛出了错误。

并行迭代器

    static class HashMapSpliterator<K,V> {
        final HashMap<K,V> map;
        Node<K,V> current;          // current node
        int index;                  // current index, modified on advance/split
        int fence;                  // one past last index
        int est;                    // size estimate
        int expectedModCount;       // for comodification checks

        HashMapSpliterator(HashMap<K,V> m, int origin,
                           int fence, int est,
                           int expectedModCount) {
            this.map = m;
            this.index = origin;
            this.fence = fence;
            this.est = est;
            this.expectedModCount = expectedModCount;
        }

        final int getFence() { // initialize fence and size on first use
            int hi;
            if ((hi = fence) < 0) {
                HashMap<K,V> m = map;
                est = m.size;
                expectedModCount = m.modCount;
                Node<K,V>[] tab = m.table;
                hi = fence = (tab == null) ? 0 : tab.length;
            }
            return hi;
        }

        public final long estimateSize() {
            getFence(); // force init
            return (long) est;
        }
    }
    static final class KeySpliterator<K,V>
        extends HashMapSpliterator<K,V>
        implements Spliterator<K> {
        KeySpliterator(HashMap<K,V> m, int origin, int fence, int est,
                       int expectedModCount) {
            super(m, origin, fence, est, expectedModCount);
        }

        // 如何进行分割,分割后返回多个Spliterator,利于并发处理。
        // 划分规则,二分,由于HashMap虽然数组容量确定,但是每个数组位置上实际有的元素
        // 数量并不确定,所以使用二分的方式并不是一个平衡划分的方式,在元素相对较多且冲突较少的情况下划分后的并发性能通常较好。
        public KeySpliterator<K,V> trySplit() {
            int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
            return (lo >= mid || current != null) ? null :
                new KeySpliterator<>(map, lo, index = mid, est >>>= 1,
                                        expectedModCount);
        }

        // 对剩下的所有元素执行action
        public void forEachRemaining(Consumer<? super K> action) {
            int i, hi, mc;
            if (action == null)
                throw new NullPointerException();
            HashMap<K,V> m = map;
            Node<K,V>[] tab = m.table;
            if ((hi = fence) < 0) {
                mc = expectedModCount = m.modCount;
                hi = fence = (tab == null) ? 0 : tab.length;
            }
            else
                mc = expectedModCount;
            if (tab != null && tab.length >= hi &&
                (i = index) >= 0 && (i < (index = hi) || current != null)) {
                Node<K,V> p = current;
                current = null;
                do {
                    if (p == null)
                        p = tab[i++];
                    else {
                        action.accept(p.key);
                        p = p.next;
                    }
                } while (p != null || i < hi);
                if (m.modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
        
        // 对当前元素执行action,若还有剩余元素,则返回true,否则返回false
        public boolean tryAdvance(Consumer<? super K> action) {
            int hi;
            if (action == null)
                throw new NullPointerException();
            Node<K,V>[] tab = map.table;
            if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
                while (current != null || index < hi) {
                    if (current == null)
                        current = tab[index++];
                    else {
                        K k = current.key;
                        current = current.next;
                        action.accept(k);
                        if (map.modCount != expectedModCount)
                            throw new ConcurrentModificationException();
                        return true;
                    }
                }
            }
            return false;
        }

        public int characteristics() {
            return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) |
                Spliterator.DISTINCT;
        }
    }
    static final class ValueSpliterator<K,V>
        extends HashMapSpliterator<K,V>
        implements Spliterator<V> {
        ValueSpliterator(HashMap<K,V> m, int origin, int fence, int est,
                         int expectedModCount) {
            super(m, origin, fence, est, expectedModCount);
        }

        public ValueSpliterator<K,V> trySplit() {
            int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
            return (lo >= mid || current != null) ? null :
                new ValueSpliterator<>(map, lo, index = mid, est >>>= 1,
                                          expectedModCount);
        }

        public void forEachRemaining(Consumer<? super V> action) {
            int i, hi, mc;
            if (action == null)
                throw new NullPointerException();
            HashMap<K,V> m = map;
            Node<K,V>[] tab = m.table;
            if ((hi = fence) < 0) {
                mc = expectedModCount = m.modCount;
                hi = fence = (tab == null) ? 0 : tab.length;
            }
            else
                mc = expectedModCount;
            if (tab != null && tab.length >= hi &&
                (i = index) >= 0 && (i < (index = hi) || current != null)) {
                Node<K,V> p = current;
                current = null;
                do {
                    if (p == null)
                        p = tab[i++];
                    else {
                        action.accept(p.value);
                        p = p.next;
                    }
                } while (p != null || i < hi);
                if (m.modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }

        public boolean tryAdvance(Consumer<? super V> action) {
            int hi;
            if (action == null)
                throw new NullPointerException();
            Node<K,V>[] tab = map.table;
            if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
                while (current != null || index < hi) {
                    if (current == null)
                        current = tab[index++];
                    else {
                        V v = current.value;
                        current = current.next;
                        action.accept(v);
                        if (map.modCount != expectedModCount)
                            throw new ConcurrentModificationException();
                        return true;
                    }
                }
            }
            return false;
        }

        public int characteristics() {
            return (fence < 0 || est == map.size ? Spliterator.SIZED : 0);
        }
    }
    static final class EntrySpliterator<K,V>
        extends HashMapSpliterator<K,V>
        implements Spliterator<Map.Entry<K,V>> {
        EntrySpliterator(HashMap<K,V> m, int origin, int fence, int est,
                         int expectedModCount) {
            super(m, origin, fence, est, expectedModCount);
        }

        public EntrySpliterator<K,V> trySplit() {
            int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
            return (lo >= mid || current != null) ? null :
                new EntrySpliterator<>(map, lo, index = mid, est >>>= 1,
                                          expectedModCount);
        }

        public void forEachRemaining(Consumer<? super Map.Entry<K,V>> action) {
            int i, hi, mc;
            if (action == null)
                throw new NullPointerException();
            HashMap<K,V> m = map;
            Node<K,V>[] tab = m.table;
            if ((hi = fence) < 0) {
                mc = expectedModCount = m.modCount;
                hi = fence = (tab == null) ? 0 : tab.length;
            }
            else
                mc = expectedModCount;
            if (tab != null && tab.length >= hi &&
                (i = index) >= 0 && (i < (index = hi) || current != null)) {
                Node<K,V> p = current;
                current = null;
                do {
                    if (p == null)
                        p = tab[i++];
                    else {
                        action.accept(p);
                        p = p.next;
                    }
                } while (p != null || i < hi);
                if (m.modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }

        public boolean tryAdvance(Consumer<? super Map.Entry<K,V>> action) {
            int hi;
            if (action == null)
                throw new NullPointerException();
            Node<K,V>[] tab = map.table;
            if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
                while (current != null || index < hi) {
                    if (current == null)
                        current = tab[index++];
                    else {
                        Node<K,V> e = current;
                        current = current.next;
                        action.accept(e);
                        if (map.modCount != expectedModCount)
                            throw new ConcurrentModificationException();
                        return true;
                    }
                }
            }
            return false;
        }

        public int characteristics() {
            return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) |
                Spliterator.DISTINCT;
        }
    }

关于并行迭代器是在java8后支持函数式编程后引入的。关于这部分的详细内容就不在这里详细介绍了,后面可能会总结1.8的新特性。简单来说就是便利的并发性能,java在底层实现了自适应的并发策略,而用户只需要提供相应的切分规则和并发需要执行的功能即可。想了解原理的可以参考下google大佬们那三篇改变世界的分布式文章。里面介绍的Map/Reduce就是底层并发策略的基础。

TreeNode

        // 类声明
        static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> 
         // 支持树功能的成员变量
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        // 继承自父类,依旧维持链表关系
        Entry<K,V> before, after;
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

在HashMap中我不会涉及过多的红黑树的细节,这些内容我会放在TreeMap中进行介绍。在这里想要说明一点的是虽然在hash冲突比较严重的时候,会将冲突的节点由链表转变为树,但是在转变为树之后并不说链表结构就不在了,也就说此时树和链表结构同时共存。树是为了提供高效查找功能,而链表是为了提供高效遍历功能。此时涉及到一个问题,就是链表的头节点和树的根节点问题,这是两个节点,可能相同也可能不同,同时,在数组中存放的可能是树的根节点,也可能不是。

以上,就是HashMap中用于支持其功能的内部数据结构,其中除了用来存放数据的Node和TreeNode之外,最重要的就是三幻神的迭代器实现。总结一下Map的迭代方式,第一种就是基于串行迭代器的增强for循环,和使用Iterator进行的显示的迭代循环,其中增强的for循环只是Iterator的一个包装,所以还是需要遵循迭代器的使用规则,第二种就是基于并行迭代器的循环方式,这种方式有些不同,形式为forEach(function action),其中我们只需要提供迭代时需要执行的功能,Java会自己根据相关的规则采取优化的并行策略。通常来说,提供索引访问的集合类还有第三种迭代方式,即我们传统的方式。

HashMap中重要的方法实现

get方法

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

通过调用getNode方法来获取,需要传递两个参数,其中一个是hash(key),我们来看看hash()这个函数。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这个函数的作用在于混合key的hash值的高16位和低16位,这样做的原因在于HashMap的容量一般不会太大,这样在做散列的时候会忽视高位的特征,所以将高位特征加入低位让其影响散列函数的行为,这个操作对降低散列后冲突的概论有一定帮助,但是并不是质的提升。
下面来看看getNode函数:

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // table存在且table不为空,且指定元素存在
        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;
            // 如果第一个不是我们要找的,则在其链表或者树中进行查找
            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;
    }

这段代码需要说明的是(n - 1) & hash是什么意思,简单来说这就是一个高效的取模运算。它能实现取模运算的原因在于,HashMap中规定容量必须是2的整数幂。当一个数n是2的整数幂的时候,n-1的二进制表示有一个别称,叫掩码,而与掩码进行按位与运算等同于取模运算,其本质是屏蔽超出位数的影响使其落在指定范围。比如HashMap的容量为16,则15的二进制表示为01111,则在按位与的过程当中,只有后四位的特征会被保留,而后四位的取值为0-15,等同于取模运算。如果理解了这个东西,那么还有一条规则就比较好理解了,即HashMap扩容之后,元素需要被重新散列,重新散列的结果只有两种,第一种为新索引与旧索引相同,第二种为新索引等于就索引加上旧的容量。举个例子来说,比如原来16容量不足,进行扩容,则被扩容为32,则31的二进制表示为11111,在容量为16的时候,hash值为10001和00001的两个元素按位与后结果都为1,此时产生了冲突,原因是它们的后四位都相同。在容量为32的时候,10001按位与后为17,而00001按位与后为1,可以清楚的看到,第5位二进制位生效了,而第5位就表示16为原HashMap容量。同理进行推广,结论就是上面描述的规则。
经过(n - 1) & hash运算之后我们直接获取到了数组的索引,此时我们直接看数组中该索引位置的元素是否为我们需要寻找的元素,如果是,返回,不是,则按节点类型进行查找。链表的查找方式我们不谈,说一下树的查找,即getTreeNode函数,下面是函数的源码:

final TreeNode<K,V> getTreeNode(int h, Object k) {
            // 1.首先找到红黑树的根节点;2.使用根节点调用find方法
            return ((parent != null) ? root() : this).find(h, k, null);
        }
// 典型的红黑树查找方式,类似于二叉排序树的查找方式
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;
                // h小于当前节点,则左子树
                if ((ph = p.hash) > h)
                    p = pl;
                // h大于当前节点,则右子树
                else if (ph < h)
                    p = pr;
                // 以下都是hash相等的情况
                // 若key相等或者广义相等,则找到啦
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                // hash相等但是key不等的情况,先处理两种特殊情况,左子树为空则找右子树,右子树为空则找左子树
                else if (pl == null)
                    p = pr;
                else if (pr == null)
                    p = pl;
                // 此时hash不能决定查找顺序,则如果元素实现了comparable接口,则按该接口顺序查找
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != 0)
                    p = (dir < 0) ? pl : pr;
                // 如果你没有实现comparable接口,则先遍历右后便利左
                else if ((q = pr.find(h, k, kc)) != null)
                    return q;
                else
                    p = pl;
            } while (p != null);
            return null;
        }

这里需要说明的是,如果你对前面Node的源码有印象,那么需要指明的是find中的hash指的是Node中的hash,其值为key和value按位与。当我们确定元素在数组中的位置时,使用的时key的hash值,当获取到在数组中的索引后,便不在使用key的hash值构造树或者链表了,因为同一个索引的元素其key的hash值经过散列后都相同,此时会使用Node的hash值进行处理,所以在find函数中出现了hash值相等的情况,但是这里hash值相等并不能说明就是需要查找的元素,因为Node的hash值由key和value共同决定,所以后面又对key进行单独判定,如果判定结果失败,此时表示Node的hash值也相同,那么如果对象实现了comparable接口,则用该接口来辅助构造之后的树,如果没有,则优先放右子树,虽然放右子树,但是红黑树有自平衡功能,放在右子树最后不一定在右子树,所以现在右子树查询,后再左子树查询。

put方法

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

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;
            // 如果当前entry节点存在,则更新当前节点
            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) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 到达阈值,变树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 如果e节点存在hash值和key值都与传入的相同,则e节点即为目标节点,跳出循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    // // 将p指向下一个节点
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                // HashMap中空操作,共LinkedHashMap用的
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        // 如果插入节点后节点数超过阈值,则调用resize方法进行扩容
        if (++size > threshold)
            resize();
        // 支持LinkedHashMap,在HashMap中是一个空操作
        afterNodeInsertion(evict);
        return null;
    }

首先来看看扩容的规则:

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        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
        }
        // 比较特殊的情况,如果老表的容量为0, 老表的阈值大于0, 是因为初始容量被放入阈值,则将新表的容量设置为老表的阈值
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            // 否则默认值
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 如果有必要,更新阈值
        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"})
        // 至此,扩容完成,下面要完成扩容后原来table中元素的处理,重散列使元素放置到正确的位置
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                // 原位置有元素
                if ((e = oldTab[j]) != null) {
                    // 置空,GC可回收
                    oldTab[j] = null;
                    // 如果没有链表或者树结构,好处理,重散列后放入对应位置即可
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    // 如果有树结构,则按照树结构进行重散列
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    // 否则按链表结构进行重散列
                    else { // preserve order
                        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;
    }
// 树的重散列规则,index是table的索引,bit是原来的容量
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
            TreeNode<K,V> b = this;
            // Relink into lo and hi lists, preserving order
            TreeNode<K,V> loHead = null, loTail = null;
            TreeNode<K,V> hiHead = null, hiTail = null;
            int lc = 0, hc = 0;
            // 从当前结点开始遍历红黑树
            for (TreeNode<K,V> e = b, next; e != null; e = next) {
                next = (TreeNode<K,V>)e.next;
                e.next = null;
                // 下面是调整链表的过程,在putVal中已经进行过说明
                if ((e.hash & bit) == 0) {
                    if ((e.prev = loTail) == null)
                        loHead = e;
                    else
                        loTail.next = e;
                    loTail = e;
                    ++lc;
                }
                else {
                    if ((e.prev = hiTail) == null)
                        hiHead = e;
                    else
                        hiTail.next = e;
                    hiTail = e;
                    ++hc;
                }
            }
            // 经过上述过程之后,原来的链表分别变味了两个链表,但此时两个链表的元素都还在一棵树当中,也就是说树还没有被分开
            if (loHead != null) {
                // 如果元素个数小于6则解树,untreeify的功能就是将链表中的节点由TreeNode替换为Node
                if (lc <= UNTREEIFY_THRESHOLD)
                    tab[index] = loHead.untreeify(map);
                else {
                    tab[index] = loHead;
                    // 否则重新构建树,treeify函数的功能就是构建一棵红黑树,详细实现在Treemap中讨论
                    if (hiHead != null) // (else is already treeified)
                        loHead.treeify(tab);
                }
            }
            // 同理如上
            if (hiHead != null) {
                if (hc <= UNTREEIFY_THRESHOLD)
                    tab[index + bit] = hiHead.untreeify(map);
                else {
                    tab[index + bit] = hiHead;
                    if (loHead != null)
                        hiHead.treeify(tab);
                }
            }
        }

关于扩容规则,在java8之前的扩容方案中,如果并发操作,会造成死循环的问题,其原因在于扩容后元素顺序发生了变动,在1.8之后的扩容方案中解决了这个问题,所以死循环的问题不会再发生,其次,不要在并发的情况下使用HashMap,别人本来就不保证并发安全。再来看看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;
            // 从根节点进行遍历
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                // 小于则走左子树
                if ((ph = p.hash) > h)
                    dir = -1;
               // 大于则走右子树
                else if (ph < h)
                    dir = 1;
                // 如果是该元素,则返回该元素
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
                // 如果没有实现comparable接口
                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;
                    }
                    // 没找到,定义一套规则来处理这种极端情况,规则为:
                    // 1.处理元素为空的情况
                    // 2.比较元素类名
                    // 3.比较元素的hashCode值
                    dir = tieBreakOrder(k, pk);
                }

                // 在确定的位置插入节点,并维持树的结构
                TreeNode<K,V> xp = 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);
                    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;
                    // 将树的根节点放到链表的头节点中,实现原理是通过调整链表中元素的位置实现的,
                    // 最后还会检查整个树是否符合红黑树的定义
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

之后再来看看变树的函数treeifyBin:

// 这个函数很简单,本质是在调用treeify函数
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        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 {
                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);
        }
    }

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;
        // 开始找
        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) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        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)))) {
                // 如果是树节点执行树节点的删除,本质上是红黑树节点的删除,在TreeMap中讨论,只是需要额外维护链表顺序
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                // 否则链表删除
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                // LinkedHashMap用的,HashMap中是空操作
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

这里提一下LinkedHashMap,LinkedHashMap是HashMap的子类,LinkedHashMap保留了HashMap的底层结构,同时加入了双向链表的结构,双向链表的结构主要就是为了保存插入顺序,LinkedHashMap只要实现在Map中遗留的一些函数即可实现这些功能。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值