当前位置: 首页 > article >正文

常见面试题之JAVA集合

1. Java中的集合分为哪几大类?

参考答案
Java中的集合主要分为三大类:ListSetMap,还有一种不常用的Queue

  • List:有序集合,允许有重复元素,可以插入多个null元素。常用的实现类有ArrayListLinkedListVector
  • Set:无序集合,不允许有重复元素,只允许有一个null元素。常用的实现类有HashSetLinkedHashSetTreeSet
  • Map:一种键值对的集合,键不允许重复,值可以重复。常用的实现类有HashMapLinkedHashMapTreeMapHashtable等。
  • Queue:一种先进先出(FIFO)的集合,常用于任务调度。常用的实现类有LinkedList(它也实现了List接口)、PriorityQueue等。

2. ArrayListLinkedList的区别是什么?

参考答案

  • 底层实现ArrayList是基于数组实现的,LinkedList是基于链表实现的。
  • 性能ArrayList在随机访问方面性能优越,因为数组支持快速随机访问;而LinkedList在插入和删除操作方面性能较好,因为链表支持快速的插入和删除操作。但在迭代访问时,LinkedList的性能会低于ArrayList
  • 内存使用ArrayList需要连续的内存空间来存储其元素,因此内存利用率较高,但扩容时需要复制整个数组,内存开销较大;LinkedList的每个元素都需要额外的存储空间来存储指向下一个元素的指针,因此内存利用率较低,但扩容时只需改变指针的指向,内存开销较小。
  • 线程安全:两者都不是线程安全的,如果需要在多线程环境下使用,需要手动同步。

3. HashSet是如何保证不重复的?

参考答案
HashSet是基于HashMap实现的,HashSet中的元素实际上是存储在HashMap的key中的。HashMap的key是不允许重复的,当向HashSet中添加一个元素时,HashSet会调用该元素的hashCode()方法来获取其哈希值,并根据哈希值来确定该元素在HashMap中的存储位置。如果两个元素的哈希值相同,HashMap会进一步通过equals()方法来比较这两个元素是否相等,如果相等,则后添加的元素不会被存储,从而保证了HashSet中的元素不重复。

4. HashMap的工作原理是什么?

参考答案
HashMap是基于哈希表(也称为散列表)实现的。哈希表是一个数组,数组中的每个元素称为桶(bucket),每个桶可以存储一个或多个键值对(Entry)。当向HashMap中添加一个键值对时,HashMap会根据键的hashCode()方法来计算该键的哈希值,并根据哈希值来确定该键值对应该存储在哪个桶中。如果两个键的哈希值相同(也称为哈希冲突),则这两个键值对会被存储在同一个桶中,并通过链表或红黑树(在JDK 8及以后)来连接。当需要查找一个键时,HashMap会根据该键的哈希值找到对应的桶,并在桶中遍历链表或红黑树来查找该键对应的值。

5. HashMapHashtable的区别是什么?

参考答案

  • 线程安全HashMap是非线程安全的,而Hashtable是线程安全的。Hashtable的所有方法都是同步的,因此在多线程环境下使用较为安全,但性能会较低。而HashMap在单线程环境下性能较高,但在多线程环境下需要手动同步。
  • 性能:由于Hashtable是线程安全的,因此在性能方面会比HashMap差一些。在单线程环境下,HashMap的性能要优于Hashtable
  • 允许nullHashMap允许键和值都为null,而Hashtable不允许键或值为null。如果向Hashtable中插入null键或null值,会抛出NullPointerException异常。
  • 继承关系HashMap是继承自AbstractMap类,并实现了Map接口;而Hashtable是继承自Dictionary类,并实现了Map接口(但实际上Hashtable是一个古老的类,在Java 2之前就存在了,而Map接口是在Java 2中引入的)。

6. TreeMap是如何实现排序的?

