java8 HashMap学习

java.util.HashMap 是JDK里散列表的一个实现,主要用来提供对象与对象之间的映射。JDK6里采用位桶+链表的形式实现,Java8里采用的是位桶+链表/红黑树的方式,非线程安全。关于jdk1.6的实现,这篇博文 Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例给出了很详细的分析。本篇主要说说java8中java.util.HashMap的不同实现。

首先介绍HashMap中几个重要字段的含义。

    /**
     * JDK1.6中table字段为Entry数组。JDK1.8为Node数组,其中Node实现了Map.Entry接口
     * static class Node<K,V> implements Map.Entry<K,V>。
     *实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Node数组中的。 
     */
    transient Node<K,V>[] table;
     /**
     * HashMap的集合视图对象。
     * 
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * HashMap中Key-Value的数量
     */
    transient int size;

    /**
     * HashMap被结构性更改的次数。结构性更改包括映射数量的变化和HashMap内部结构的变化(即Rehash)
     * 同时它用来实现HashMap集合视图的fail-fast机制。
     * fail-fast是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。
     * JDK1.6中该字段为 transient volatile int modCount;其中volatile是为了强制从主内存读写该字段,保证了多线程下其并发可见性。
     *JDK1.8除去了volatile,毕竟该类本身是非线程安全类,volatile变量的读写需要Memory Barrier,会禁止编译器指令重排序,会降低性能。
     */
    transient int modCount;

    /**
     * 它是HashMap阀值。其大小为 容量*负载因子。当Key-Vaule的值超过了该值时会重新调整HashMap的大小,调用 resize()方法。
     *并发条件下rehash是不安全的。
     */
    int threshold;

    /**
     * 哈希表的负载因子 
     */
    final float loadFactor;

HashMap提供了4个构造函数
    //使用指定 容量和 负载因子构造
    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() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
       }

    //使用另外一个Map构造
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
        }

另外,JDK8对HashMap进行了优化,提供了以下几个字段,
    static final int TREEIFY_THRESHOLD = 8;//当一个bin中的节点个数超过该值,则会调用treeifyBin来将bin中链表变为红黑树实现。

    static final int UNTREEIFY_THRESHOLD = 6;

    static final int MIN_TREEIFY_CAPACITY = 64;

当我们使用put方法插入数据时候,会调用putVal方法。

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
	
	//判断tab是否为空或者长度为0 则调用 resize()方法。
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
	//找到Key值对应的bin,并且为空,则直接插入
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
	    //若改节点是TreeNode,即节点的红黑树实现。则调用TreeNode相应方法
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
	    //不是TreeNode,则遍历链表	
            else {
                for (int binCount = 0; ; ++binCount) {
                    //到达链表尾部还没找到Key,则生成新的链表节点。同时判断是否满足优化条件,满足则把链表转化为红黑树,以提高查询效率
                    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;
                    p = e;
                }
            }
            //更新已经存在Key的Value值
            if (e != null) { 
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

如上所示,当满足条件if (binCount >= TREEIFY_THRESHOLD - 1) ,把链表变成treemap,这样查找效率从o(n)变成了o(log n)。

很久以前就一直想写博客,但一直怕写的不好。总归是要开始的,当做自己的学习笔记也不错。以上出于个人的理解,有疏漏错误之处还望大家指出。

另外,参考了另一篇博客

HashMap在JDK8中的提升



猜你喜欢

转载自blog.csdn.net/quanzhongzhao/article/details/45363133