Java集合 高频面试题(1)

  • 3 扩容机制

    • 3.1 ArrayList 的扩容机制
  • 3.2 LinkedList 的扩容机制

  • 3.3 HashMap 的扩容机制

  • 4 HashMap 的相关问题?

    • 4.1 HashMap 的底层实现
  • 4.2 为什么要改成“数组+链表+红黑树”

  • 4.3 面试回答

  • 5 HashSet 的相关问题

    • 5.1 HashSet 的底层实现
  • 5.2 HashSet 如何检查重复

  • 6 ConcurrentHashMap 的相关问题

    • 6.1 ConcurrentHashMap 的底层具体实现
  • 6.2 面试回答

  • 7 相似集合的异同

    • 7.1 ArrayList 和 LinkedList 的异同
  • 7.2 ArrayList 和 Vector 的异同

  • 7.3 HashMap 和 Hashtable 的异同

  • 7.4 HashMap 和 ConcurrentHashMap 的异同

  • 7.5 ConcurrentHashMap 和 Hashtable 的异同

  • 7.6 HashMap 和 HashSet 的异同

  • 7.7 TreeSet 和 HashSet 的异同

1 Java 集合概论

==============================================================================

数组(Array)和集合都是对多个数据进行存储操作的结构,也简称为 Java 容器。此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(文件或数据库)。

然而目前数组(Array)在存储数据方面存在以下特点:

  1. 数组初始化以后,长度就不可变了,不便于扩展;

  2. 数组声明的类型,就决定了进行元素初始化时的类型;

  3. 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,同时无法直接获取存储元素的个数;

  4. 数组存储的数据是有序的、可以重复的。 存储数据的特点单一;

当然,上述有些特点也可以被当作优点,然而在实际操作中,由于数组的不够灵活,所以我们需要其它的方式来存储数据,Java 集合类就这样应运而生了。

Java 集合类中有可以用于存储数量不确定的多个对象的集合(Collection 接口),还有可用于保存具有映射关系的关联数组的集合(Map 接口)。


  • Collection 接口:单列数据,用于存储数量不确定的多个对象 ;

  • List接口:元素有序、可重复的集合(对应“动态”数组),即集合中的每个元素都有其对应的顺序索引,可以根据序号存取容器中的元素。

  • ArrayList 实现类:作为 List 接口的主要实现类,它是线程不安全的,但效率高;适⽤于频繁的查找⼯作。

  • LinkedList 实现类:使用 LinkedList 实现类对于频繁的插入、删除操作的效率比 ArrayList 高;

  • Vector 实现类:作为 List 接口的古老实现类,它是线程安全的,但效率低;

  • Set 接口:元素无序、不可重复的集合(对应高中讲的“集合”)

  • HashSet 实现类:作为 Set 接口的主要实现类,是线程不安全的;可以存储 null 值。

  • LinkedHashSet 实现类:是 HashSet 的子类。遍历其内部数据时,可以按照添加的顺序遍历,使其看起来是有序的。

  • TreeSet 实现类:底层使⽤红⿊树,TreeSet可以确保集合元素处于排序状态;排序方法分为自然排序和定制排序,自然排序指的是根据自然规律排序,比如数字的话就按照从小到大排序,定制排序指的是根据自己既定的规则进行排序。

  • Map 接口:双列数据,保存具有映射关系 “key-value对” 的集合

  • HashMap 实现类:作为Map的主要实现类,它是线程不安全的,但效率高。可以存储 null 值的 key 和 value。

  • LinkedHashMap 实现类:保证在遍历 map 元素时,可以按照添加的顺序实现遍历。

  • ConcurrentHashMap 实现类:线程安全的 Map;

  • TreeMap 实现类:底层使用红黑树,保证按照添加的 key-value 对进行排序,实现排序遍历。

  • Hashtable 实现类:古老的实现类,它是线程安全的,但效率低;不能存储 null 的 key 和 value。

  • Properties 实现类:常用来处理配置文件,key 和 value 都是 String 类型。