参考答案
TreeMap是基于红黑树(一种自平衡的二叉查找树)实现的。红黑树是一种有序的数据结构,它能够在O(log n)的时间复杂度内完成插入、删除和查找操作。当向TreeMap中添加一个键值对时,TreeMap会根据键的自然顺序(或者根据构造函数中指定的比较器)来将键值对插入到红黑树中。由于红黑树是有序的,因此TreeMap中的键值对也是按照键的顺序排列的。当对TreeMap进行迭代时,迭代器会按照键的顺序返回键值对。

7. LinkedHashMapHashMap的区别是什么?

参考答案

  • 底层实现LinkedHashMapHashMap都是基于哈希表实现的,但LinkedHashMapHashMap的基础上增加了一个双向链表来维护键值对的插入顺序或访问顺序。
  • 顺序性HashMap是无序的,即不保证键值对的插入顺序和迭代顺序相同;而LinkedHashMap是有序的,它可以根据插入顺序或访问顺序来迭代键值对。默认情况下,LinkedHashMap是按照插入顺序来迭代键值对的。
  • 性能:由于LinkedHashMap需要维护一个双向链表来保持顺序性,因此在性能方面会比HashMap稍微差一些。但在大多数情况下,这种性能差异是可以忽略不计的。

8. ConcurrentHashMap是如何实现线程安全的?

参考答案
ConcurrentHashMap是Java中的一个线程安全的哈希表实现。它采用了分段锁(Segment Lock)技术来提高并发性能。在ConcurrentHashMap中,整个哈希表被划分为多个段(Segment),每个段都是一个独立的哈希表,并且每个段都有自己的锁。当多个线程同时访问ConcurrentHashMap时,它们可以并发地访问不同的段,而不需要等待其他线程释放锁。这样,ConcurrentHashMap就可以在多线程环境下实现高效的并发访问。在JDK 8及以后,ConcurrentHashMap的实现发生了较大的变化,它不再使用分段锁技术,而是使用了更加细粒度的锁(如CAS操作和synchronized块)来实现线程安全。这种变化使得ConcurrentHashMap在性能方面有了更大的提升,并且更加适用于高并发场景。

9. ArrayListVector的区别是什么?

参考答案

  • 线程安全Vector是线程安全的,而ArrayList是非线程安全的。Vector的所有方法都是同步的,因此在多线程环境下使用较为安全;而ArrayList在单线程环境下性能较高,但在多线程环境下需要手动同步。
  • 性能:由于Vector是线程安全的,因此在性能方面会比ArrayList差一些。在单线程环境下,ArrayList的性能要优于Vector
  • 扩容机制ArrayListVector在扩容时都会创建一个新的数组,并将旧数组中的元素复制到新数组中。但是,ArrayList的扩容机制是动态的,当数组容量不足时,会自动扩容为原来的1.5倍(或指定的大小);而Vector的扩容机制是固定的,当数组容量不足时,会按照指定的增量(默认为原容量的1倍)进行扩容。
  • 历史原因Vector是一个古老的类,在Java 2之前就存在了;而ArrayList是在Java 2中引入的,作为Vector的一个替代品。由于Vector是线程安全的,因此在早期多线程环境下使用较多;但随着Java多线程技术的发展和普及,以及ArrayList在单线程环境下性能的优势,现在ArrayList的使用更加广泛。

10. HashSetTreeSet的区别是什么?

参考答案

  • 底层实现HashSet是基于HashMap实现的,而TreeSet是基于TreeMap实现的。因此,HashSet的性能和行为与HashMap类似,而TreeSet的性能和行为与TreeMap类似。
  • 排序性HashSet是无序的集合,不保证元素的存储顺序和迭代顺序相同;而TreeSet是有序的集合,它会按照元素的自然顺序(或者根据构造函数中指定的比较器)来对元素进行排序。因此,TreeSet的迭代顺序是确定的,与元素的插入顺序无关。
  • 性能:由于TreeSet需要维护元素的排序顺序,因此在性能方面会比HashSet差一些。特别是在插入和删除操作时,TreeSet需要调整红黑树的结构来保持排序顺序,而HashSet则不需要。
  • 允许nullHashSet允许包含一个null元素,而TreeSet不允许包含null元素。如果向TreeSet中插入null元素,会抛出NullPointerException异常。

