HashMap、HashTable、LinkedHashMap的区别
HashSet:
HashSet其实就是具有相同value的HashMap
equals()相同,hashcode()也相同
hashcode()相同,equals()不一定相同
HashMap:
- 不是线程安全的,源码中没有与线程有关的任何内容
- 有一个静态内部类Node,实现了Map.Entry接口,该类中包含key、key的hash值、value、下一个node的引用,HashMap中的每一个元素都是一个Node对象
- 特殊的实例变量threshold:域值,指HashMap中的有效数据等于这个值的时候,就要进行扩容
- 特殊的实例变量loadFactor:默认为0.75,指每次进行扩容后的threshold与可容纳的长度的比值
- 储存数据时,是根据key的hash值作为数组的下标来保存,默认的初始化没有对HashMap设置容量大小,当第一次put数据时,会设置默认容量=16,即一个长度为16的数组,然后根据loadFactor得到域值(默认为16*0.75=12),即当数组中的实际个数大于12时,将数组的长度和域值一起进行扩容(<<1)为原来的两倍
- 扩容后的数为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<<30(230),loadFactor的计算方法不变,如果实际值还是超过了230,则将域值和loadFactor都设置为Integer.MAX_VALUE
- 结构是数组(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线程不安全
- 因为HashMap中的所有操作,都没有任何有关线程的设置
- 主要是在扩容的时候,会创建新的空数组并生成新的hash值来保存数据,多线程会丢失数据
- 多线程情况下,将不能把每个hash值相同的元素保存下来
当给HashMap指定初始大小后,其容量实际大小是多少
- tableSizeFor()这个方法很神奇,当指定初始大小后,会对这个数字进行-1、右移、或运算,来算出比这个数字大,且离这个数字最近的2的幂次数
- 第一步-1操作,目的是为了防止指定的数字本身就是一个2的幂次数字
- 第二步右移和或运算,即将第一步后的数字进行右移,右移位数为: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:
- 是线程安全的,synchronized关键字只添加到了方法上,所以是相对的线程安全
- 静态内部类为Entry实现了Map.Entry
- 是数组+链表的结构
- 默认的初始化大小为11,loadFactory为0.75,域值为11*0.75=8,且域值最大为Integer.MAX_VALUE-7,防止OOM
- 在扩容后,为原来的2倍+1
- value不允许为空
7、 不常使用,因为效率没有HashMap高,线程也不是绝对安全
LinkedHashMap
- 不是线程安全
- 继承了HashMap
- 静态内部类为Entry也是继承自HashMap.Node,并多了两个引用before、after,表明为双向链表结构
- 整体结构为链表+数组(hash表)+链表+红黑树,即除了与HashMap的结构相同外,还另外维护一个双向链表结构,该链表中内容为每个节点的前一个和后一个节点
- 有一个成员属性boolean accessOrder,表示迭代顺序,默认为false(true为访问顺序,指根据访问的顺序进行排序;false为插入顺序,指根据插入的顺序进行排序)
- 增加(put):用的还是HashMap中的方法,在HashMap的put()方法中调用了newNode()方法,该方法在HashMap中直接返回新的Node,LinkedHashMap重写了该方法,指定了Node的before和after,来保证有序
- 在HashMap中有几个模板方法,实现是在LinkedHashMap中,实现内容即根据accessOrder对链表进行修改以保证对应顺序
- 在进行containsValue时,是通过循环节点来实现,比HashMap通过下标循环取数快
- 其他特点与HashMap一样,因为是继承自HashMap
- 有一个特殊的算法:LRU(least recently used)最近最少使用算法,或距今最久未用淘汰算法,是缓存管理中的一个算法,其内容为将最近访问过的元素移动到链尾,链头的元素即为访问过时间最久的