Java集合解析:HashMap

HashMap是Java常用的数据结构之一,在面试中也经常被问到。

HashMap数据结构
JDK1.8之后:数组+链表+红黑树
在这里插入图片描述

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;
        }
}

几个重要问题

  • HashMap不是线程安全的,没有synchronize
  • 影响hashmap的两个因素:initial capacity(初始容量) and load factor(负载因子)
    • 初始容量:初始化时数组的长度。默热值16,自定义数值也需要是2的整数次幂(为了加快哈希计算以及减少哈希冲突)
    • 负载因子:在扩容之前允许达到的最满值。默认值是0.75(平衡了时间和空间)
  • 几个重要参数
/**
 * The default initial capacity - MUST be a power of two.
 */
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.
 */
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.
 */
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.
 */
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.
 */
static final int MIN_TREEIFY_CAPACITY = 64;

HashMap源码解析

  • hash计算和冲突解决
static final int hash(Object key) {
        int h;
        //高位和低位都参与运算,为了更好的散列化
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  • putValue
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
	...
	//之前该位置没有值,直接放入
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
    //如果key一致,则更新value
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
            //是红黑树的话,插入树中
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
        //不是红黑树,放入链表,如果链表长度大雨等于8,则将链表转化为红黑树
            for (int binCount = 0; ; ++binCount) {
                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;
            }
        }
        //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;
        }
    }
    //说明之前没有该key,插入,返回null
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
  • getVaule
final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> 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<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;
    }
  • resize机制
    • 当个数达到 >= 初始容量*负载因子, 发生扩容
    • 长度变为原来的两倍,将old hashmap中的值复制到新的map中
    • 重新hash,old元素重新hash的位置要不然还是原值,要不然移动2次幂 。
  • Hashmap为什么是不安全的?
    • 在jdk1.7中,链表使用头插法,在多线程环境下,扩容时会造成环形链或数据丢失。
    • 在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。

常见面试题

  1. HashMap的底层数据结构?
  2. HashMap的存取原理?
  3. Java7和Java8的区别?
  4. 为啥会线程不安全?
  5. 有什么线程安全的类代替么?
  6. 默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
  7. HashMap的扩容方式?负载因子是多少?为什是这么多?
  8. HashMap的主要参数都有哪些?
  9. HashMap是怎么处理hash碰撞的?
  10. hash的计算规则?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值