11. Collections工具类中有哪些常用的方法?

参考答案
Collections工具类提供了许多用于操作集合的静态方法,以下是一些常用的方法:

  • sort(List<T> list):对指定的列表进行排序。
  • reverse(List<?> list):反转指定列表的顺序。
  • shuffle(List<?>list):使用默认随机源对指定列表进行置换,所有排列具有相同的出现可能性。
  • fill(List<? super T> list, T obj):使用指定元素替换指定列表中的所有元素。
  • copy(List<? super T> dest, List<? extends T> src):将指定源列表中的所有元素复制到目标列表中。
  • max(Collection<? extends T> coll, Comparator<? super T> comp):根据指定比较器返回给定集合的最大元素。
  • min(Collection<? extends T> coll, Comparator<? super T> comp):根据指定比较器返回给定集合的最小元素。
  • addAll(Collection<? super T> collection, T... elements):将所有指定的元素添加到指定集合中。
  • frequency(Collection<?> c, Object o):返回指定集合中指定元素的出现次数。
  • disjoint(Collection<?> c1, Collection<?> c2):如果两个指定集合没有公共元素,则返回 true
  • emptyList():返回一个空的、不可变的列表。
  • emptySet():返回一个空的、不可变的集合。
  • emptyMap():返回一个空的、不可变的映射。
  • singletonList(T o):返回一个只包含指定对象的不可变列表。
  • singleton(T o):返回一个只包含指定对象的不可变集合。
  • singletonMap(K key, V value):返回一个仅包含指定键和值的不可变映射。

12. List接口中有哪些常用的方法?

参考答案
List接口继承自Collection接口,并增加了许多用于操作列表的特定方法,以下是一些常用的方法:

  • add(E e):将指定的元素追加到此列表的末尾。
  • add(int index, E element):在列表的指定位置插入指定的元素。
  • get(int index):返回列表中指定位置的元素。
  • remove(int index):移除列表中指定位置的元素。
  • set(int index, E element):用指定的元素替换列表中指定位置的元素。
  • indexOf(Object o):返回指定元素在列表中第一次出现的索引,如果元素不存在,则返回 -1。
  • lastIndexOf(Object o):返回指定元素在列表中最后一次出现的索引,如果元素不存在,则返回 -1。
  • subList(int fromIndex, int toIndex):返回列表中指定的 fromIndex(包括)和 toIndex(不包括)之间的部分视图。
  • size():返回列表中的元素数。
  • isEmpty():如果列表不包含元素,则返回 true
  • contains(Object o):如果列表包含指定的元素,则返回 true
  • iterator():返回列表中的元素的迭代器。
  • listIterator():返回列表中元素的列表迭代器(可以从任意位置开始遍历列表)。

13. Set接口中有哪些常用的方法?

参考答案
Set接口继承自Collection接口,但由于其不允许包含重复元素,因此具有一些特定的行为。以下是一些常用的方法:

  • add(E e):如果集合中尚未存在指定的元素,则添加该元素。
  • remove(Object o):从集合中移除指定的元素。
  • contains(Object o):如果集合包含指定的元素,则返回 true
  • size():返回集合中的元素数。
  • isEmpty():如果集合不包含元素,则返回 true
  • iterator():返回集合中的元素的迭代器。
  • clear():从集合中移除所有元素。

由于Set接口不允许包含重复元素,因此它没有像List接口那样的根据索引访问元素的方法。

14. Map接口中有哪些常用的方法?

