hashmap源码分析

2018-07-19  本文已影响59人  msrpp

常量定义

hashmap实现是存储键值对的一种数据结构。首先看它的声明:

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

可以得知HashMap是继承自抽象类AbstractMap且实现了Map接口。(这里有一个疑问,既然抽象类AbstractMap也是实现了Map,是否可以直接写成:

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

看下HashMap对应的一些常量,在此之前我们需要对hashmap的存储结构有所了解。实际是Node<K,V>[] table,数组的每个节点为一个Node链表,每个Node存储着键值对。我们将数组的每个元素称为bucket。Node的结构体如下,存储了Key的hash值,Key和Value,还有一个指向下个节点的指针。

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

HashMap内部定义的常量:

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

    static final int MAXIMUM_CAPACITY = 1 << 30; //,

    static final float DEFAULT_LOAD_FACTOR = 0.75f;//
    static final int TREEIFY_THRESHOLD = 8;

    static final int UNTREEIFY_THRESHOLD = 6;

    static final int MIN_TREEIFY_CAPACITY = 64;

常用方法

HashMap最常用的就是get,put 两个方法。下面我们拿一个简单的例子来说明HashMap的工作流程,现有如下代码:

         HashMap<String,Integer> map = new HashMap<>();

        map.put("abc",123);
        map.put("def",456);
        Integer result = map.get("abc");
        Boolean b1 = map.containsValue(123);
        Boolean b2= map.containsKey("def");
        map.forEach((key, value) -> {
            System.out.println(key+value);
        });
        map.remove("123");

首先调用默认构造函数:

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

put方法和注释:

   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)
           //首次调用put将第一次resize();
           n = (tab = resize()).length;
       if ((p = tab[i = (n - 1) & hash]) == null)
           //若p为null,则当前的桶是空的,可以直接插入数据。
           tab[i] = newNode(hash, key, value, null);
       else {
           //这个hash值对应的桶之前已经有数据了。
           Node<K,V> e; K k;
           if (p.hash == hash &&
               ((k = p.key) == key || (key != null && key.equals(k))))
               e = p;
           else if (p instanceof TreeNode)
                 //这里如果node是TreeNode的实例,则这个链表就已经变成红黑树了。
               e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
           else {
               //循环查找链表的节点key是否和当前的key相等,binCount为链表长度,大于TREEIFY_THRESHOLD 触发treeifyBin
               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;
                   }
                   if (e.hash == hash &&
                       ((k = e.key) == key || (key != null && key.equals(k))))
                       break;
                 //已有相同的key插入,直接覆盖即可。
                   p = e;
               }
           }
           if (e != null) { // existing mapping for key
               V oldValue = e.value;
               if (!onlyIfAbsent || oldValue == null)
                   e.value = value;
               afterNodeAccess(e);
               return oldValue;
           }
       }
       ++modCount;
       //size 为当前map存储的node数量,用来判断是否需要resize
       if (++size > threshold)
           resize();
       afterNodeInsertion(evict);
       return null;
   }

说下几个自我感觉的重点:

注意下remove中几个参数的意思:node:要查找的目标节点;p:,如果node是bucket中的第一个节点,pre 即是node,否则是node的pre节点;matchValue:是否需要key,value都匹配才删除。
get的代码:

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

remove的代码:

    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)))) {
                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;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

containsValue和containsKey方法

因为HashMap是按照Key来排序的,调用containsValue将循环遍历所有node来查找,效率低下,不建议过多使用containsValue函数。

forEach方法

因为HashMap不是线程安全的,如果在forEach执行过程中table中的内容发生了变化。modCount会发生变化,此时抛出一个ConcurrentModificationException,实现思想和乐观锁一致。

HashMap的迭代器。

附示例代码

        HashMap<String,String> ss = new HashMap<>();
        ss.put("1","a");
        ss.put("2","b");
        Iterator<Map.Entry<String ,String>> iterator = ss.entrySet().iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

我们可以通过HashMap.entrySet()来获取一个该对象的Entry集合entrySet变量,开始我很疑惑,HashMap中并没有对这个变量做增加元素等操作。那怎么能平白产生数据呢?后来看到了HashMap的内部类复写了EntrySet的iterator方法。返回的是这个类对象。

    final class EntryIterator extends HashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }

其中nextNode实现

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

这下明白了,变量entrySet并没有存储实际数据,只是通过iterator方法返回了Hashmap中的table存储的Node。需要注意的是,iterator对象中存储了table的位置,和当前节点的引用。这样在调用next的时候才可以返回下一个值。

LinkedHashMap

LinkedHashMap继承自HashMap,比其多了一个功能,就是可以按照插入顺序遍历出元素。原理: LinkedHashMap内部维护了一个链表,且重写了newNode方法,在新建节点的同时,就已经完成了链表的维护.LinkedHashMap#Entry<K,V>#newNode代码如下:

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }
上一篇 下一篇

猜你喜欢

热点阅读