HashMap、HashTable、LinkedHashMap的区别

HashMap、HashTable、LinkedHashMap的区别

    • HashSet:
    • HashMap:
      • HashMap的容量为什么必须为2的幂次
      • 为什么HashMap线程不安全
      • 当给HashMap指定初始大小后,其容量实际大小是多少
      • Map内存泄漏
    • HashTable:
    • LinkedHashMap

HashSet:

HashSet其实就是具有相同value的HashMap
equals()相同,hashcode()也相同
hashcode()相同,equals()不一定相同

HashMap:

  1. 不是线程安全的,源码中没有与线程有关的任何内容
  2. 有一个静态内部类Node,实现了Map.Entry接口,该类中包含key、key的hash值、value、下一个node的引用,HashMap中的每一个元素都是一个Node对象
  3. 特殊的实例变量threshold:域值,指HashMap中的有效数据等于这个值的时候,就要进行扩容
  4. 特殊的实例变量loadFactor:默认为0.75,指每次进行扩容后的threshold与可容纳的长度的比值
  5. 储存数据时,是根据key的hash值作为数组的下标来保存,默认的初始化没有对HashMap设置容量大小,当第一次put数据时,会设置默认容量=16,即一个长度为16的数组,然后根据loadFactor得到域值(默认为16*0.75=12),即当数组中的实际个数大于12时,将数组的长度和域值一起进行扩容(<<1)为原来的两倍
  6. 扩容后的数为2的幂次方,因为在put数据时,是根据hash值与长度求&(也就是取模)得到下标进行保存的,即容量length为2^n,hash&(length-1)==hash%length

HashMap的容量为什么必须为2的幂次

如 length=16(二进制10000),hash1=10(二进制1010),hash2=7(二进制111)
分别将hash1和hash2与length进行位运算&:
length&hash1=0,length&hash2=0
由此推断16以内有所有数与16进行&fct运算的结果都为0,显然不对,但将16-1:
如 length=15(二进制1111),hash1=10(二进制1010),hash2=7(二进制111)
分别将hash1和hash2与length进行位运算&:
length&hash1=10,length&hash2=7
这样的话,位运算的结果是可以作为Hash的
因此HashMap的容量必须为2的幂次,元素所在的位置为容量-1与hash进行&运算

  1. 正常情况下,长度最大为1<<30(230),loadFactor的计算方法不变,如果实际值还是超过了230,则将域值和loadFactor都设置为Integer.MAX_VALUE
  2. 结构是数组(hash表)+链表+红黑数的实现,主要是数组+链表

a). 如果key的hash值不相等,HashMap为数组,数组中的每个元素为Node
b). 如果key的hash值相等,则这些相等的元素为链表,但当这个链表的长度大于8的时候,将这个链表转换成红黑树(红黑树为java8新加)
c). 8的来源是根据概率计算得来的,即出现hash值相等的个数很少会大于8,而红黑树大于8时,查询成本低,但新增成本高,因为要进行左右旋。
9. 红黑树的查找长度为log(n),当长度为8时,查找为3,链表为折半查找,当长度大于8时适用
10. 允许key和value为空

为什么HashMap线程不安全

  1. 因为HashMap中的所有操作,都没有任何有关线程的设置
  2. 主要是在扩容的时候,会创建新的空数组并生成新的hash值来保存数据,多线程会丢失数据
  3. 多线程情况下,将不能把每个hash值相同的元素保存下来

当给HashMap指定初始大小后,其容量实际大小是多少

  1. tableSizeFor()这个方法很神奇,当指定初始大小后,会对这个数字进行-1、右移、或运算,来算出比这个数字大,且离这个数字最近的2的幂次数
  2. 第一步-1操作,目的是为了防止指定的数字本身就是一个2的幂次数字
  3. 第二步右移和或运算,即将第一步后的数字进行右移,右移位数为:1、2、4、8、16,然后与右移前的数字进行或运算,右移1位:因为一个数字的二进制数,第一位必然是1,如右移前为0001xxxx,右移后为00001xxx,这两个二进制数进行或操作后结果为00011xxx,这样会必然出现有两个11,然后再右移2位和或,得到的是0001111x,依此类推,最后出现的数字为00011111,然后进行+1,得到的数字正好是一个2的幂次数

Map内存泄漏

内存泄漏指JVM中的对象没有被回收,但也不能再使用
内存溢出有栈溢出等。

Map内存泄漏是指map中的key大量重复,更新后的key,旧的key没有再引用
一般情况下,Map的key为String类型居多,而java自带的类型,都重写了hashCode()和equals()
如果用自定义类型的key,则这个类型必须重写hashCode()和equals(),这样在每次有key值相同时,都会重新计算hash

HashTable:

  1. 是线程安全的,synchronized关键字只添加到了方法上,所以是相对的线程安全
  2. 静态内部类为Entry实现了Map.Entry
  3. 是数组+链表的结构
  4. 默认的初始化大小为11,loadFactory为0.75,域值为11*0.75=8,且域值最大为Integer.MAX_VALUE-7,防止OOM
  5. 在扩容后,为原来的2倍+1
  6. value不允许为空
    7、 不常使用,因为效率没有HashMap高,线程也不是绝对安全

LinkedHashMap

  1. 不是线程安全
  2. 继承了HashMap
  3. 静态内部类为Entry也是继承自HashMap.Node,并多了两个引用before、after,表明为双向链表结构
  4. 整体结构为链表+数组(hash表)+链表+红黑树,即除了与HashMap的结构相同外,还另外维护一个双向链表结构,该链表中内容为每个节点的前一个和后一个节点
  5. 有一个成员属性boolean accessOrder,表示迭代顺序,默认为false(true为访问顺序,指根据访问的顺序进行排序;false为插入顺序,指根据插入的顺序进行排序)
  6. 增加(put):用的还是HashMap中的方法,在HashMap的put()方法中调用了newNode()方法,该方法在HashMap中直接返回新的Node,LinkedHashMap重写了该方法,指定了Node的before和after,来保证有序
  7. 在HashMap中有几个模板方法,实现是在LinkedHashMap中,实现内容即根据accessOrder对链表进行修改以保证对应顺序
  8. 在进行containsValue时,是通过循环节点来实现,比HashMap通过下标循环取数快
  9. 其他特点与HashMap一样,因为是继承自HashMap
  10. 有一个特殊的算法:LRU(least recently used)最近最少使用算法,或距今最久未用淘汰算法,是缓存管理中的一个算法,其内容为将最近访问过的元素移动到链尾,链头的元素即为访问过时间最久的

你可能感兴趣的:(java)