面试Java集合没过关?答案在这呢
今天就发表一些面试中常考的面试问题,关于Java集合部分的。
ArrayList
初始容量:10
扩容策略
- new =old+old>>1,就是扩大为原容量的1.5倍。如果需要的容量大于Integer.MAX_VALUE-8,则会使用Integer.MAX_VALUE作为新的容量。
扩容过程
- 创建一个新的数组,其长度为新计算的容量。
- 将原数组中的所有元素复制到新数组中。
- 将ArrayList的内部引用从原数组更新为新数组。
扩容性能影响
扩容过程涉及到内存分配和元素复制,可能会对性能造成影响。
- ArrayList不是线程安全的集合类,在多线程环境下,如果多个线程同时对ArrayList进行读写操作,可能会导致数据不一致或 抛出 并发修改异常。
- 使用Array List在多线程环境下需要进行同步操作,这会增加额外的开销,影响性能。可以使用线程安全的集合类Vector或者通过同步代码块来保证线程安全,但这也会降低并发性能。
- 可以通过设置一个合适的初始容量,以减少扩容次数。
Array List的添加和删除元素为什么慢?
- Array List的内部实现是基于数组。而数组在插入和删除元素时需要移动其他元素来保证连续性和顺序性,这个过程需要耗费较多的时间。
Array List支持通过索引进行快速随机访问。
HashSet
默认情况下,HashSet不是线程安全的,所以需要使用Hash Set线程安全的方法
- 使用 Collections.synchronizedSet:
- Java提供了一个简单的方法来创建线程安全的集合,即通过Collections.synchronizedSet方法。这个方法返回一个同步的集合包装器,确保在多线程环境下对集合的访问时线程安全的。
- 使用 ConcurrentHashMap:
- 如果需要更高效的并发访问,可以使用 ConcurrentHashMap 来实现类似 HashSet的功能,ConcurrentHashMap提供了更细粒度的锁机制,在并发环境下性能更好。
- 使用 CopyOnWriteArraySet:
- 对于读操作远多于写操作的场景,可以使用 CopyOnWriteArraySet。它的实现基于 CopyWriteArrayList,在每次修改时都会复制整个底层数组,因此在写操作较少时性能较好。
HashMap
- 在JDK1.7采用数组+链表的数据结构来实现,存在Entry链死循环和数据丢失问题。
- 在JDK1.8采用数组+链表+红黑二叉树的数据结构来实现,优化了1.7中数组扩容的方案,解决了Entry链死循环和数据丢失问题。
- 但在多线程背景下,put方法存在数据覆盖问题。
- 多线程环境下可以使用Collections.syunchronizedMap同步加锁的方式,可以使用HashTable,但是同步的方式显然性能不达标。而ConcurrentHashMap更适合高并发场景使用。
- ConcurrentHashmap在JDK1.7使用 Segment+HashEntry分段锁的方式实现,1.8则抛弃了Segment,改为使用CAS+synchronized+Node实现,也加入了红黑树,避免链表过长导致性能问题。
ConcurrentHashMap怎样实现
- JDK1.7 数组+链表Segment:分段锁地核心,每个Segment是一个小的哈希表,拥有独立的锁。HashEntry:哈希表中的每个节点,存储键值对。ConcurrentHashMap:包含多个Segment,每个Segment管理一部分的哈希表
- JDK1.8 数组+链表主要通过volatile+CAS或者synchronized来实现的,线程安全的。
Hash Map的扩容过程
- 当添加元素是,如果当前数组为空,会进行初始化。默认情况下,会创建一个长度为16的数组,并且加载因子(load factor)默认为0.75.
- 当数组中的元素数量大于等于数组长度与加载因子的乘积时,会触发扩容。扩容时,数组长度会翻倍(*2),并重新哈希所有元素到新的数组中。
哈希冲突的解决方法
- 链接法:使用链表或其他数据结构来存储冲突的键值对,将它们链接再同一个哈希桶中。
- 开放寻址法:在哈希表中找到一个可用的位置来存储冲突的键值对,而不是存储再链表中。常见的开放寻址方法包括线性探测、二次探测和双重散列。
- 再哈希法:当发生冲突时,使用另一个哈希函数再次计算键的哈希值,直到找到一个空槽来存储键值对。
- 哈希桶扩容:当哈希冲突过多时,可以动态地扩大哈希桶地数量,重新分配键值对,以减少冲突地概率。
Java面试题 文章被收录于专栏
本专栏汇总了大量的Java面试题和Java面经,希望对大家有所帮助!