HashMap底层实现和扩容机制

目录

一、HashMap简介

二、有关数据结构的分析

三、主要方法解析

1、构造器

2、其他方法

3、get方法

4、put方法

5、resize方法


一、HashMap简介

        HashMap主要是用来存放键值对的,其key可以是null但是只能存在一个是null,而value可以存在多个。 HashMap是非线程安全的,实现了Map接口,是java中常用的集合之一,JDK8之前是有数组和链表实现的,而哈希表是为了减少哈希碰撞的;JDK8后对哈希碰撞优化了许多,当链表的长度大于默认的阈值的时候(默认为8),(当长度大于8小于64的候,会 先进行扩容,而不是进行转换为红黑树,只有长度大于等于64的时候,才会转换为红黑树),会进行扩容,转换为红黑树只是为了减少查找的时间。HashMap默认的初始化的长度是16,每次的扩容会增加为原来的两倍。

二、有关数据结构的分析

        HashMap集合,其实就是一个散列表,使用扰动函数hash(),将key传入,通过key的hashcode值来计算hash值,然后通过(n-1)&hash,经过与运算得到存放的位置,当位置存在元素的时候,会比较key是否相等,即比较存在的元素和要存进去的元素的hash值和key值是否相等,相等就覆盖,不相等就存在元素后面,(无的理解就是,因为是通过数组和链表来实现的,那么主要的部分是数组,每一个数组元素的位置就是存放的一个链表,通过与运算得到的位置存在元素的时候,判断过后又不相等,那么就把他存在这个元素的后面,不知道我理解对不对),其实扰动函数就是为了减少哈希碰撞。

这是JDK8的hash扰动函数

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

这是JDK7的扰动函数

   static final int hash(Object key) {
        int h;
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

         明显可以看出JDK7比8多扰动了两次,所以在新能上会比JDK8要差许多,也就减少了一些哈希碰撞,其实hash方法主要是解决一些hashcode计算差的实现。

        JDK8之后的最大优化就是这个扩容的机制,减小哈希碰撞,解决了链表长度带来的搜索能力差的问题,在大于64的时候转换为红黑树,大大减少了查询时间。当链表长度大于阈值8的时候,会先调用treeifyBin()这个方法,可以看出,先会获取到传入map长度为多少,判断长度是否为null或者小于64,满足才会调用resize()方法,进行扩容;如果长度大于64,那么就会执行这个方法进行转换为红黑树。

 final void treeifyBin(Node[] tab, int hash) {
        int n, index; Node e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode hd = null, tl = null;
            do {
                TreeNode 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);
        }
    }

底层的字段解析

 //初始容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//负载因子,默认为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//该值是在判断是否转化为红黑树时使用的阈值,即想当于一个阈值计数,必须大于2小于8,大于8就会判断,而 
//扩容
static final int TREEIFY_THRESHOLD = 8;
//当改变大小的期间,bucket的数量小于这个值
static final int UNTREEIFY_THRESHOLD = 6;
//判断是否转换为红黑树的最大临界值,当大于等于这个值的时候,就会转换为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;
//加载因子
final float loadFactor;

三、主要方法解析

1、构造器

        ①这个构造器,主要是两个参数,初始化容量和加载因子

//两个参数 initialCapacity 初始容量 loadFactor 加载因子
public HashMap(int initialCapacity, float loadFactor) {
     /**
       * 开始时判断初始容量是否大于零,成立则会抛出异常IllegalArgumentException
       * 参数异常,即非法初始化容量
     */
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        //当大于最大容量时,则会将容量规定为最大容量
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //当加载因子小于等于零或者为空的时候也会抛出一个异常IllegalArgumentException,参数异常,            
        //非法
        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);
    }

③这个是没有参数的,无参构造器

//默认容量大小为16,负载因子为0.75 
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

④传递的是一个map集合

