1.LinkedHashMap继承了HashMap
HashMap,WeakHashMap和IdentityHashMap继承了abstractMap
TreeMap继承了abstrcatMap,同时它实现的navigableMap接口继承了SortedMap
他们都实现了Map接口
2.在abstractMap中有两个成员变量,分别是keyset和values,他们都被transient关键字修饰,即不可序列化。
被static修饰的静态变量也是不可序列化的,因为序列化是保存的是对象状态,静态变量保存的是类状态,所以static修饰的静态变量天然不可序列化,不管有没有被transient关键字修饰。注意:如果transient修饰的变量为自定义类变量,那该类需要实现Serializable接口
3.abstractMap中有两个内部类,SimpleEntry和SimpleImmutableEntry,他们都实现了Map接口中内置的Entry接口,也实现了可序列化接口。他们两个的不同之处在于,从方法上看SimpleEntry的改变值方法可用,而调用SimpleImmutableEntry改变值的方法会抛出异常;其次从内部字段来看,SimpleEntry只有键不可变,SimpleImmutableEntry键值都不可变
4.数组:存储区间内连续,查询快,占内存大,空间复杂度大,插入和删除很麻烦,因为插入某位置后该位置之后的元素全部得后移,删除同理。并且大小固定不易进行动态扩展
链表:存储区间内离散,插入删除快,查询慢,因为每次查询都要从第一个开始遍历,大小扩展灵活
哈希表:也叫作散列表,继承自dictionary类。他通过key和一个hash函数计算出对应的hash值,这个hash值决定了这个键值对存放的位置。
Hash函数:将hash表中的键值对映射为元素存储位置的函数,hash函数应易于计算,并且使计算出来的索引值均匀分布,减少哈希冲突。Hash函数计算出的索引值是一个固定长度的输出值。如果Hash(key1) !=Hash(key2),那么key1一定不等于key2;如果Hash(key1) ==Hash(key2),那么key1可能等于key2也可能不等于key2,不等于时,会发生hash冲突。
Hash冲突:两个不同的key通过hash函数计算出的hash值相同。
如何解决hash冲突?
- 开放地址法:将hash表中的空地址向冲突键开放,当hash表未满时,冲突的键就需要尝试另外的单元,直到找到空单元为止
- 链地址法:将具有相同hash值的键存储在同一个链表中,假设hash表长为m,那么就可以将hash表定义为一个由m个头结点组成的链表数组
在解决hash冲突时,我们一般采用链地址法,链地址法相对于开放地址法需要更多的内存空间,但它可以减少在查找和插入具有相同hash值的key的操作过程中的平均查找长度,因为在链地址法中,待比较的key都是hash值相同的key,但开放地址法中还包含了hash值不同的key
5.HashMap
5.1.在JDK1.7之前hashmap的底层结构为数组+链表
在1.7之后为数组+链表/红黑树,在链表长度达到8时链表就会尝试转化为红黑树,如果数组长度没有大于64,则继续使用扩容策略,当数组长度大于64时,将链表的所有节点转化为红黑树。当红黑树的节点小于等于6个时,又会转化为链表。因为红黑树本质上是平衡二叉树,查询效率要高于链表
5.2在hashmap中有两个重要的参数:初始容量大小和加载因子。初始容量即数组的初始长度为16,加载因子为0.75f,用数组容量乘以加载因子得到一个值,当数组中元素个数超过这个值,就会调用rehash方法引起扩容,此时会创建一个数组长度为原来两倍的新数组,并且原数组中的所有数据都会重新计算hash值加入到新数组中,所以扩容是非常消耗性能的
Hashtable,hashmap,treemap三者有何不同
- Hashtable继承dictionary类,treeMap和hashmap继承abstractMap类
- Hashtable和treemap的键和值都不能为null,hashmap的键有一个可以为null,值可以有多个为null
- Hashtable和hashmap都是无序的,treemap由于是利用红黑树实现的(即任何一个节点的值都大于等于它左节点的值,小于等于它右节点的值),所以是默认按键的升序排列
一般情况下选hashmap,因为相对而言它的效率最高
Hashmap1.7和1.8的区别
- 数据结构的不同
- 1.7扩容时需要重新计算hash值,1.8不需要重新计算,采用原hash值和扩容后容量n进行&操作来计算新的索引位置
- 1.7采用头插入法,这样容易在扩容时发生链表成环的问题。1.8采用尾插入法
Hashmap在1.7和1.8都是线程不安全的(1.8中虽然解决的死循环和数据丢失问题,但仍然存在数据覆盖问题),线程安全场景应使用concurrentHashMap
concurrentHashMap在1.7和1.8的区别?
1.数据结构
1.7:Segment数组 + hashEntry数组 + 链表
1.8:hashEntry数组 + 链表/红黑树
2.锁机制
1.7:Reentrantlock,锁分段
1.8:CAS + Synchronized
1.8相对于1.7的优化?
1.put和get操作时,1.7中需要计算两次hash值,首先确定在Segment数组上的索引,再计算在HashEntry数组上的索引。1.8中只要计算一次
2.1.8底层引进了红黑树代替链表,增大了查询效率,时间复杂度从o(n)缩小到o(logn)
3.由于1.8中移除了Segment,所以锁粒度也随之减小
Hashtable也是线程安全的,为什么不推荐使用呢?
Hashtable使用Synchronized方式实现线程安全,但在多线程激烈竞争的情况下,它的效率非常低,因为多线程访问hashtable的同步方法时,可能会阻塞或轮询
ArrayList:底层使用数组(elementData[])存储,它允许存储所有元素,包括null,可以存储多个null,ArrayList是线程不安全的,因为它的源码里面没有用Synchronized关键字,所以多线程情况下不建议使用ArrayList。
创建ArrayList对象时,如果使用的是无参构造器,那么默认底层数组长度为0,第一次添加数据需要扩容,扩容为10。当需要再次扩容时,扩容为原来的1.5倍。
若使用的是有参构造,则参数即为数组长度,扩容时也是1.5倍
Vector:底层也是维护一个数组,相对于ArrayList而言,它是线程安全的。若使用无参构造器,则默认数组长度为10,扩容按两倍扩容。
LinkedList:底层实现了双向链表和双端队列的特点,可以添加任意元素,包括null,线程不安全