HashMap复习笔记

HashMap了解吗?

  • HashMap底层数据结构:数组+链表+红黑树

  • 允许put null值,HashMap在调用hash算法时,如果key为null,那么hash值为0,这一点和HashTable和ConcurrentHashMap有所不同。

    • (key == null) ? 0:(h=key.hashCode()) ^ (h >>> 16);
  • loadFactor:负载因子,默认为0.75,时均衡了时间和空间损耗计算出来的,较高的值会减少空间的开销,扩容减少,数组大小增加速度变慢,但是查找成本增加,hash冲突增加,链表变长。

  • 如果有很多需要存储在HashMap中的数据,要在一开始把它的容量设置为足够大,防止出现段的扩容。

  • 可以通过Collections.synchronizedMap()来实现线程安全或者使用ConcurrentHashmap

  • put的过程

    • 首先会判断数组有没有进行初始化,没有的话,先执行初始化操作,resize()方法
    • (n-1) & hash用来定位到数组中具体的位置,如果数组中的位置为空,直接在该位置添加值
    • 如果数组中该位值有值的话,会先判断该位置的key和hash值是否和要插入的key和hash值相等,若相等则直接覆盖。
    • 如果要插入位置是链表,采用的是尾插法插入数据,并且当链表的长度大于等于8时,并且数组长度大于64,则会将链表转换为红黑树,否则,就将数组进行扩容。
    • 如果插入位置是树节点,则进行红黑树的插入操作。
    • 插入完成后,会判断size是否大于threshold,是否需要扩容,若扩容的话,数组大小为之前的2倍,扩容完成后,将原数组上的节点移动到新数组上。
  • 为什么树化操作的阈值为8

    • 链表的查询时间复杂度为O(n),红黑树的查询时间复杂度为O(logn),在数据量不多的时候,使用链表比较快,只有当数据量比较大的时候,才会转换为红黑树,但是红黑树占用的空间大小是链表的2倍,考虑到时间和空间上的损耗,所以要设置边界值;链表的长度为8的概率很低,在HashMap注释中写了,出现的概率不足千分之一,红黑树只是为了在极端情况下来保证性能。
  • 哈希冲突:如果两个不同的对象经过hash运算后得到的值相同,就会发生碰撞,这种现象就被称为hash冲突

    • 有以下方式可以解决哈希冲突:
      • 开放定址法
      • 再哈希法
      • 链地址法
      • 建立公共溢出区
  • hash算法

    • (key == null) ? 0:(h=key.hashCode()) ^ (h >>> 16); 该方法只是得到要存入对象的hash值,具体获取要存入的数组的下标的计算方法是“(length-1)& hash”。
    • 在这里插入图片描述
  • 为什么数组大小始终为2的n次幂?

    • 因为在确定某个值在数组位置的下标时,采用的是(数组大小-1)位与上hash值,而数组大小减一后,用2机制表示最后几位都是1,这样每位在位与运算之后,不是0就是1,如果我们hash值是均匀分布的话,那么得到的数组下标也是均匀分布的,而如果数组的容量不是2的n次幂,那就没有这个特性了。
    • 为了能让HashMap存取高效,尽量减少碰撞,尽量把数据分配均匀。
    • 比如数组长度为16时,length-1的二进制一定是“…00001111…”这种形式,即后面几位全部都是1,那么做与运算时,只会对后面的四位进行运算,肯定会落在0-15的范围内。
    • 取余(%)的操作中如果除数时2的幂次方则等价于被除数和除数减一的与(&)操作,也就是说(hash% length == hash & ( length -1))前提时length是2的幂次方。并且采用二进制位操作,可以提高运算效率。
  • 为什么数组的大小默认是16?

    • 16是一个经验值,2,4,8太小了,会频繁扩容,32有些大了,会多占用空间。
  • 为什么JDK1.8采用了尾插法?

    • JDK1.7时采用头插法,在扩容后rehash,会使得链表的顺序颠倒,引用关系发生改变,那么在多线程的情况下,会出现链表成环进而出现死循环的问题,而尾插法又不会出现这种问题,rehash后链表的顺序不变,引用关系也不会发生改变。
  • HashMap和HashTable的区别?

    • 实现方式不同:HashTable继承了Dictionary类,而HashMap继承的是AbstractMap类
    • 初始容量不同:HashMap的初始容量为16,HashTable为11,负载因子都为0.75
    • 扩容机制不同:HashMap每次扩容为原来的2倍,HashTable每次扩容为原来的2倍+1。
    • 底层数据结构不同:HashMap底层待用数组+链表+红黑树的数据结构,而HashTable底层是数组+链表的数据结构。
    • 线程是否安全:HashMap是线程不安全的,HashTable是线程安全的,因为Hash Table内部的方法都有synchronized关键字修饰,因此HashMap的效率要比HashTable高一些。为了保证线程安全,可以使用ConcurrentHashMap。
    • HashMap可以存储null的key和null的value,但是null为键只能有一个。HashTable不允许存储null键和null值,否则会抛出异常。
    • HashMap提供了可供应用迭代的键的集合,因此HashMap是快速失败的。
  • HashMap和TreeMap的区别

    • HashMap
      • 数组方式存储key/value,线程不安全,允许null作为key和value,key不可以重复,value允许重复,不保证元素迭代顺序,每次扩容会重新计算key的hash值,会消耗资源,key必须重写equals和hashCode方法。
    • TreeMap
      • 基于红黑树的NavigableMap实现,线程不安全,不允许null,key不可重复,value允许重复,存入TreeMap的元素应当实现Comparable接口,或者实现Comparator接口,默认按照key排序后的顺序迭代元素。
      • 用于对存入元素的时候进行自动排序,可以进行自定义排序。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值