Java基础之集合框架的一些问题

1.LinkedHashMap继承了HashMap

HashMap,WeakHashMap和IdentityHashMap继承了abstractMap

TreeMap继承了abstrcatMap,同时它实现的navigableMap接口继承了SortedMap

他们都实现了Map接口

2.在abstractMap中有两个成员变量,分别是keysetvalues,他们都被transient关键字修饰,即不可序列化

被static修饰的静态变量也是不可序列化的,因为序列化是保存的是对象状态,静态变量保存的是类状态,所以static修饰的静态变量天然不可序列化,不管有没有被transient关键字修饰。注意:如果transient修饰的变量为自定义类变量,那该类需要实现Serializable接口

3.abstractMap中有两个内部类,SimpleEntrySimpleImmutableEntry,他们都实现了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冲突?

  1. 开放地址法:将hash表中的空地址向冲突键开放,当hash表未满时,冲突的键就需要尝试另外的单元,直到找到空单元为止
  2. 链地址法:将具有相同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三者有何不同

  1. Hashtable继承dictionary类,treeMap和hashmap继承abstractMap类
  2. Hashtable和treemap的键和值都不能为null,hashmap的键有一个可以为null,值可以有多个为null
  3. Hashtable和hashmap都是无序的,treemap由于是利用红黑树实现的(即任何一个节点的值都大于等于它左节点的值,小于等于它右节点的值),所以是默认按键的升序排列

一般情况下选hashmap,因为相对而言它的效率最高

Hashmap1.7和1.8的区别

  1. 数据结构的不同
  2. 1.7扩容时需要重新计算hash值,1.8不需要重新计算,采用原hash值和扩容后容量n进行&操作来计算新的索引位置
  3. 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,线程不安全

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Superzl1002

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值