Java集合之HashMap分析和使用

一、概览

HashMap是最常用的集合之一,基于哈希表实现,用于存储键值对,从key映射到value,实现了Map接口
在这里插入图片描述
HashMap的几个特点:

  • 允许 null key与null value ,也就是键值对均可为空
  • key 不能重复
  • HashMap是无序集合,无法保证元素的特定顺序
  • HashMap不是线程安全的
  • HashMap的get与put方法使用了hashCode() 和 equals() 方法,所以若将简单java类作为key最好在该类中对这两个方法进行重写。这就是为什么不可变类更适合作为键的原因,如String类和Integer类

二、构造方法

打开HashMap的源码,其一共提供了4个构造方法:

  • 默认的构造方法,此方法用的较多,默认初始容量为16,load factor(加载因子)为0.75,关于加载因子是什么后面会说,请继续往下看
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
  • 有一个参数为initialCapacity的构造方法,此参数指定了容量大小(load factor仍为0.75),推荐使用此方法
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
  • 有两个参数的构造方法initialCapacity与loadFactor,由用户自己指定初始容量与加载因子。但此方法慎用,因为默认的加载因子0.75在时间与空间上的开销取得了较好的平衡
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);
    }
  • 下面这种构造方法包含了另一个map,其加载因子仍为0.75
public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

三、数据结构分析

其底层数据结构在JDK1.8时做了一次变动,下面分别讲讲这两个版本的数据结构
JDK1.8之前
在JDK1.8之前,HashMap的底层数据结构是基于“数组+链表”的形式实现的。这里先放一张图再根据图来说明:
在这里插入图片描述
从图中我们可以看出这是一个数组链表,数组中每一个格子都是一个链表;链表中每一个节点Node都有四个元素:

Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

要存储的元素先将其key进行hash操作,再计算出要存储在数组table[]中的位置,若该位置链表上没有元素,则将其放在链表首位置;若该位置有元素发生冲突则直接将冲突的元素加入到链表后面。

JDK1.8
此版本在进行hash冲突时进行了相对大的变化,在上表中链表长度若大于threshold阈值(默认为8)则会将该链表改为红黑树。这样做的原因主要是为了节省检索时间:假设其中某一个链表长度足够长,我要取得该链表的最后一个 元素,则要从前到后整个遍历该链表,其时间复杂度为O(n),开销很大。
在这里插入图片描述
在Java8中,Node基本相同,但是只能使用在链表的情形下,对于图中的红黑树使用的是TreeNode。

四、loadFactor加载因子

加载因子是一种决定何时增加HashMap容量以保持O(1)的get()和put()操作复杂度的度量。 HashMap的默认加载因子为0.75f。

    /**
     * The load factor used when none specified in constructor.
     */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

上面我们提到HashMap的默认初始容量为16,当我们不断往map里面添加元素并且数量达到了16*0.75=12时,就需要将当前的容量进行扩容,而扩容的操作涉及到Rehashing操作,将重新计算已经存储元素的hashCode并将这些元素复制到一个更大容量的HashMap中去,此操作开销较大;
load factor 默认为0.75f是官方给出的一个较好的临界值,最好不要随意改动

ps: 我们在使用HashMap时尽量先初始化其容量大小以避免多次进行扩容操作,降低性能

五、HashMap常用方法测试

public class HashMapTest {

    public static void main(String[] args) {
        Map<String, String> userCityMapping = new HashMap<>();

        // 检查是否为空
        System.out.println("is userCityMapping empty? : " + userCityMapping.isEmpty());

        userCityMapping.put("John", "纽约");
        userCityMapping.put("Lily", "北京");
        userCityMapping.put("Steve", "伦敦");

        System.out.println("userCityMapping HashMap : " + userCityMapping);

        // 查看数量
        System.out.println("We have the city information of " + userCityMapping.size() + " users");

        String userName = "Steve";
        // 检查是否存在以Steve为key的数据
        if(userCityMapping.containsKey(userName)) {
            // 通过key获得value
            String city = userCityMapping.get(userName);
            System.out.println(userName + " lives in " + city);
        } else {
            System.out.println("City details not found for user " + userName);
        }

        // 检查是否包含value为纽约
        if(userCityMapping.containsValue("纽约")) {
            System.out.println("There is a user in the userCityMapping who lives in New York");
        } else {
            System.out.println("There is no user in the userCityMapping who lives in New York");
        }

        // key重复直接覆盖
        userCityMapping.put(userName, "加利福尼亚");
        System.out.println(userName + " 搬到了新城市 " + userCityMapping.get(userName) + ", New userCityMapping : " + userCityMapping);

        // The get() method returns `null` if the specified key was not found in the HashMap
        System.out.println("Lisa's city : " + userCityMapping.get("Lisa"));
    }
}

参考:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值