HashMap小结

目录

1.简述JAVA的HashMap

2.HashMap的底层结构和扩容机制

3.HashMap和Hashtable的区别

4.如何决定使用HashMap还是TreeMap?

5.HashMap和ConcurrentHashMap区别


1.简述JAVAHashMap

JDK8 之前底层实现是数组 + 链表, JDK8 改为数组 + 链表 / 红黑树。主要成员变量包括存储数据的
table 数组、元素数量 size 、加载因子 loadFactor
HashMap 中数据以键值对的形式存在,键对应的 hash 值用来计算数组下标,如果两个元素 key
hash 值一样,就会发生哈希冲突,被放到同一个链表上。
table 数组记录 HashMap 的数据,每个下标对应一条链表,所有哈希冲突的数据都会被存放到同一条链表,Node/Entry 节点包含四个成员变量: key value next 指针和 hash 值。在 JDK8 后链表超过 8 会转 化为红黑树。
若当前数据 / 总数据容量 > 负载因子, Hashmap 将执行扩容操作。
默认初始化容量为 16 ,扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75

2.HashMap的底层结构和扩容机制

1)底层结构:
HashMap 内部使用数组存储数据,数组中的每个元素类型为 Node<K,V>

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

        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;
        }
    }
Node 包含了四个字段: hash key value next ,其中 next 表示链表的下一个节点。
2)扩容机制:
1.执行 put() , 该方法会执行 hash(key) 得到 key 对应的 hash 值 算法 
h = key.hashCode()) ^ (h >>> 16)

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
2.执行putVal
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //table 就是 HashMap 的一个数组,类型是 Node[] 
        //if 语句表示如果当前 table 是 null, 或者 大小=0 
        //就是第一次扩容,到 16 个空间.
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //(1)根据 key,得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置 
        //并把这个位置的对象,赋给 p 
        //(2)判断 p 是否为 null 
        //(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个                    Node(key="java",value=PRESENT) 
        //(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
        //(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象 
        //(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同 
        //就不能加入
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
        //再判断 p 是不是一颗红黑树, 
        //如果是一颗红黑树,就调用 putTreeVal , 来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
        //如果 table 对应索引位置,已经是一个链表, 就使用 for 循环比较 
        //(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后 
        // 注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点 
        // , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树) 
        // 注意,在转成红黑树时,要进行判断, 判断条件 
        // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64)) 
        // resize(); 
        // 如果上面条件成立,先 table 扩容. 
        // 只有上面条件不成立时,才进行转成红黑树 
        //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
                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;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //size 就是我们每加入一个结点 Node(k,v,h,next), size++
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
put 操作过程总结:
1. 判断 HashMap 数组是否为空,是的话初始化数组(由此可见,在创建 HashMap 对象的时候并不会
直接初始化数组);
2. 通过 (n - 1) & hash 计算 key 在数组中的存放索引;
3. 目标索引位置为空的话,直接创建 Node 存储;
4. 目标索引位置不为空的话,分下面三种情况:
4.1. key 相同,覆盖旧值;
4.2. 该节点类型是红黑树的话,执行红黑树插入操作;
4.3. 该节点类型是链表的话,遍历到最后一个元素尾插入,如果期间有遇到 key 相同的,则直接覆
盖。如果链表长度大于等于 TREEIFY_THRESHOLD ,并且数组容量大于等于
MIN_TREEIFY_CAPACITY ,则将链表转换为红黑树结构;
5. 判断 HashMap 元素个数是否大于等于 threshold ,是的话,进行扩容操作。

3.HashMap和Hashtable的区别

1. HashMap Hashtable 的轻量级实现, HashMap 允许 key value null ,但最多允许一条记录的 key
null. HashTable 不允许。
2. HashTable 中的方法是线程安全的,而 HashMap 不是。在多线程访问 HashMap 需要提供额外的同步
机制。
3. Hashtable 使用 Enumeration 进行遍历, HashMap 使用 Iterator 进行遍历。

4.如何决定使用HashMap还是TreeMap?

如果对 Map 进行插入、删除或定位一个元素的操作更频繁, HashMap 是更好的选择。如果需要对 key
合进行有序的遍历, TreeMap 是更好的选择。

5.HashMap和ConcurrentHashMap区别

HashMap虽然效率高,但是线程不安全的,所有我们在开发中,为了解决线程安全使用ConcurrentHashMap。它运用局部加锁的方式,提高并发下的完成效率。(Hashtable是全员加上synchronized方法进行加锁,效率慢,不推荐使用。)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值