Java集合相关面试题

Java中集合框架组成部分

collection 是一个单例集合符接口

ArrayList

1、简介

ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除

2、ArrayList、Vector和LinkList 区别

相同点:

  1. ArrayList 和 Vector 相同底层都是底层数组结构

不同点:

  1. ArrayList 线程不安全,ArrayList 底层扩容是1.5倍;
  2. Vector 线程安全,底层扩容是2倍;
  3. LinedKList 底层采用链表形式结构;

3、ArrayList初始容量是多少

private static final int DEFAULT_CAPACITY = 10;

4、JDK1.7 ArrayList 和ArrayList1.8有什么区别

  • 在 java1.7 中在构造方法初始化容量
  • 在 jdk1.8 中 在 add 方法来设置初始化容量

5、描述一下AarryList中add方法

  1. 当添加第一个元素时,调用add方法,最小的minCapacity为1时,当前这元素集合为空时,则默认赋值初始为10;
  2. 在调用ensureCapacityInternal中group方法来判断是否扩容,当capacity大于10,则扩容为1.5倍,每次新增都会修改modCount;
  3. 在调用Array.copyOf把elementData赋值一个新的newCapacity;

6、描述AarryList的优缺点

优点:ArryList底层是动态数组结构,查询和修改数据效率非常快,通过下标快速查询,时间复杂比较低

缺点:新增和删除时需要从新拷贝和位移和计算位置,效率比较低;空间复杂度比较高Arrays.Copyof

LinkedList

1、 LinkedList 工作原理

LinkedList底层采用链表结构存储数据结构,每个node节点包含了 prev、item、next 结构,采用收尾相结合,查询和修改数据比较慢,删除和新增比较快

2、add 方法原理

  1. 首先在获取最后一个节点的数据
  2. 构建一个当前新的node节点
  3. 在赋值最后一个节点last = newNode
  4. 当最后一个节点为 null时,则当前新增节点为第一个节点

        Fist = newNode

        否则的 l.next = newNode

final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
    first = newNode;
else
    l.next = newNode;
size++;
modCount++;

HashMap

1、 HashMap工作原理

  1. HashMap根据键的 hashCode 值计算下标位置存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。
  2. HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。
  3. 如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。

2、HashMap 与HashTable 得区别

相同点

  1. HashMap和HashTable都是K V 键值对。

不同点

  1. HashMap 线程不安全;
  2. HashMap 可以插入为null值,并且插入为第一个数组;
  3. HashTable 线程安全,每个方法通过synchronized来控制线程安全;
  4. HasHTable 不能插入为null值;

3、HashCode发生碰撞怎样解决

  1. 当hashcode相同,先用eqlues比较,当对象相同时,直接把新值覆盖老值;
  2. 当对象不相同时,用链表存储在尾部;

4、 HashCode中put方法

 

(1)对key求哈希,在计算在table中的索引位置;

  1. 把高16位移运算到低16位,在通过异步运算;
  2. 在通过(hash.length-1)&hashCode计算出具体下标位置;

(2)如果没有碰撞,直接放入桶中;

(3)如果碰撞了,以链表的方式放在链表的后面;

(4)如果链表的长度超过阈值8且元素的个数大于64,将链表转换成红黑树;

(5)如果key已经存在,就新值替换旧值,并返回旧值;

(6)如果 size > threshold,就要resize。

1.8 采用的是尾插法

  1. 第一次添加元素时,table为null或length长度为0则扩容,则阈值:16*.75 = 12;
  2. 通过key计算hash求值 当下标计算下标的位置 当获取下标为空null则添加新的元素;
  3. 当hashCode相同,遍历链表存储到最后一个位置;
  4. 当链表长度大于8时,则链表修改为红黑树存储;
  5. 当key的value值相同时,则把新增覆盖老的值返回oldValue;
  6. 当元素的个数size >阈值时 则需要扩容;

7、HashMap的底层数据结构是怎样的?

  1. JDK1.7及之前HashMap底层是数组和链表;
  2. JDK1.8及以后HashMap底层是数组和链表以及红⿊树;

5、 HashMap 扩容

loadFactor默认值0.75,例如:数组长度默认值是16,如果数组上已经有12个元素(16*0.75)时,这个时候就需要进行扩容

当数组中元素的个数 size > 阈值的时候会进行扩容

  • 首先:第一步

        Table 长度 = 扩大为原来的2倍

        阈值 =阈值*2

  • 第二步:
  1. OldCap 进行遍历 复制到新的 newCap
  2. 1.7 :会把每个key从新计算位置到下标,复制到新的 容器里面
  3. 1.8 :e.hash & oldCap==0表示扩容前后对当前节点的索引值没有发生改变 e.hash & oldCap==1 时=原索引+oldCap

总结

  1. 感觉1.8性能是非常好
  2. 获取数据,当hashcode发生碰撞时同一个链表过长提高好几倍的效率
  3. 使用HashMap一个要赋初始容量,容量真的耗内存
  4. 当不用的对象,赋值为null,这样GC才会回收

6、HashMap时间复杂度是多少

  1. 链表存储:当index相同时,查询复杂度为O(n),因为hashcode相同时,底层采用链表结构,next获取下一数据,通过equals进行比较;
  2. 数组存储:当index不相同时,查询时间复杂度为O(1),因为底层采用数组结构,查询一次就可以;
  3. 红黑树存储:时间复杂度Olog(n)

ConcurrentHashMap

ConcurrentHashMap底层实现原理(JDK1.7 & 1.8) - 简书

1、ConcurrentHashMap的存储结构是怎样的?

1. Java7中的ConcurrnetHashMap的数据结构是数组和链表。

  • 确保多线程安全的机制使用的分段锁,ConcurrnetHashMap初始化时默认会创建⼀个16个元素的
  • Segment,每⼀个线程只会对⼀个Segment进行操作,从而确保多线程安全。同时每⼀个Segment都是⼀个类似HashMap数组的结构,可以扩容,遇到Hash冲突会转化为链表

2. Java8中的ConcurrnetHashMap的数据结构是数组+链表+红⿊树。

  • 使用CAS加Synchronized锁机制确保线程安全。

2、ConcurrentHashMap中put方法

  1. 如果没有初始化就先调用initTable()方法来进行初始化过程;
  1. 如果没有hash冲突就直接CAS插入;
  2. 如果还在进行扩容操作就先进行扩容;
  3. 如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入;
  4. 最后一个如果Hash冲突时会形成Node链表,在链表长度超过8,Node数组超过64时会将链表结构转换为红黑树的结构,break再一次进入循环;
  5. 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容;

3、ConcurrentHashMap中get方法

  1. 计算hash值,定位到该table索引位置,如果是首节点符合就返回;
  2. 如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回;
  3. 以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null;

4、总结与思考

其实可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树,相对而言,总结如下思考

  1. JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)
  2. JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了
  3. JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档
  4. JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock,我觉得有以下几点
    1. 因为粒度降低了,在相对而言的低粒度加锁方式,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了
    2. JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然
    3. 在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存,虽然不是瓶颈,但是也是一个选择依据


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值