//把一个map集合转化为hashmap集合,负载因子为默认的0.75
public HashMap(Map m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
//会调用下面这个函数
        putMapEntries(m, false);
    }

final void putMapEntries(Map m, boolean evict) {
    //获取得到的map集合的大小
        int s = m.size();
    //当map的容量大于零的时候
        if (s > 0) {
    //当node值为null是,即里面没有对象
            if (table == null) { // pre-size
    //计算ft的值与最大容量对比,获得的值给t
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
    //判断t的大小与目标的容量的2次幂
                if (t > threshold)
    //满足就替换
                    threshold = tableSizeFor(t);
            }
    //判断s与目标的容量的2次幂
            else if (s > threshold)
    //满足就调用方法进行扩容
                resize();
    //否则就进行转移数据到新的hashmap集合中,使用迭代器
            for (Map.Entry e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

2、其他方法

//返回集合的大小
 public int size() {
        return size;
    }

//返回集合是否为空
 public boolean isEmpty() {
        return size == 0;
    }

//这个方法就是判断传过来的hash值,和key值是否存在
 final Node getNode(int hash, Object key) {
        Node[] tab; Node first, e; int n; K k;
    //判断table中不为空,或者长度大于零,并且位置的n-1与hash值进行与运算是不为零的,才能进行下面         
    //的操作
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
    //要判断第一个hash值和传来的hash值是否相等,总要检查第一个节点
            if (first.hash == hash && // always check first node
    //判断第一个节点的key值是否等于这个传来的key或者传过来的key不等于null,并且用equals来比较也       
    //是相等的才会返回第一个节点的node值
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
    //判断第一个节点的下一个节点不为null
            if ((e = first.next) != null) {
    //判断第一个节点是否是树节点,是就调用getTreeNode()方法计算数节点
                if (first instanceof TreeNode)
                    return ((TreeNode)first).getTreeNode(hash, key);
    //不满足上面这些,就会循环遍历找到满足条件的key值
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
    //循环条件是下一个不为null
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

        下面这个方法,put是hashmap集合中的唯一的一个存放值的方法(供使用者调用的),调用的putVal()方法主要是为了,判断存入的值是否在链表中存在,判断这个key的hash值是否是节点值,不是map的节点值,还会判断是否是treemap的树节点值,是节点值,还会判断key是否存在,如果key存在还要满足使用equals()方法判断两个key值是否是相等的,相等就会覆盖,不等就会使用拉链法,放在该值的后面,这些作完还需要判断阈值大小,如果在8到64之间要进行扩容,resize()方法。afterNodeInsertion()方法只是会函数,为了方便LinkedHashMap()的处理。

  //这个方法和get()差不多,get是获取value值,这个就是想判断集合中是否存在这个key,返回boolean类型
public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    
        Node[] tab; Node p; int n, i;
    //判断table的node是否存在
        if ((tab = table) == null || (n = tab.length) == 0)
    //满足就进行扩容
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
    //数据所在的位置是不是为null
            tab[i] = newNode(hash, key, value, null);
        else {
            Node e; K k;
    //判断p的hash值和传过来的key的hash值是否相等,或key不等于null与key调用equals判断为true
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
    //满足则有e与p是相等的
                e = p;
            else if (p instanceof TreeNode)
    //判断是不是tree的节点,是就调用getTreeNode()方法得到treeNode值
                e = ((TreeNode)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)
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
    //没有存在映射的值
            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;
    }

         这个方法是生成树,从这个节点开始,balanceInsertion这个方法就是,判断左右节点是否有值,还有就是父节点是否存在值,然后返回Treeify()方法,结束循环,执行moveRootToFront方法,最后就是,把集合中的值搬移到另外一个新的tree树结构的集合中,要确保root是第一个节点,要计算第一个值的位置,通过n-1&hash做与运算的到位置,把node的值给到frist;当root不是第一个节点的时候,刚刚计算得到的位置的value值就是root,root.pre就会在下一次删除的时候断开链接。继续判断下一个节点是不是为null,frist.pre节点后的节点链接断开;这些不满足就会得到root节点后的第一个节点就是frist,然后root断开链接后的节点为null(这是root是第一个节点的情况)。

        然后调用checkInvariants(root)方法,排序的方法,要判断父节点parent,左节点left,右节点right,大小进行排序,使用二叉树排序

//判断两个对象是不是一个对象,是就返回1,不是就返回-1
static int tieBreakOrder(Object a, Object b) {
       int d;
       f (a == null || b == null ||
        (d = a.getClass().getName().
        compareTo(b.getClass().getName())) == 0)
        d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
              -1 : 1);
        return d;
    }

final void treeify(Node[] tab) {
    //初始化TreeNode集合为null
            TreeNode root = null;
    //循环遍历,获取node值,直到下一个是null,才停止循环
            for (TreeNode x = this, next; x != null; x = next) {
                next = (TreeNode)x.next;
                x.left = x.right = null;
                if (root == null) {
    //如果节点值为null,不是红节点,节点值为x
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
    //计算x的hash值
                    K k = x.key;
                    int h = x.hash;
                    Class kc = null;
                    for (TreeNode p = root;;) {
    //对比p的hash值和x的hash值,对dir赋值
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
    //数据类型比较器,是null还是string
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
    //比较k和pk是不是相同的值
                            dir = tieBreakOrder(k, pk);

                        TreeNode xp = p;
    //判断返回的dir是1还是-1,进而判断p节点的左右是否有值
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
    //没有值就把xp设为上一节点
                            x.parent = xp;
    //dir为-1的话,左节点就为x
                            if (dir <= 0)
                                xp.left = x;
                            else
    //否则右节点为x
                                xp.right = x;
    //然后调用方法balanceInsertion计算root值,然后跳出循环执行moveRootToFront方法
                            root = balanceInsertion(root, x);
                            break;
                        }
                    }
                }
            }
            moveRootToFront(tab, root);
        }


static  TreeNode balanceInsertion(TreeNode root,TreeNode x) {
            //设置x为红节点
            x.red = true;
            //定义四个TreeNode类型的变量
            for (TreeNode xp, xpp, xppl, xppr;;) {
            //判断父节点是否为空
                if ((xp = x.parent) == null) {
            //为空就不是红节点
                    x.red = false;
                    return x;
                }
            //xp不是红节点,或者xp不是父节点
                else if (!xp.red || (xpp = xp.parent) == null)
            //返回root
                    return root;
            //判断左右节点是不是红节点
                if (xp == (xppl = xpp.left)) {
                    if ((xppr = xpp.right) != null && xppr.red) {
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        if (x == xp.right) {
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else {
                    if (xppl != null && xppl.red) {
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        if (x == xp.left) {
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }
//搬移到新的集合中
static  void moveRootToFront(Node[] tab, TreeNode root) {
            int n;
//root,tab及tab的长度不能为null
            if (root != null && tab != null && (n = tab.length) > 0) {
//计算第一个数值的位置,位置n-1与root的hash值做与运算得到位置
                int index = (n - 1) & root.hash;
//得到第一个位置的节点值
                TreeNode first = (TreeNode)tab[index];
                if (root != first) {
//如果root不是第一个节点,那么会确定计算得到的位置是第一个节点值,value的值就root
                    Node rn;
                    tab[index] = root;
                    TreeNode rp = root.prev;
//下一次删除的时候要断开链接
                    if ((rn = root.next) != null)
                        ((TreeNode)rn).prev = rp;
                    if (rp != null)
                        rp.next = rn;
                    if (first != null)
                        first.prev = root;
                    root.next = first;
                    root.prev = null;
                }
                assert checkInvariants(root);
            }
        }
//递归不变的检查,得到树结构
  static  boolean checkInvariants(TreeNode t) {
            TreeNode tp = t.parent, tl = t.left, tr = t.right,
                tb = t.prev, tn = (TreeNode)t.next;
            if (tb != null && tb.next != t)
                return false;
            if (tn != null && tn.prev != t)
                return false;
            if (tp != null && t != tp.left && t != tp.right)
                return false;
            if (tl != null && (tl.parent != t || tl.hash > t.hash))
                return false;
            if (tr != null && (tr.parent != t || tr.hash < t.hash))
                return false;
            if (t.red && tl != null && tl.red && tr != null && tr.red)
                return false;
            if (tl != null && !checkInvariants(tl))
                return false;
            if (tr != null && !checkInvariants(tr))
                return false;
            return true;
        }

3、get方法

        get方法就是通过key来计算节点值,通过调用getNode来的到key的节点值,先要判断是否右这个节点,才能得到value值。getNode方法在我看来就是计算hash值,找到位置,也是通过(n - 1) & hash来做与运算得到位置,得到位置,就会判断这个位置上的key值是不是和给的值一样,不一样就使用拉链法放在后面,一样就覆盖,在对比key的时候既要用==来判断,也要用equals来判断key值,最后才能得到key所对应的value值。

//获取集合中的数据
 public V get(Object key) {
       Node e;
//调用方法getNode(),判断是否有这个hash值,和key值,有就返回key对应的value值,没有就返回null
       return (e = getNode(hash(key), key)) == null ? null : e.value;
   }


    final Node getNode(int hash, Object key) {
        Node[] tab; Node 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)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;
    }

4、put方法

        put方法其实和get方法相似,都要通过key值判断集合中是否存在key这个键,没有才能存储值,若果相同那么就覆盖,会调用putVal方法,上面右,就部解释来,其实就和getNode相似。

//put方法为hashmap集合的唯一一个存储值的方法(传递单个值)
public V put(K key, V value) {
//调用putVal()方法存储
      return putVal(hash(key), key, value, false, true);
   }

5、resize方法

        扩容方法,先会判断容量大小,如果为空,那么就会以初始容量大小进行扩容,不为空,那么就会把数组大小给到oldCap中,然后会定义新的容量大小和新的阈值。然后oldCap与最大值进行比较,大于最大值,那么阈值大小也等于最大值;否则那就是oldCap左移一位的到newCap,相当于扩大两倍,并且要判断是否大于最大值,然后阈值大小也要扩大到原来的两倍。下面就要判断oldTab是否为null,进行for循环,新建Node数组e,先回判断第一个节点是否为null,不等于null那就把老的node改为null,在判断前已经把旧的值给新的来,那么就会判断下一个节点是不是null,等于null的话那就计算新的数组中的位置把e放到新数组中;然后会判断是不是treeNode,即是不是tree类数据,是就调用split方法把树进行拆分;后面就是把旧的数组里面的值搬移到新的数组中,因为不确定旧的数组中的节点是不是在新的数组中的同样位置,所以要确定搬移的位置,所以定义了四个数据来确定新数组的位置,其实新的位置就是在原来旧的位置和旧的位置加上旧的数组的大小,就在这两个位置,所要要判断是在高位,还是在低位,要hash值在进行因此判断,得到新的数组的位置,得到新的链表后,然后把旧的值搬移到新的位置。

 final Node[] resize() {
        Node[] oldTab = table;
    //判断数组oldTab是否为空
        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;
    //然后返回oldTab
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1;
        }
        else if (oldThr > 0) 
            newCap = oldThr;
        else {     
        //获得新的数组容量大小和阈值大小,为了下面创建新的数组做铺垫          
            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"})
            Node[] newTab = (Node[])new Node[newCap];
        table = newTab;
    //创建新的数组
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
        //计算新数组的下标
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode)e).split(this, newTab, j, oldCap);
                    else {
                        Node loHead = null, loTail = null;
                        Node hiHead = null, hiTail = null;
                        Node 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;
    }

你可能感兴趣的:(java,数据结构)