Java中的 List、Set、Map 之类的集合容器中只能存放引用类型,而不能存放类似于 int、double 之类的基本类型。

参考文献:【传送门

2 如何选用集合

===========================================================================

如果我们需要根据键值获取元素值就选用 Map 接口下的集合,当需要排序时就选择 TreeMap,不需要排序时就选择 HashMap,需要保证线程安全就选用 ConcurrentHashMap;

如果我们只需要存放元素时,就选择实现 Collection 接口的集合,此时如果需要保证元素唯一就选择实现 Set 接口的集合,如 TreeSet 或者 HashSet,此时如果需要排序就只选择 TreeSet;

当确定选择实现 Collection 接口的集合后,如果不需要保证元素唯一,就选择实现 List 接⼝的 ArrayList 或 LinkedList 等集合,此时如果需要线程安全的话,就只能选择 Vector,当不需要保证线程安全时,该集合需要频繁的删除和插入,就选择 LinkedList,需要频繁的查找就选择 ArrayList;

3 扩容机制

=========================================================================

3.1 ArrayList 的扩容机制


ArrayList 底层的数据结构是动态数组,其扩容情况可以分为两种情况:

第一种情况,当 ArrayList 的容量为 0 时,此时添加元素的话,需要扩容,三种构造方法创建的 ArrayList 在扩容时略有不同:

  1. 无参构造,创建 ArrayList 后容量为 0,添加第一个元素后,容量变为 10,此后若需要扩容,则按照 1.5 倍进行扩容;

  2. 当传容量,参数为 0 或者传列表,列表为空构造时,创建 ArrayList 后容量为 0,添加第一个元素后,容量为 1,此时 ArrayList 是满的,下次添加元素时则按照 1.5 倍进行扩容;

第二种情况,当 ArrayList 的容量大于 0,并且 ArrayList 是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的 1.5 倍。

ArrayList 没有缩容。无论是remove方法还是clear方法,它们都不会改变现有数组elementData的长度。但是它们都会把相应位置的元素设置为null,以便垃圾收集器回收掉不使用的元素,节省内存。

3.2 LinkedList 的扩容机制


LinkedList 的底层是双向链表,所以不需要主动扩容。ArrayList 的底层是动态数组,所以当数组的内存不足时,需要主动扩容。

3.3 HashMap 的扩容机制


HashMap 的扩容机制分为两种情况:

第一种情况,无参构造时,HashMap 的默认初始容量是16,之后每次扩充,容量变为原来的 2 倍;

第二种情况,有参构造时,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的 2 的 N 次方,例如传 9,容量为16。之后每次扩充,容量变为原来的 2 倍。

为什么HashMap 的容量大小永远是 2 的 N 次方呢?

**为了能让 HashMap 存取⾼效,尽量减少哈希碰撞,也就是要尽量把数据分配均匀。**我们上⾯也讲过了,Hash 值的范围值-2147483648到2147483647,前后加起来⼤概40亿的映射空间,只要哈希函数映射得⽐较均匀松散,⼀般应⽤是很难出现碰撞的。但问题是⼀个40亿⻓度的数组,内存是放不下的。所以这个散列值是不能直接拿来⽤的。⽤之前还要先做对数组的⻓度取模运算,得到的余数才能⽤来要存放的位置也就是对应的数组下标。

我们⾸先可能会想到采⽤ % 取余的操作来实现。但是,重点来了:“如果除数是2的幂次,那么取余(%)操作则等价于与其被除数减⼀的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次⽅;)。” 并且 采⽤⼆进制位操作 &,相对于 % 能够提⾼运算效率,这就解释了 HashMap 的⻓度为什么是 2 的幂次⽅。

4 HashMap 的相关问题?

===================================================================================

4.1 HashMap 的底层实现


JDK1.8 之前

