HashMap

267 阅读4分钟

1. 为什么需要哈希表, 哈希表是什么?Hash函数是什么?

  • 数组和列表想要查询某个元素是否存在, 或者获取某个元素的位置效率低下
  • 散列表(hash table)把key,value映射到表中的某个位置来访问记录,以加快查找速度
  • hash函数是散列算法, 一种消息摘要

2. 解决hash碰撞的方法

int值是有限的,只有2^32个, 所以通过hash()计算的值必然会有重复, 此时会产生冲突,碰撞, 二维数组法会浪费大量的内存, 所以采用拉链法, jdk8后采用红黑树进一步优化
减少碰撞:

  • 将key的哈希值与高16位换到低16位做异或运算;
  • HashMap默认初始化长度为16,并且每次自动扩展或者是手动初始化容量时,必须是2的幂。
  • 扰动函数
  • 使用string或integer作为key(final修饰,并且重写了equal()hashCode()方法)

3. hashMap的特点

  • 实现快速存取, key,value允许为空,key相同和覆盖,
  • 底层是hash表, 不保证有序
  • hashmap线程不安全

4. hashap底层实现

put()

1.计算关于key的hashcode值(与Key.hashCode的高16位做异或运算)
2.如果散列表为空时,调用resize()初始化散列表
3.如果没有发生碰撞,直接添加元素到散列表中去
4.如果发生了碰撞(hashCode值相同),进行三种判断
4.1:若key地址相同或者equals后内容相同,则替换旧值
4.2:如果是红黑树结构,就调用树的插入方法
4.3:链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。
5.如果桶满了大于阀值,则resize进行扩容

resize()

当数组table的size达到阙值时即++size > load factor * capacity 时,重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元素挨个重新hash分配到新结构中去。

get()

对key的hashCode进行hashing,与运算计算下标获取bucket位置,如果在桶的首位上就可以找到就直接返回,否则在树中找或者链表中遍历找,如果有hash冲突,则利用equals方法去遍历链表查找节点

5. HashMap、HashTable是什么关系?

底层都是数组加链表实现,共有一个map接口 hashtable单独继承了一个dictionary抽象类, 并且线程安全 hashmap的kv可以为空而hashtable不可以,会出现空指针异常

6. HashMap的线程安全

HashMap线程不安全在哪?

  • 死循环 多线程扩容中会产生死循环的现象,使用了头插法,并且链表反转,但是再jdk8中,对resize()方法做出了调整,头节点用一次尾插法

  • 数据丢失/脏读 两个线程同时使用put()方法,且两个key指向同一个散列桶 ab取得相同的头节点,此时线程a操作头节点后,b在处理的头节点就会丢失或覆盖a操作的数据

如何规避HashMap的线程不安全?

集合工具类Collections里有关于集合包装成线程同步的方法,从而解决多线程并发下用hashmap的线程安全的问题

内部实现也很简单, 就是等同于hashtable, 只是对当前传入对象, 加了对象锁

使用ConcurrentHashMap这是hashmap线程安全的替代品,
使用了synchronized修饰符和CAS来保证并发安全, 数据结构跟HashMap1.8的结构类似synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍

自平衡的二叉查找树红黑树

  1. 节点是红色或黑色。
  2. 根节点是黑色。
  3. 每个叶子节点都是黑色的空节点(NIL节点)。
  4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
  5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

6. 有序的Map

HashMap 是不是有序的?无序的

有没有有序的Map实现类呢?有 TreeMap 和 LinkedHashMap

TreeMap 和 LinkedHashMap 是如何保证它的顺序的? TreeMap 是通过实现 SortMap 接口,能够把它保存的键值对根据 key 排序,基于红黑树,从而保证 TreeMap 中所有键值对处于有序状态。 LinkedHashMap 则是通过插入排序(就是你put的时候的顺序是什么,取出来的时候就是什么样子)和访问排序(改变排序把访问过的放到底部)让键值有序。