参考答案
Map接口提供了用于操作键值对的许多方法,以下是一些常用的方法:

  • put(K key, V value):将指定的键与指定的值关联(或覆盖)。
  • get(Object key):返回指定键所映射的值,如果映射不存在,则返回 null
  • remove(Object key):从映射中移除指定键的键-值对。
  • containsKey(Object key):如果映射包含指定键的键-值对,则返回 true
  • containsValue(Object value):如果映射包含一个或多个键映射到指定值,则返回 true
  • size():返回映射中的键-值对的数目。
  • isEmpty():如果映射不包含任何键-值对,则返回 true
  • keySet():返回映射中包含的键的视图。
  • values():返回映射中包含的值的视图。
  • entrySet():返回映射中包含的键-值对的视图。
  • getOrDefault(Object key, V defaultValue):返回指定键映射的值,如果该映射不存在,则返回默认值。
  • putIfAbsent(K key, V value):如果指定键不存在,或者其当前映射的值是 null,则将其与指定的值关联并返回 null,否则返回当前值。

15. HashMap的初始容量和加载因子是什么?它们对性能有什么影响?

参考答案
HashMap的初始容量和加载因子是影响其性能的两个重要参数。

  • 初始容量HashMap的初始容量是创建时哈希表的桶的数量。默认初始容量是16。如果初始容量设置得太小,随着元素的增加,哈希表会频繁地进行扩容操作,这会影响性能。扩容操作需要重新分配内存和重新哈希所有元素,因此是一个昂贵的操作。
  • 加载因子:加载因子是哈希表在其容量自动增加之前可以达到多满的一种度量。默认加载因子是0.75。当哈希表中的元素数量超过加载因子与当前容量的乘积时,哈希表会进行扩容操作。加载因子过高会导致哈希表中桶的链表过长,增加查找元素的时间复杂度;加载因子过低会导致哈希表频繁扩容,浪费内存。
    合理选择初始容量和加载因子可以平衡哈希表的时间复杂度和空间复杂度,从而提高HashMap的性能。

16. HashMap中的哈希冲突是如何解决的?

参考答案
HashMap中,哈希冲突是通过链表(或红黑树,在JDK 8及以后)来解决的。当两个或多个键的哈希值相同时,它们会被放在同一个桶中,并通过链表或红黑树来连接。具体来说:

  • 链表:在JDK 7及之前,当发生哈希冲突时,HashMap会在桶中使用链表来存储多个键值对。当查找一个键时,HashMap会根据键的哈希值找到对应的桶,并在桶中遍历链表来查找该键对应的值。
  • 红黑树:在JDK 8及以后,当桶中的链表长度超过阈值(默认为8)时,HashMap会将链表转换为红黑树。红黑树是一种自平衡的二叉查找树,它能够在O(log n)的时间复杂度内完成插入、删除和查找操作。因此,当桶中的元素较多时,使用红黑树可以提高查找效率。
    通过链表和红黑树的结合使用,HashMap可以有效地解决哈希冲突问题,并在大多数情况下提供较好的性能。

17. LinkedHashMap是如何维护插入顺序或访问顺序的?

参考答案
LinkedHashMap通过维护一个双向链表来保持元素的插入顺序或访问顺序。具体来说:

  • 插入顺序:当使用默认构造函数创建LinkedHashMap时,它会按照元素的插入顺序来迭代键值对。这是通过在每个桶中维护一个双向链表来实现的。当向LinkedHashMap中添加一个键值对时,新元素会被添加到链表的末尾。因此,迭代LinkedHashMap时,会按照插入顺序返回键值对。
  • 访问顺序:当使用带有accessOrder参数的构造函数创建LinkedHashMap,并将accessOrder设置为true时,LinkedHashMap会按照元素的访问顺序来迭代键值对。在这种情况下,每次访问(通过getput方法)一个元素时,该元素都会被移动到链表的末尾。因此,迭代LinkedHashMap时,会按照最近访问的顺序返回键值对。这种机制可以用于实现缓存等功能,其中最近访问的元素更有可能再次被访问。

18. ConcurrentHashMap在JDK 8中的实现有哪些变化?

