Hashmap

HashMap 源码解读

成员变量

/**
 * The default initial capacity - MUST be a power of two.
 * 默认初始容量,必须是2的幂
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
 * The maximum capacity, used if a higher value is implicitly specified
 * by either of the constructors with arguments.
 * MUST be a power of two <= 1<<30.
 * 最大容量
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

/**
 * The load factor used when none specified in constructor.
 * 默认的加载因子 0.75
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
 * The bin count threshold for using a tree rather than list for a
 * bin.  Bins are converted to trees when adding an element to a
 * bin with at least this many nodes. The value must be greater
 * than 2 and should be at least 8 to mesh with assumptions in
 * tree removal about conversion back to plain bins upon
 * shrinkage.
 * 当链表的值超过8则会转红黑树
 * 该值必须大于 2 且至少应为 8,以便与树移除中关于在收缩时转换回普通 bin 的假设相匹配。
 */
static final int TREEIFY_THRESHOLD = 8;

/**
 * The bin count threshold for untreeifying a (split) bin during a
 * resize operation. Should be less than TREEIFY_THRESHOLD, and at
 * most 6 to mesh with shrinkage detection under removal.
 * 当链表的值小于6 则会从红黑树转回链表
 */
static final int UNTREEIFY_THRESHOLD = 6;

/**
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
 * between resizing and treeification thresholds.
 * 当Map 里面的数量超过该值是,表中的桶才能进行树形化,否则桶内元素太多时会扩容,而不是树形化
 * 为了避免进行扩容 树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
 */
static final int MIN_TREEIFY_CAPACITY = 64;

/**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     * table用来初始化
     */
    transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     * 用来存放缓存
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * The number of key-value mappings contained in this map.
     * HashMap中存储的数量
     */
    transient int size;

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     * 用来记录HashMap的修改次数
     */
    transient int modCount;

    /**
     * The next size value at which to resize (capacity * load factor).
     * 用来调整大小下一个容量的值计算方式为(容量*负载因子)
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    int threshold;

    /**
     * The load factor for the hash table.
     * hash 表的 加载因子
     * @serial
     */
    final float loadFactor;

总结:

  • 常用的几个默认值
    • 默认容量16
    • 默认加载因子0.75
    • 链表转红黑树默认大小8
    • 红黑树转链表默认大小6
    • 链表转红黑树 桶大小为 64

构造方法

/**
 * Constructs an empty {@code HashMap} with the default initial capacity
 * (16) and the default load factor (0.75).
 * 创建一个默认容量为16 加载因子为0.75 的空构造方法
 *
 */
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

 /**
  * Constructs an empty {@code HashMap} with the specified initial
  * capacity and the default load factor (0.75).
  * 指定容量大小的构造方法,加载因子默认0.75
  * @param  initialCapacity the initial capacity.
  * @throws IllegalArgumentException if the initial capacity is negative.
  */
 public HashMap(int initialCapacity) {
     this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }

/**
 * Constructs an empty {@code HashMap} with the specified initial
 * capacity and load factor.
 *
 * @param  initialCapacity the initial capacity
 * @param  loadFactor      the load factor
 * @throws IllegalArgumentException if the initial capacity is negative
 *         or the load factor is nonpositive
 *  指定容量,指定加载因子的构造函数
 */
 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);
 }

put方法

public V put(K key, V value) {
   //第四个参数默认为 false 如果为true 则表示不改变map中已存在的value
    return putVal(hash(key), key, value, false, true);
}

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //tab 哈希数组,p hash桶首节点, n hashmap 长度, i计算处的数组下标
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //如果hash数组是空或者 长度为0
        if ((tab = table) == null || (n = tab.length) == 0)
            //获取长度并进行扩容
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null) // 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)))) //key值相等,是哈希桶上的节点
                e = p;
            else if (p instanceof TreeNode) // 树节点
                // 在红黑树中进行添加,如果该节点已经存在,则返回该节点
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // 是链表节点,遍历链表
                for (int binCount = 0; ; ++binCount) {
                    //找到尾部,表明添加的key-value没有重复,在尾部进行添加
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //判断 是否要转化为红黑树结构
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                          // 此处进行判断当前数组长度是否下于64,如果小于 则进行扩容,大于则转为红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 如果链表中有重复的key,e则为当前重复的节点,结束循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // e不为null 则表示有重复的key,则用待插入的值进行覆盖,返回旧值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 修改次数 +1 
        ++modCount;
        //实际长度 +1 ,怕断是否大大临界值,大于则扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        //添加成功
        return null;
    }

