简单的HashMap,一句话概括“数组+链表+红黑树”

HashMap

     1、HashMap简介

             1、HashMap是我们平时经常使用的一个对象,我们应该知道他是key-value的形式存放的,那具体底层是如何实现的呢,这片文档重新将map源码重要的代码注释全部写了一遍,我们一步一步看代码

             2、简单介绍下hashMap存储的方式

                    1、初始化一个16长度的数组,每次当新对象put进来的时候,会先计算出这个对象的哈希值,存入到对应该哈希值的角标中

                    2、如果出现了两个哈希值相同的,会将新传入的对象传入到上一个对象的next中,这也就形成了链表,当链表长度大于8,数组长度大于64,就会将该条链表转换为红黑树,主要是为了查询效率

                    3、当数组存放的容量大于4分之三(可以自己设定),会进行扩容,扩容时将老数组的值重新全部计算哈希值传入到新的数组中

     2、HashMap源码部分

                以下源码我已经上传在资源中,https://download.csdn.net/download/qq_38384460/14819831,点击即可下载,不需要积分哦!

           1、继承关系

                   1、AbstractMap:抽象map,实现了Map接口,hashMap继承AbstractMap主要是为了实现该AbstractMap的方法

                   2、Map:提供了接口以便子类使用,例如get,put等等

                   3、Cloneable:表示HashMap可以实现克隆

                   4、Serializable:序列化,可以被本地储存

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

           2、静态变量

                  从以下静态变量中,我们可以看出,hashMap的长度必须为2的幂次方,最大容量,扩容阈值(比如16的0.75就是12,12就会扩容),链表升级为红黑树的阈值为8,红黑树退化回链表的阈值为6,最后一个变量表明不止链表大于8,并且需要数组长度大于64,这时一个并且的关系

    /**初始化长度 16,长度必须是2的幂次方*/
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    /**最大容量 1073741824 大于该容量就不再扩容*/
    static final int MAXIMUM_CAPACITY = 1073741824;
    /**容量阈值 到达容量的 百分之75 扩容*/
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    /**当链表长度大于8  转换为红黑树的阈值*/
    static final int TREEIFY_THRESHOLD = 8;
    /**当链表长度小于6  红黑树退化为链表*/
    static final int UNTREEIFY_THRESHOLD = 6;
    /**数组容量大于64 才能升级为 红黑树*/
    static final int MIN_TREEIFY_CAPACITY = 64;

         3、常量

             这里主要定义了必要的几个对象,table是为了存放现有的数组,数组的长度,被修改的次数,具体的扩容阈值threshold(上面静态常量中存放的是0.75,这里就是12)

    /**数组*/
    transient Node<K, V>[] table;
    /**数组转换为的set*/
    transient Set<Map.Entry<K, V>> entrySet;
    /**数组数量*/
    transient int size;
    /**数组被修改次数*/
    transient int modCount;
    /**数组大于该值会被扩容  计算方式 (容量 *负载系数)例如默认就是 16 * 0.75 = 12 大于12就会扩容*/
    int threshold;
    /**容量阈值 到达容量的 百分之75 会扩容 该值是在构造时传入,如果没有传 会将 DEFAULT_LOAD_FACTOR 传入*/
    final float loadFactor;

         4、构造方法

                 1、无参构造会默认数组长度和扩容阈值,也可以自定义传入数组长度和扩容阈值,但是注意一点,传入的数组长度会被tableSizeFor方法计算成大于该长度的的最小的二次方

    /**
     * 无参构造
     * 只需要传入扩容阈值
     * 其他字段都是默认
     *
     * 具体扩容的数组长度 没有初始化
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
    }

    /**
     * 只传数组长度的构造
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * 构造
     * initialCapacity 初始化数组长度  不需要再次扩容
     * loadFactor
     */
    public HashMap(int initialCapacity, float loadFactor) {
        //初始化长度不能小于0
        if (initialCapacity < 0)  throw new IllegalArgumentException("Illegal initial capacity: " +  initialCapacity);

        //判断最大长度
        if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY;

        //如果扩容小于0或者没有 抛异常
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor);

        //初始化扩容
        this.loadFactor = loadFactor;

        //具体扩容的数组长度 必须为该值的最小的二次方 tableSizeFor可以返回该值的最小的二次方
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * 返回这个值的最小的2的n次方
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

       5、内部类

               在讲方法前,我们先看下比较重要的node,这个就是核心要操作的对象,传入的值实际上就是传入到node中了,node中有几个比较参数,hash其实就是key的hash值,key,value,next就是下一个值,也就是有了next才有了链表

    // new一个Node
    Node<K, V> newNode(int hash, K key, V value, Node<K, V> next) {
        return new Node<>(hash, key, value, next);
    }
    /**
     * 数组
     * @param <K>
     * @param <V>
     */
    static class Node<K, V> implements Map.Entry<K, V> {
        final int hash; //hash值
        final K key;
        V value;
        Node<K, V> next;  //链表的下一个节点

        //构造方法  hash值  key  value  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;
        }

        //重写了toString
        public final String toString() {
            return key + "=" + value;
        }

        //重写了hashCode
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        //传入值 实现了Entry接口
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            //如果是当前对象 true
            if (o == this) return true;

            //如果是Entry,
            if (o instanceof Map.Entry) {
                //强转为Entry
                Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;

                //key内容一致 并且value内容一致  返回true
                if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

        6、put方法

                 1、先根据key获取到hash值,如果key为null,返回0。

                 2、根据hash值获取到要存储到数组中的角标,如果该角标没有值,直接newNode存入这个角标。

                 3、如果这个角标有值,遍历这个角标上的链表,直到链表的next为null,将当前值放在该链表的最后一位。

                 4、如果数组容量到达扩容阈值,就扩容,如果链表长度大于8,数组长度大于64,就将该条链表转换为红黑树

    /**
     * 将值转换为hash的方法
     * 如果为null  返回0
     */
    static final int hash(Object key) {
        if(key == null) return 0;
        int h;
        return (h = key.hashCode()) ^ (h >>> 16);
    }


    /**    
     * 传入值
     */
    public V put(K key, V value) {
        //evict:如果遇到相同key  覆盖
        return putVal(hash(key), key, value, false, true);
    }

   /**
     * 传值
     *
     * @param hash
     * @param key
     * @param value
     * @param onlyIfAbsent true 如果重复  就不覆盖之前的值
     * @param evict        创建时传入false  其他情况传入false
     * @return 返回上一个值 没有返回null
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        Node<K, V>[] tab = table; //当前数组
        int tabLen = tab == null?0:tab.length; // 数组长度

        //如果没有数组
        if (tab  == null || tabLen == 0){
            //扩容一个数组
            tab = resize();
            //初始化数组的长度
            tabLen = tab.length;
        }

        int tabIndex = (tabLen - 1) & hash; //根据hash获取到应该放入的数组角标
        Node<K, V> first = tab[tabIndex]; //获取到这个链表的头节点
        if (first == null)
            //链表没有值 new一个
            tab[tabIndex] = newNode(hash, key, value, null);
        else {
            //链表已经有值了
            Node<K, V> next;
            K k = first.key;

            if (first.hash == hash && (k == key || (key != null && key.equals(k)))) {
                //查询到这个链表的第一个就是要存的这个值 就拿到这个值
                next = first;
            } else if (first instanceof TreeNode)
                //如果为红黑树  用红黑树方法取值
                next = ((TreeNode<K, V>) first).putTreeVal(this, tab, hash, key, value);
            else {
                //如果查询链表中有没有这个值
                int binCount = 0;
                //遍历链表  插入这个节点
                while (true){
                    //链表长度 用于判断是否转换为红黑树 默认为8
                    binCount ++;
                    next = first.next;

                    //如果发现最终没有找到这个key  直接new一个
                    if (next == null) {
                        first.next = newNode(hash, key, value, null);

                        //链表长度大于8 转为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash);
                        break;
                    }

                    //找到了该key 就不添加了 返回了当前这个next
                    if (next.hash == hash && ((k = next.key) == key || (key != null && key.equals(k))))  break;

                    //将当前node 继续遍历
                    first = next;
                }
            }
            //如果是找到了  直接将value值赋给找到的数组
            if (next != null) {
                V oldValue = next.value;

                //onlyIfAbsent 如果为false 覆盖之前的value  || 之前的值为null  也会覆盖
                if (!onlyIfAbsent || oldValue == null) next.value = value;

                afterNodeAccess(next);
                return oldValue;
            }
        }

        //操作了一次  为了防止并发 在查询时发现这个值有变动 则为其他线程调用了  就是并发了
        ++modCount;

        //数组长度大于扩容阈值   扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

         7、扩容方法

                   1、获取到老的数组的容量,然后乘2倍

                   2、初始化一个新的数组,将老数组的值重新赋值到新数组中去

    /**
     * 将数组的长度 * 2
     *
     * @return the table
     */
    final Node<K, V>[] resize() {
        Node<K, V>[] oldTab = table;//老数组
        int oldCap = (oldTab == null) ? 0 : oldTab.length; //老数组的最大容量
        int oldThr = threshold; //老的扩容阈值
        int newCap = 0; //新数组的长度
        int newThr = 0;  //新的扩容阈值

        if (oldCap > 0) {
            //老数组有值

            //如果老数组长度已经大于或者等于 数组最大长度 则不再扩容
            if (oldCap >= MAXIMUM_CAPACITY) {
                //扩容阈值 为 int 的最大值
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }

            //数组容量扩容一倍
            newCap = oldCap << 1;
            if(newCap < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY){
                //如果旧数组最大长度扩容一倍后小于最大值 并且 旧数组原最大长度大于16

                // 扩容阈值扩容一倍
                newThr = oldThr << 1;
            }

        } else if (oldThr > 0) {
            //初始容量设置为阈值
            newCap = oldThr;
        } else {
            // 零初始阈值表示使用默认值
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }

        //如果新的扩容阈值 等于0  计算出新的扩容阈值
        if (newThr == 0) {
            float ft = (float) newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE);
        }
        //扩容阈值赋给共享变量扩容阈值
        threshold = newThr;

        //扩容 new 一个新的数组
        @SuppressWarnings({"rawtypes", "unchecked"})
        Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
        table = newTab;
        if (oldTab == null) return newTab;

        //遍历老数组
        for (int j = 0; j < oldCap; ++j) {
            Node<K, V> e =  oldTab[j];
            if (e == null) continue;

            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 {
                //赋值链表
                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;
    }

        8、get方法

             1、获取到key的hash,找到这个hash在数组中的角标

             2、根据key跟数组头节点比较,如果是头节点直接返回,如果不是,遍历该链表,知道找到为止

    /**
     * 根据key获取value
     */
    public V get(Object key) {
        Node<K, V> e = getNode(hash(key), key);
        return e == null ? null : e.value;
    }    
   /**
     * 根据hash和key获取node
     * 先从数组第一个节点开始找  如果找不到  查找链表
     */
    final Node<K, V> getNode(int hash, Object key) {
        Node<K, V>[] tab = table; //当前的数组
        if(tab == null){
            return null;
        }

        int tabLen = tab.length; //当前数组的长度
        int tabIndex = (tabLen - 1) & hash; // 获取链表的角标
        Node<K, V> first = tab[tabIndex]; //获取到角标上的对象

        //如果链表为空 返回null
        if (tabLen <= 0 || first == null) { return null;};

        K k = first.key; //链表头的key

        //先判断 如果 链表头的key和传入的key相同 直接返回链表头
        if (first.hash == hash && (k == key || (key != null && key.equals(k))))  return first;

        Node<K, V> next = first.next;  //链表的下一个节点
        //没有下一个节点 直接返回
        if (next == null) return null;

        if (first instanceof TreeNode) return ((TreeNode<K, V>) first).getTreeNode(hash, key); //如果是一个红黑树 从红黑树里面找

        //遍历链表
        while (next != null){
            k = next.key; //更新key为链表的下一个链表节点的key

            //循环找到下一个节点  如果是就返回
            if (next.hash == hash && (k == key || (key != null && key.equals(k)))) return next;

            next = first.next;  // 链表的下一个节点
        }
        return null;
    }

        9、remove 删除

               1、根据key获取到node

               2、如果删除的key是头节点,直接将头节点赋值成该node的下一个

               3、如果不是头节点,找到该节点的上一个,赋值成该节点的下一个,只要不链接该节点就相当于删除

/**
     * 根据key删除
     */
    public V remove(Object key) {
        /**
         *  hash
         *  key
         *  value
         *  matchValue 是否必须匹配value
         *  movable 是否移动节点  树
         */
        Node<K, V> e = removeNode(hash(key), key, null, false, true);
        return e == null ? null : e.value;
    }

    /**
     * hash
     * key
     * value
     * matchValue 是否必须匹配value
     * movable 是否移动节点  树
     */
    final Node<K, V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) {
        Node<K, V>[] tab = table; //数组
        int n = tab == null?0:tab.length; // 数组长度
        int index = (n - 1) & hash; //数组角标
        Node<K, V> first = tab[index]; //链表头节点
        if (tab == null || n <= 0 || first == null) return null;

        Node<K, V> node = getNode(hash(key), key);

        if(node == null ) return null;
        V v = node.value;

        //需要匹配value
        if(matchValue){
            //没有匹配成功
            if(!(v  == value  || (value != null && value.equals(v)))){
                //value没有匹配成功
                return  null;
            }
        }

        if (node instanceof TreeNode) {
            //如果为树
            ((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
        } else if (node == first){
            //如果删除的是头节点  就将头节点改成之前头节点的下一个
            tab[index] = node.next;
        } else {
            //由于之前遍历链表的时候已经将first改成了要删除节点的上一个,所以将first的下一个节点直接改成要删除节点的下一个即可
            first.next = node.next;
        }

        ++modCount;
        --size;
        afterNodeRemoval(node);
        return node;
    }

猜你喜欢

转载自blog.csdn.net/qq_38384460/article/details/112839403
今日推荐