👀个人主页: Java李小立
后面会持续更新java面试专栏,请持续关注
如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连❤️❤️❤️)
面试宝典列表(持续更新):
序号 | 内容 | 链接地址 |
---|---|---|
1 | Java基础篇 | (点击跳转)java面试宝典-基础篇 |
2 | Java集合框架篇 | (点击跳转)java面试宝典-集合框架篇 |
3 | Java多线程篇 | (点击跳转)java面试宝典- 多线程篇 |
4 | JVM篇 | 待分享 |
5 | Spring篇 | 待分享 |
6 | Mybatis篇 | 待分享 |
7 | SpringcCloud篇 | 待分享 |
8 | Redis篇 | 待分享 |
9 | Mysql篇 | 待分享 |
10 | dubbo篇 | 待分享 |
11 | zookeeper篇 | 待分享 |
12 | kafka篇 | 待分享 |
13 | RocketMq篇 | 待分享 |
14 | Nacos篇 | 待分享 |
(一)集合框架知识点
首先附两张图展现集合框架整体
collection:
Map:
1.1. List,Set,Map三者的区别及总结
- List:对付顺序的好帮手
List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
- Set:注重独一无二的性质
不允许重复的集合。不会有多个元素引用相同的对象。
- Map:用Key来搜索的专家
使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。
1.2. Arraylist 与 LinkedList 区别
Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低)。
LinkedList底层使用的是双向循环链表数据结构(插入,删除效率特别高)。
学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。
1.3.ArrayList 与 Vector 区别
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector ,代码要在同步操作上耗费大量的时间。Arraylist不是同步的,所以在不需要同步时建议使用Arraylist。
1.4. HashMap 和 Hashtable 的区别
- HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰。
- 因为线程安全的问题,HashMap要比HashTable效率高一点,HashTable基本被淘汰。
- HashMap允许有null值的存在,而在HashTable中put进的键值只要有一个null,直接抛出NullPointerException。
1.5. HashSet 和 HashMap 区别
1.6.HashMap 和 ConcurrentHashMap 的区别
- ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
- HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。
1.7. HashSet如何检查重复
当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。
1.8. comparable 和 comparator的区别
- comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
- comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序
一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort().
1.9. Collection包结构,与Collections的区别
Collection是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、
Set;
Collections是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种
集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的
Collection框架。
1.10. 如何对Object的list排序
1.对objects数组进行排序,我们可以用Arrays.sort()方法
2.对objects的集合进行排序,需要使用Collections.sort()方法
1.11. 如何实现数组与List的相互转换
List转数组: list.toArray(arraylist.size())方法;
数组转List: Arrays的asList(arr)方法
1.12. 集合框架底层数据结构总结
Collection
- List
Arraylist:数组(查询快,增删慢 线程不安全,效率高 )
Vector:数组(查询快,增删慢 线程安全,效率低 )
LinkedList:链表(查询慢,增删快 线程不安全,效率高 ) - Set
HashSet(无序,唯一):哈希表或者叫散列集(hash table)
LinkedHashSet:链表和哈希表组成 。 由链表保证元素的排序 , 由哈希表证元素的唯一性
TreeSet(有序,唯一):红黑树(自平衡的排序二叉树。)
Map
3. HashMap:基于哈希表的Map接口实现(哈希表对键进行散列,Map结构即映射表存放键值对)
4. LinkedHashMap:HashMap 的基础上加上了链表数据结构
5. HashTable:哈希表
6. TreeMap:红黑树(自平衡的排序二叉树)
(二)集合框架面试题
2.1. 详细谈一下HashMap
这个能说多少说多少,很大概率必问,推荐百度一下
java7的实现
HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个蓝色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
- capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
- loadFactor:负载因子,默认为 0.75。
- threshold:扩容的阈值,等于 capacity * loadFactor
java8的实现
Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树组成。
根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。
2.2. HashMap 中的 key 我们可以使用任何类作为 key 吗?
平时可能大家使用的最多的就是使用 String 作为 HashMap 的 key,但是现在我们想使用某个自定义类作为 HashMap 的 key,那就需要注意以下几点:
- 如果类重写了 equals 方法,它也应该重写 hashCode 方法。
- 类的所有实例需要遵循与 equals 和 hashCode 相关的规则。
- 如果一个类没有使用 equals,你不应该在 hashCode 中使用它。
- 咱们自定义 key 类的最佳实践是使之为不可变的,这样,hashCode 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode 和 equals 在未来不会改变,这样就会解决与可变相关的问题了。
2.3. HashMap 的长度为什么是 2 的 N 次方呢?
为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,也就是说尽量把数据能均匀的分配,每个链表或者红黑树长度尽量相等。
我们首先可能会想到 % 取模的操作来实现。
取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作(也就是说hash % length == hash &(length - 1) 的前提是 length 是 2 的 n 次方)。并且,采用二进制位操作 & ,相对于 % 能够提高运算效率
这就是为什么 HashMap 的长度需要 2 的 N 次方了。
2.4. 说说Hashtable 与 HashMap 的区别
虽然Hashtable很少使用,但面试官还是喜欢这样问,无奈小立给大家还是总结一下。
- 出生的版本不一样,Hashtable 出生于 Java 发布的第一版本 JDK 1.0,HashMap 出生于 JDK1.2。
- 都实现了 Map、Cloneable、Serializable(当前 JDK 版本 1.8)。
- HashMap 继承的是 AbstractMap,并且 AbstractMap 也实现了 Map 接口。Hashtable 继承Dictionary。
- Hashtable 中大部分 public 修饰普通方法都是 synchronized 字段修饰的,是线程安全的,HashMap 是非线程安全的。
- Hashtable 的 key 不能为 null,value 也不能为 null,这个可以从 Hashtable 源码中的 put 方法看到,判断如果 value 为 null 就直接抛出空指针异常,在 put 方法中计算 key 的 hash 值之前并没有判断 key 为 null 的情况,那说明,这时候如果 key 为空,照样会抛出空指针异常。
- HashMap 的 key 和 value 都可以为 null。在计算 hash 值的时候,有判断,如果key==null ,则其 hash=0 ;至于 value 是否为 null,根本没有判断过。
- Hashtable 直接使用对象的 hash 值。hash 值是 JDK 根据对象的地址或者字符串或者数字算出来的 int 类型的数值。然后再使用除留余数法来获得最终的位置。然而除法运算是非常耗费时间的,效率很低。HashMap 为了提高计算效率,将哈希表的大小固定为了 2 的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
- Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了Enumeration 的方式。
- 默认情况下,初始容量不同,Hashtable 的初始长度是 11,之后每次扩充容量变为之前的2n+1(n 为上一次的长度)而 HashMap 的初始长度为 16,之后每次扩充变为原来的两倍。
这个回答完了,面试官可能会继续问:HashMap 是线程不安全的,那么在需要线程安全的情况下
还要考虑性能,有什么解决方式?
这里最好的选择就是 ConcurrentHashMap 了,但面试官肯定会叫你继续说一下ConcurrentHashMap 数据结构以及底层原理等。
2.5. 说一下ConcurrentHashMap
Segment 段
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁。
线程安全
Segment 继承 ReentrantLock 加锁 简单理解就是ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
java7的实现
并行度(默认 16)
concurrencyLevel:并行级别、并发数、Segment 数,怎么翻译不重要,理解它。默认是 16,也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以处理起来要麻烦些。
java8的实现
采用 Node + CAS + Synchronized来保证并发安全进行实现。
2.6. HashMap 与 ConcurrentHashMap 的异同
- 都是 key-value 形式的存储数据;
- HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的;
- HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑树。当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红黑树查询速度快;
- HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 * 数组大小的方式进行扩容;
- ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry,Segment 数组大小默认是 16,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized来保证并发安全进行实现。
2.7. 红黑树有哪几个特征?
面试官问道HashMap很大概率又会继续问红黑树
2.8. 说说List,Set,Map三者的区别?
- List(对付顺序的好帮手): List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
- Set(注重独一无二的性质): 不允许重复的集合。不会有多个元素引用相同的对象。
- Map(用Key来搜索的专家):使用键值对存储。 Map会维护与Key有关联的值。两个Key可以引 用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。
2.9. ArrayList 和 LinkedList 的区别有哪些?
ArrayList
- 优点:ArrayList 是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询 操作效率会比较高(在内存里是连着放的)。
- 缺点:因为地址连续,ArrayList 要移动数据,所以插入和删除操作效率比较低。
LinkedList
- 优点:LinkedList 基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址。对于新增和删除操作,LinkedList 比较占优势。LinkedList 适用于要头尾操 作或插入指定位置的场景。
- 缺点:因为 LinkedList 要移动指针,所以查询操作性能比较低。
适用场景分析
- 当需要对数据进行对随机访问的时候,选用 ArrayList。
- 当需要对数据进行多次增加删除修改时,采用 LinkedList。
如果容量固定,并且只会添加到尾部,不会引起扩容,优先采用 ArrayList。
当然,绝大数业务的场景下,使用 ArrayList 就够了,但需要注意避免 ArrayList 的扩容,以及非顺序的插入
2.10. 有数组了为什么还要搞个 ArrayList 呢?
通常我们在使用的时候,如果在不明确要插入多少数据的情况下,普通数组就很尴尬了,因为你不知道需要初始化数组大小为多少,而 ArrayList 可以使用默认的大小,当元素个数到达一定程度后,会自动扩容。可以这么来理解:我们常说的数组是定死的数组,ArrayList 却是动态数组。
2.11. 用过 ArrayList 吗?说一下它有什么特点?
ArrayList是Java 集合框架中的一种存放相同类型的元素数据,是一种变长的集合类,基于定长数组实现,当加入数据达到一定程度后,会实行自动扩容,即扩大数组大小。
底层是使用数组实现,添加元素。
- 如果 add(o),添加到的是数组的尾部,如果要增加的数据量很大,应该使用 ensureCapacity()方法,该方法的作用是预先设置 ArrayList 的大小,这样可以大大提高初始化速度。
- 如果使用 add(int,o),添加到某个位置,那么可能会挪动大量的数组元素,并且可能会触发扩容机制。
高并发的情况下,线程不安全。多个线程同时操作 ArrayList,会引发不可预知的异常或错误。
ArrayList 实现了 Cloneable 接口,标识着它可以被复制。注意:ArrayList 里面的 clone() 复制其实是浅复制。
版权声明:本文为博主原创文章,未经博主允许不得转载
https://blog.csdn.net/qq_44614710/article/details/113694683