resize()方法

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    //旧数组长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //旧数组当前临界值
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        //判断就数组长度是否已经达到 最大容量 1 << 30
        if (oldCap >= MAXIMUM_CAPACITY) {
            //设置临界值为int最大值  2 ^ 31 -1
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // ##  新数组的长度即临界值均扩容为原来的二倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    //如果当前 oldCap = 0,但是已经初始化了,就像把元素删除之后,他的临界值还存在
    //如果是首次初始化那么 odlThr 临界值应该为0
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    //odlCap oldThr 均为0 的情况 表示首次初始化,设置为默认值
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 此处为 ## 处的补充,如果此时 新数组扩容后容量大于最大值或者原数组长度小于16 则为0 ,计算新数组临界值
    if (newThr == 0) {
        // new数组的临界值
        float ft = (float)newCap * loadFactor;
        //判断是否new数组容量大雨最大值,临界值是否大雨最大值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //此处为真正的改变,也就是容量和临界值都改变了
    threshold = newThr;
    //忽略该告警
    @SuppressWarnings({"rawtypes","unchecked"})
            //初始化
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //对table 进行赋值为当前new 数组
    table = newTab;
    //如果oldtab不为空,将元素遍历到newtab中
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            //临时变量
            Node<K,V> e;
            //当前哈希桶位置有值,表示可能会发生冲突
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                // 如果下标处的下一个节点为null,表示没有链表
                if (e.next == null)
                    //对元素进行hash运算,存入到新的数组中
                    newTab[e.hash & (newCap - 1)] = e;
                //存在hash冲突,该节点为红黑树结构
                else if (e instanceof TreeNode)
                    //将树进行转移到newCap中
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    //链表结构,对链表进行便利后,把值转移到扩容后hashmap中
                    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;
                    }
                }
            }
        }
    }
    //返回扩容后hashMap
    return newTab;
}

get() 方法

public V get(Object key) {
    Node<K,V> e;
   //调用getNode方法完成获取
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
    //first 头节点,e 临时变量,n 长度 k key
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    //判断table是不是空, first 节点是不是空
    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))))
            // 检查第一个节点 如果 hash一致,key 相等 则返回第一个节点
            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() 方法

    public V remove(Object key) {
        Node<K,V> e;
      //调用removeNode方法 完成移除
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    // 数组下标节点 p,n 长度, index 当前数组下标
    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 要删除节点 e 临时变量 k key  v value
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // hash 一致,并且 key也一致,将要删除节点赋值给node
            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);
            }
        }
        // 找到要删除的节点,判断mathValue
        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;
            //此方法在hashMap中是为了让子类去实现,主要是对删除结点后的链表关系进行处理
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

红黑树

介绍

红黑树是一种自平衡的二叉查找树,自平衡可以对较长的链表做出优化减少树的深度

特点

  • 节点颜色要么是红色要么是黑色
  • 根节点为黑色
  • 每个叶节点(NIl节点,空节点)黑色
  • 每个红色节点的两个字节点都是黑色(每个叶子节点到根节点的所有路径上不能为两个连续的红色节点)
  • 从任一个节点到其每个叶子节点的所有路径都包含相同数目的黑色节点

时间复杂度

如果桶中没有元素则是O(1) (通过hashcode直接找到位置)

如果有元素,且当前长度小于8 非 红黑树节点则为O(n)

如果为红黑树节点为 O(logn)

线程安全

hashmap为非线程安全的集合

如何实现线程安全

方法1 :使用hashtable

方法2:使用concurrentHashmap

方法3:使用使用java.util.collections.synchronizedMap()方法包装 HashMap object,得到线程安全的Map,并在此Map上进行操作。

优缺点

1、过度依赖hash算法,如果key是自定义类,需要自己重写hashcode方法

2、hashmap存储元素的位置除了和hash值有关,还和数组长度有关,如果数组太小发生hash 碰撞,如果频繁发生扩容,就必须所有元素需要重新计算其index 位置,在使用时尽量先确定hashmap大小,防止频繁扩容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值