HashMap踩过的坑!


总结:HashMap和Hashtable的底层实现: 数组+链表结构;Put碰撞时覆盖与否;

原因:做出一种寻址容易,插入删除也容易的数据结构

使用hahmap对数组中的元素计数:--->map.keyset.contains
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < arr.length; i++) {
            if (map.keySet().contains(arr[i])) {
                map.put(arr[i], map.get(arr[i]) + 1);
            } else {
                map.put(arr[i], 1);
            }
        } 

HashMap

分析源码:
HashMap 存储 key value
底层的数据结构:数组链表

HashMap源码

1.HashMap底层数据结构如何?

2.HashMap如何保存数据,增删改咋实现?

3.HashMap如何扩容?

4.为什么扩容的大小一定要是2的整数次幂,也就是2的N次方.

截取部分源码:

1.
继承自AbstractMap 并实现了接口
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

2.
0001 左移40001 0000 = 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

static final int MAXIMUM_CAPACITY = 1 << 30;

3.

来说说HashMap的实现

HashMap基于==哈希表==,底层结构由数组来实现,添加到集合中的元素以“key—value”形式保存到数组中,在数组中key—value被包装成一个实体来处理—-也就是上面Map接口中的Entry

哈希冲突:通过链表的形式来解决

HashMap继承于AbstractMap,实现了Map, Cloneable, Serializable接口。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
特点:
  • 允许存入null键,null值(null值只有一个,并存于数组第一个位置)
  • ==无序集合==,而且顺序会随着元素的添加而随时改变(添加顺序,迭代顺序不一致)
  • 随着元素的增加而动态扩容(与ArrayList原理一致)
  • 不存在重复元素(得益于hashCode算法和equals方法)
  • 线程不安全

HashMap的最终实现:
在HashMap中,存储元素的是==Entry[]数组==,而其中的元素也就是Entry对象:

HashMap的hash方法原理

参考文章:
https://www.zhihu.com/question/20733617

Put

  1. 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。
  2. 一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;
  3. 这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。
    • 也就是说数组中存储的是最后插入的元素。
Put过程的覆盖问题

良心贴:https://www.cnblogs.com/yangqiangyu/p/5276629.html

Put

graph LR
key生成的hashcode产生的index-->之前的index位置已经有Entry则碰撞
之前的index位置已经有Entry则碰撞-->所以遍历Entry如果key的value和hashcode都相同就直接替换
之前的index位置已经有Entry则碰撞-->key的value和hashcode不相同没有重复的话就addEntry
key生成的hashcode产生的index-->之前的index位置没有Entry则addEntry
碰撞时覆盖!

碰撞时不覆盖!

碰撞时的替换源码

主要是这行代码:if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }

容量(Capacity)和负载因子(Load factor)

Capacity就是buckets的数目

当bucket填充的数目(即hashmap中元素的个数)大于capacity*load factor时就需要调整buckets的数目为当前的2倍。

Load factor就是buckets填满程度的最大比例

加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.链表长度会越来越长,查找效率降低。
加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)冲突的机会越大,则查找的成本越高。
- 默认值0.75

哈希冲突处理

graph LR
哈希冲突处理-->开放定址法
开放定址法-->线性探测再散列
开放定址法-->平方探测再散列
哈希冲突处理-->链地址法
哈希冲突处理-->再哈希
哈希冲突处理-->建立公共溢出区

什么是hash

  1. 任意长度的二进制通过映射转成一个固定长度的二进制值
  2. key - value 唯一映射关系

hash表

事件复杂度O(1).

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值