此时 HashMap 的底层实现是数组+链表的形式,HashMap 通过 key 的 hashCode() 经过扰动函数处理过后得到 hash 值,然后利用公式((n - 1) & hash)判断当前元素存放的位置,如果当前位置存在元素的话,就判断该元素与要存⼊的元素的 hash 值以及 key 值是否相同,如果均相同则直接覆盖,如果值不同,则通过拉链法来解决哈希冲突。

所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建⼀个链表数组,数组中每⼀格就是⼀个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

JDK1.8 及之后

对于插入,相比于之前的版本, JDK1.8 在解决哈希冲突时有了较⼤的变化,当链表长度大于阈值(默认为 8)(将链表转换成红⿊树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间。

对于移除,当同一个索引位置的节点在移除后小于 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点。

4.2 为什么要改成“数组+链表+红黑树”


主要是为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n)(线性级),而使用红黑树是 O(logn)(对数级)。

4.3 面试回答


请问 HashMap 的底层实现是怎么样的?

在 JDK1.8 之前的话,HashMap 的底层是通过数组+链表的形式来实现的,在 JDK1.8 及之后的话,则是通过数组+链表/红黑树的形式来实现,这两者的区别在于,当哈希冲突严重时,后者会将链表转换成红黑树来存储,从而提升查找性能。

2 什么是哈希冲突

因为 HashMap 在存储数据时是根据键的哈希值所对应的位置来存放的,然后可能存在键不同,对应的位置相同的情况,这种情况便是哈希冲突,在实际中,常采用拉链法来解决,拉链法指的是将链表和数组相结合,数组中每⼀格就是⼀个链表,若遇到哈希冲突,则将冲突的值加到链表后即可。

3 什么是红黑树,红黑树如何进行自调整,与哈希表的区别

红黑树是一种可以自平衡的二叉查找树。(两头乌,路径上不能有两个连续的红色节点)

红黑树通过变色、左旋转和右旋转来保持平衡。

与哈希表的区别:

  1. 在增改查性能上,红黑树的性能是稳定且高效的,而哈希表当哈希碰撞严重时,时间复杂度会达到线性级;

  2. 红黑树是有序的,哈希表是无序的;

  3. 相对来说,红黑树占用的内存更小。

5 HashSet 的相关问题

==================================================================================

5.1 HashSet 的底层实现


HashSet 的底层实际上是通过 HashMap 来实现的,除了实现 Set 接口所规范的方法外,其它方法都是直接调用 HashMap,HashSet 将对应元素存放在 HashMap 的键上,与此相对应的值存放了一个静态的 final 修饰的 Object 对象。

5.2 HashSet 如何检查重复


当把对象加入到 HashSet 中时,HashSet 会比较该对象的哈希值是否和 HashSet 中已经存在的对象的哈希值相等,如果不相等则直接加入;如果相等,则调用 equals() 方法来检查这两个哈希值相等的对象是否真的相同,如果两者相同,HashSet 就不会让其加⼊操作成功。

6 ConcurrentHashMap 的相关问题

============================================================================================

6.1 ConcurrentHashMap 的底层具体实现


独家面经总结,超级精彩

本人面试腾讯,阿里,百度等企业总结下来的面试经历,都是真实的,分享给大家!

image

image

image

image

Java面试准备

准确的说这里又分为两部分:

  1. Java刷题
  2. 算法刷题

Java刷题:此份文档详细记录了千道面试题与详解;

image

image

百度等企业总结下来的面试经历,都是真实的,分享给大家!

[外链图片转存中…(img-xeV5bkTK-1714395187047)]

[外链图片转存中…(img-ANMvS6kg-1714395187047)]

[外链图片转存中…(img-AKRKWx1V-1714395187048)]

[外链图片转存中…(img-3XHDZ7l2-1714395187048)]

Java面试准备

准确的说这里又分为两部分:

  1. Java刷题
  2. 算法刷题

Java刷题:此份文档详细记录了千道面试题与详解;

[外链图片转存中…(img-iiqdnzgT-1714395187048)]

[外链图片转存中…(img-KSGHmjks-1714395187049)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值