Java Map集合(二)

1. HashMap原理

1.1 HashMap的容量

        HashMap中使用数组作为存储元素的桶,对应的内部属性为table,如下图所示。HashMap的内部数组不是在创建HashMap对象时初始化,而是在首次存入元素时进行初始化,以减少对内存的占用。

        从源码注释中我们可以发现,官方规定table的长度总是2的幂,即2的N次方。这样设计的原因是为了保证HashMap的速度足够快。

        我们知道,不论存操作还是取操作,HashMap都使用除留取余法通过key的哈希值计算key在桶中的索引。如果能优化这一计算的速度,将会大幅优化HashMap存取操作的速度。

        在数学中有这样一条公式:X % 2^n = X & (2^n - 1) 。简单的说,当X对Y取余时,如果Y是2的幂,则可以将取余运算转换为位运算,以提高计算速度。

        综上,HashMap的容量始终是2的幂,以保证内部高效的哈希运算。

1.2 HashMap的初始容量

        与ArrayList相似,HashMap的初始容量分为默认容量和手动指定两种情况。

        当使用无参构造器创建HashMap对象时,table的初始化长度为16,由如下的静态常量指定。

        因此,默认情况下,HashMap的默认容量为16。

        HashMap支持使用带参构造器HashMap(int initialCapacity)来创建HashMap对象,并指定内部数组的初始化长度。

        此处需要注意,HashMap并不会直接使用开发者指定的长度作为内部数组的长度,而是会通过一个内部方法,计算大于开发者指定长度的最小的2的幂作为内部数组的长度。

        例如:

        因此,当开发者指定初始长度时,HashMap的容量为大于该长度的最小的2的幂。

1.3 HashMap的扩容

        HashMap中提供了桶的自动扩容机制,在满足特定的条件时自动将桶的长度扩容到原来的两倍。

        想要理解桶的扩容条件,需要先分清楚4个概念:

  • 1、容量(capacity):HashSet内部数组的长度,默认长度为16
  • 2、大小(size):HashSet中实际存储的元素的个数,默认为0
  • 3、负载因子(loadFactor):用来衡量HashSet“满”的程度,默认值为0.75f
  • 4、临界值(threshold):当size超过临界值时,HashSet将会扩容,threshold = capacity * loadFactor

        对于一个默认的HashSet来说,临界值=16 * 0.75 = 12,即当存储了超过12个元素时,HashSet会自动扩容,将容量扩大到原来的2倍,即32。

        扩容后,还会对所有的元素进行一次rehash操作,相当于对所有的元素重新做一遍Hash运算,是一项比较耗时的操作。

        由于存在上述的设计,因此开发者手动指定HashMap的初始容量时,需要计算合适的容量,而不是直接传入要存储元素的个数。具体可参考《阿里巴巴 Java开发手册》中的建议。

1.4 树化与退化

        HashMap中使用链地址法处理Hash冲突,当桶中某个位置的链表过长时,会响查询效率的情况。

        自Java 8开始,HashMap在解决哈希冲突时引入了红黑树的应用,以提高查找操作的效率。当链表长度大于等于阈值(默认为8),同时HashMap容量已达到64时,链表会转换为红黑树,从而减少查找操作的时间复杂度,这个过程称为树化(Treeify)。

        树化的2个阈值由HashMap内部的静态常量指定。

        红黑树是一种自平衡的二叉搜索树,它具有良好的平衡性能和较快的查找、插入和删除操作的时间复杂度。相比于链表,红黑树在大型哈希桶中可以提供更快的查找速度。

        同时,当红黑树中的节点数量减少到一定程度(默认为6)时,HashMap会将红黑树转换回链表。这个过程称为退化(Untreeify)。退化的阈值同样由HashMap内部的静态常量指定:

2. LinkedHashMap

2.1 LinkedHashMap概述

        HashMap虽然提供了高效的添加和查询功能,但是无法保存元素的添加顺序。在一些特定的应用场景中,可能需要保存键值对的添加顺序,此时可以使用LinkedHashMap来实现。

        例如:项目既需要提供按用户名快速查询用户信息的功能,也需要提供按用户签到顺序显示用户信息列表的功能。此时需要能够记载元素的添加顺序。

        LinkedHashMap是在HashMap的基础上维护了一个Entry的双向链表,以此记录元素之间的先后顺序。

2.2 LinkedHashMap示例

        编写代码,测试LinkedHashMap的遍历。代码示意如下:

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        LinkedHashMap<Integer,String> map = new LinkedHashMap<>();
        // 存放元素,以键值对形式
        map.put(5, "Tom");
        map.put(3, "Jerry");
        map.put(9, "Lucy");
        // 通过entrySet方法遍历
        Set<Map.Entry<Integer,String >> entrySet = map.entrySet();
        for(Map.Entry<Integer,String> entry:entrySet){
            System.out.println("key: " + entry.getKey()+" value: " + entry.getValue());
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangyan_1010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值