Java集合——HashMap

将总结的内容记录下来,方便他人,也能防止自己遗忘!脑容量不够的时候,真是捉急啊!

一、概述

1. 什么时候会使用HashMap?有什么特点呢?

2. HashMap的工作原理

3. get和put的原理是怎么的?equals()和hashCode()都有什么作用?

4. hash的实现,为什么要这样实现呢?

5. 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

执行下面的操作时:

HashMap<String,Integer> map = new HashMap<String,Integer>();
map.put("语文",1);
map.put("数学",2);
map.put("英语", 3);
map.put("历史", 4);
map.put("政治", 5);
map.put("地理", 6);
map.put("生物", 7);
map.put("化学", 8);
for(Entry<String,Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

输出的结果为:

政治: 5
生物: 7
历史: 4
数学: 2
化学: 8
语文: 1
英语: 3
地理: 6

下面是大致的结构,对HashMap有一个初步的认识:

 在官方文档中有几个关键的信息:基于Map接口实现、允许null键/值、非同步、不保证有序(比如插入的顺序)、也不保障不随时间变化。

二、两个重要的参数

在HashMap中有两个重要的参数,容量(Capacity)和负载因子(Load factor)

Capacity就是bucket的大小,Load factor就是bucket填满程度的最大比例。如果对迭代性能要求很高的话,不要把capacity设置的过大,也不要把load factor设置过小。当bucket中的entries的数目大于capacity*load factor时候就需要调整bucket的大小为当前的2倍。

三、put函数的实现

put函数大致的思路:

1.首先调用Key的hash方法,计算出哈希码,通过哈希码快速找到莫格存放位置(桶),这个位置可以被称为bucketIndex

2. 如果发生了碰撞(可能多个元素找到了相同的bucketIndex),这时会取到bucketIndex位置已经存储的元素,最终通过equals来比较,equals方法就是再碰撞的情况下才会执行的方法,并以链表的方式存在buckets里。

3. 如果没有发生碰撞直接放入bucket里

4. 如果碰撞导致链表过长(大于等于TREEIEY_THRESHOLD),将链表转换成红黑树;

5. 如果节点已经存在就替换old value(保证key的唯一性)

6. 如果bucket满了(超过load factor*current capacity),需要resize。

四、get函数的实现

get函数的大致思路为:

1. 如果是bucket中的第一个节点,直接读取

2. 如果有冲突,通过key.equals(k)去查找对应的entry

若为树,则在树中通过key.equals(k)查找,O(logn);

若为链表,则在链表中通过key.equals(k)查找,O(n).

详情可见代码:

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
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) {
            // 在树中get
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            // 在链表中get
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

五、hash函数的实现

在get和put的过程中,计算下标时,先对hashCode进行hash操作,然后再通过hsah值进一步计算下标,详细如下:

 在对hashCode()计算hash时具体实现是这样的: 

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

 这个函数大概的意思就是:高16bit不变,低16bit和高16bit做了一个异或。

在之前说过的,在获取HashMap的元素时,基本上分为两部:

         1. 首先根据hashCode(),然后确定bucket的index;

         2. 如果bucket的节点的key不是我们需要的话,通过keys.equals()在链中找。

在Java8之前的实现时用链表解决冲突的,在产生碰撞的情况下,进行get的时候,这两部的事件复杂度时O(1)+O(n)。因此,在碰撞比较严重的时候n会很大,O(n)的速度显然时影响速度的。

在Java8中,利用红黑树替换链表,这样复杂度就变成了O(1)+O(logn)了,这样在n很大的时候,对速度又较大的提升。

六、 RESIZE的实现

当put时,如果发现目前的bucket占用程度已经超过了Load Factor所希望的比例,那么就会发生resize。在resize的过程中,将bucket扩充2倍,之后重新计算index,将节点放入新的bucket中。又因为我们用的是2次幂的扩展(指长度扩为原来2倍),元素的位置要么在原来的位置,要么在原来的位置移动到2次幂的位置。

七、 总结

根据开头咱们说的几点来进行下简单的总结:

1. 什么时候会使用HashMap?有社么特点呢?

     基于Map接口的实现,存储键值对时,它可以接收null的键值,是非同步的,HashMap存储着Entry(hash,key,value,next)对象。

2. 你知道HashMap的工作原理吗?

通过hash的方法,通过put和get存储和获取对象。存储对象时,将key/value传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Factor,则resize为原来的2倍)获取对象时,我们将K传给get,他会调用hashCode计算hash从而得到bucket位置,并进一步调用equals方法确定键值对。如果发生碰撞时,HashMap通过链表将产生碰撞冲突的元素组织起来,在Java8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

3. get和put的原理是怎样的?equals和hashCode有社么作用?

通过对key的hashCode()进行hashing,并计算下标((n-1)&hash),从而获得bucket的位置。如果产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点

4. hash实现是怎么样的呢?为啥要这么实现?

在Java1.8的实现中,通过hashCode()的高16位异或低16位实现的:(h=k.hashCode())^(h>>>16),主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。

5. 如果HashMap的大小超过负载因子(load factor)定义的容量,怎么办?

如果超过了负载因子(默认0.75),则会重新resize一个原来长度两倍的HashMap,并能重新调用hash方法。

Java中的HashMap是一种常用的集合类,用于存储键值对。它继承自AbstractMap类,并且实现了Map接口。与HashTable相比,HashMap具有更好的性能和扩展性。[1] HashMap的底层原理是使用哈希表来实现的,它通过将键映射到存储桶中来存储和获取值。HashMap使用键的hashCode()方法来计算哈希码,然后根据哈希码将键值对存储在相应的存储桶中。在存储和获取值时,HashMap会根据键的哈希码进行快速查找,从而实现了O(1)的平均时间复杂度。 在HashMap中,键和值都允许为null,但是只能有一个为null的键。如果多个键映射到同一个存储桶,HashMap会使用链表或红黑树来解决冲突。当链表长度超过一定阈值时,链表会被转换为红黑树,以提高查找效率。 除了HashMap,还有其他一些相关的类可以用来实现线程安全的HashMap,例如Collections.synchronizedMap()方法可以将HashMap转换为同步的Map。这样可以在多线程环境中安全地使用HashMap。不过,由于性能的原因,这种方式不常用。 总结来说,JavaHashMap是一种常用的集合类,使用哈希表来存储键值对。它具有良好的性能和扩展性,允许键和值为null。在多线程环境中可以使用同步的Map来实现线程安全。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Java集合 —— HashMap原理解析](https://blog.csdn.net/m0_56602092/article/details/130338081)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [【javaHashMap底层实现原理及面试题](https://blog.csdn.net/twotwo22222/article/details/128426417)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值