数据结构解析-HashMap

概要

HashMap在JDK1.8之前的实现方式 数组+链表,但是在JDK1.8后对HashMap进行了底层优化,改为了由 数组+链表+红黑树实现,主要的目的是提高查找效率。

如图所示:

JDK版本 实现方式 节点数>=8 节点数<=6
1.8以前 数组+单向链表 数组+单向链表 数组+单向链表
1.8以后 数组+单向链表+红黑树 数组+红黑树 数组+单向链表

HashMap

1.继承关系

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

2.常量&构造方法

    //这两个是限定值 当节点数大于8时会转为红黑树存储
    static final int TREEIFY_THRESHOLD = 8;
    //当节点数小于6时会转为单向链表存储
    static final int UNTREEIFY_THRESHOLD = 6;
    //红黑树最小长度为 64
    static final int MIN_TREEIFY_CAPACITY = 64;
    //HashMap容量初始大小
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    //HashMap容量极限
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //负载因子默认大小
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //Node是Map.Entry接口的实现类
    //在此存储数据的Node数组容量是2次幂
    //每一个Node本质都是一个单向链表
    transient Node<K,V>[] table;
    //HashMap大小,它代表HashMap保存的键值对的多少
    transient int size;
    //HashMap被改变的次数
    transient int modCount;
    //下一次HashMap扩容的大小
    int threshold;
    //存储负载因子的常量
    final float loadFactor;

   //默认的构造函数
   public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    //指定容量大小
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
     //指定容量大小和负载因子大小
    public HashMap(int initialCapacity, float loadFactor) {
        //指定的容量大小不可以小于0,否则将抛出IllegalArgumentException异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
         //判定指定的容量大小是否大于HashMap的容量极限
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
         //指定的负载因子不可以小于0或为Null,若判定成立则抛出IllegalArgumentException异常
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
         
        this.loadFactor = loadFactor;
        // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
        this.threshold = tableSizeFor(initialCapacity);
    }
    //传入一个Map集合,将Map集合中元素Map.Entry全部添加进HashMap实例中
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        //此构造方法主要实现了Map.putAll()
        putMapEntries(m, false);
    }

3.Node单向链表的实现

//实现了Map.Entry接口
 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;
        }
        //equals属性对比
        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;
        }
    }

4.TreeNode红黑树实现

  static final class TreeNode<K,V> extends LinkedHashMap.LinkedHashMapEntry<K,V> {
        TreeNode<K,V> parent;  // 红黑树的根节点
        TreeNode<K,V> left; //左树
        TreeNode<K,V> right; //右树
        TreeNode<K,V> prev;    // 上一个几点
        boolean red; //是否是红树
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }

        /**
         * 根节点的实现
         */
        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }
    ...

5.Hash的计算实现

//主要是将传入的参数key本身的hashCode与h无符号右移16位进行二进制异或运算得出一个新的hash值
 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
延伸讲解
5.1.下面的做了一个例子讲解,经过hash函数计算后得到的key的hash值
 
5.2那为什么要这么做呢?直接通过key.hashCode()获取hash不得了吗?为什么在右移16位后进行异或运算?
答案 : 与HashMap的table数组下计算标有关系
我们在下面讲解的put/get函数代码块中都出现了这样一段代码
//put函数代码块中
tab[i = (n - 1) & hash]) 
//get函数代码块中
tab[(n - 1) & hash])

我们知道这段代码是根据索引得到tab中节点数据,它是如何与hash进行与运算后得到索引位置呢! 假设tab.length()=1<<4

 
 
 
//put函数代码块中
tab[i = (n - 1) & hash]) 
//get函数代码块中
tab[(n - 1) & hash])

猜你喜欢

转载自www.cnblogs.com/deityjian/p/11965430.html