HashMap底层及源码整理*

之前用富文本编辑器效果不是很好,还是用Markdown舒服。。。

关于Map接口的延伸关系:HashMap底层及源码整理*
下面针对各个实现类的特点做一些说明:
  1. HashMap:根据hashCode值存储数据,大多数情况下可以直接定位到他的值,因而具有很快的访问顺序,但遍历顺序确实不确定的。HashMap最多只允许一条记录的键为null,允许多条值为null,HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用Collection借口的synchronizedMap或ConcurrentHashMap
  2. HashTable:HashTable都是基于元素的哈希码以及equals方法来存储的。是遗留类,很多映射的常用功能跟HashMap相似,不同的是他继承于Dictionary类,并且是线程安全的,任一时间只能有一个线程写HashTable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。HashTable不建议在新代码中使用,若不需要线程安全可以用HashMap代替,需要线程安全的场合可以用ConcurrentHashMap替换
  3. LinkedHashMap:LinkedHashMap是HashMap的子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的数据肯定是先插入的,也可以在构造时带参数,按照访问次序排序
  4. TreeMap:TreeMap实现SortedMap接口,能够将保存的数据根据键排序,默认是按键值的升序排序。也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或在构造TreeMap是传入自定义的Comparator,否则会在运行时报ClassCastException

HashMap底层及源码整理*
HashMap底层及源码整理*

遍历取KeyValue
HashMap底层及源码整理*

HashMap:HashMap是一个用来存储键值对的集合,每一个键值对就是一个Entry,这个Entry分布存储在一个数组中,这个数组就是HashMap的主干HashMap底层及源码整理*

功能实现:

  1. 确定数组索引位置HashMap底层及源码整理*
    HashMap底层及源码整理*

其中n为table的长度
2. 分析HashMap的put方法:
1. 判断键值对对数组table[i]是否为空或者为null,否则执行resize()进行扩容
2. 根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向6,如果table[i]不为空,则接着转向3
3. 判断table[I]的首个元素是否和key一样,如果相同直接覆盖value,否则转向4,这里的相同指的是hashCode一级equals
4. 判断table[I]是否为treeNode,即table[I]是否为红黑树,如果是红黑树则直接在树中插入键值对,否则转向5
5. 遍历table[i] (链表),判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作,遍历过程中若发现key已经存在则直接覆盖value
6. 插入成功后判断实际存在的键值对数量size是否超过了最大容量threshold,如果超过则进行扩容
7. risize方法中有遍历每个节点是否小于阙值6若小于则把树形转化为链表
进程图:
HashMap底层及源码整理*
源码:
HashMap底层及源码整理*

为什么HashMap不是线程安全的

假如一个HashMap到了resize的临界点,这时两个线程同时对他进行了扩容之后进行ReHash,可能会形成链表环,当get一个不存在的key而这个key的hash计算为该下标时会造成死循环

一些问题
  1. 如果HashMap的大小超过了负载因子定义的容量怎么办?
    扩容
  2. 重新调整HashMap大小会发生什么问题?rehash线程不安全问题
  3. HashMap和HashTable的区别
  • 继承不同HashTable继承自Dictionary类,HashMap继承自AbstractMap
  • HashMap同步,HashMap不同步
  • HashTable不允许null值的键值,HashMap允许一个null键多个null值
  • Hash值不同,HashTable直接使用对象的hashCode,hashMap还要重新计算
  • 默认大小和扩容机制不同遍历方式的内部实现不同,HashTable除了Iterator外还用了Enumeration
  1. 常用的键?为什么?
    String,Integer这样的wrapper类包装类作为hashMap的类再适合不过了,而且String最为常用,因为String是不可变的,也是final的,而且已经重写了equals和hashCode方法,其他的wrapper类页游这个特点。不可变性是必要的,因为为了要计算hashCode,就要防止键值改变,如果键值在放入时和获取时返回不同的hashCode的话,那么就不能从HashMap中找到对象,不可变性还有其他的优点如线程安全。