参考答案
在JDK 8中,ConcurrentHashMap的实现发生了较大的变化,主要包括以下几个方面:

  • 数据结构:在JDK 7及之前,ConcurrentHashMap使用分段锁(Segment Lock)来提高并发性能。每个段都是一个独立的哈希表,并且有自己的锁。而在JDK 8中,ConcurrentHashMap不再使用分段锁技术,而是使用了更加细粒度的锁(如CAS操作和synchronized块)来实现线程安全。这种变化使得ConcurrentHashMap在性能方面有了更大的提升,并且更加适用于高并发场景。
  • 节点结构:在JDK 8中,ConcurrentHashMap的节点结构也发生了变化。它引入了红黑树(在链表长度超过阈值时)来优化哈希冲突的处理。当桶中的链表长度过长时,ConcurrentHashMap会将链表转换为红黑树,以提高查找效率。这种优化在高度冲突的哈希表中特别有效。
  • 扩容机制:在JDK 8中,ConcurrentHashMap的扩容机制也进行了优化。与JDK 7中的一次性扩容不同,JDK 8中的ConcurrentHashMap支持渐进式扩容。这意味着扩容操作不会一次性完成,而是分散在多个插入操作中逐渐进行。这种机制可以减少扩容时对其他操作的影响,提高并发性能。
  • 并发控制:在JDK 8中,ConcurrentHashMap使用了更加细粒度的并发控制。它结合了CAS(Compare-And-Swap)操作和synchronized块来实现线程安全。对于大多数读操作,如get,是不需要加锁的,这大大提高了读取性能。而对于写操作,如put,则使用synchronized块或CAS操作来保证线程安全。这种细粒度的并发控制使得ConcurrentHashMap在高并发场景下具有更好的性能。
  • 弱一致性:与JDK 7及之前的版本相比,JDK 8中的ConcurrentHashMap在迭代时提供了弱一致性保证。这意味着在迭代过程中,即使其他线程对ConcurrentHashMap进行了修改,迭代器也不会抛出ConcurrentModificationException。相反,它会尽可能地反映出迭代过程中的修改。这种弱一致性保证使得ConcurrentHashMap更加适用于并发迭代场景。
  • 性能提升:由于上述优化,JDK 8中的ConcurrentHashMap在性能上有了显著提升。它不仅在单线程环境下表现优异,而且在高并发场景下也表现出色。这使得ConcurrentHashMap成为处理高并发数据的首选数据结构之一。

http://www.kler.cn/a/428141.html

相关文章:

  • 软件工程概论试题三
  • 【安全测试】测开方向学习遇到的问题记录
  • SSM-MyBatis-总结
  • 为大模型提供webui界面的利器:Open WebUI 完全本地离线部署deepseek r1
  • PHP 7 新特性
  • 基于Langchain-Chatchat + ChatGLM 本地部署知识库
  • 光伏组件的度电成本如何降低?
  • 解决 Maven 部署中的 Artifact 覆盖问题:实战经验分享20241204
  • Docker--Docker Container(容器)
  • Android显示系统(03)- OpenGL ES - GLSurfaceView的使用
  • Android 调用手机相册,相机功能实现
  • 零基础学安全--Burp Suite验证码识别以及爆破
  • 记一次由docker容器使得服务器cpu占满密码和密钥无法访问bug
  • 基于 NXP S32K312+FS23 的汽车通用评估板方案
  • ADBC 查询语法介绍:EXECUTE_QUERY
  • 基于SpringBoot框架自习室在线预定管理系统(计算机毕业设计)
  • 电子公文交换系统设计 ——基于商用密码标准的密码模块的应用
  • 释放 AI 潜能:掌握提问策略,让 AI 事半功倍
  • 单片机软件工程师前景分析
  • 攻防世界39-bug-CTFWeb
  • HarmonyOS NEXT的Navigation,跳转子页面后底部Tab不隐藏问题解决
  • 12.08Java
  • 跟李笑来学美式俚语(Most Common American Idioms): Part 54
  • 15.数据容器-字典dict
  • 在玩《黑神话:悟空》时游戏画面卡顿是什么原因?游戏画面卡顿要怎么解决?
  • Rust快速入门(四)