硬核!Java集合面经大全——双非上岸阿里巴巴系列
Posted 来老铁干了这碗代码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了硬核!Java集合面经大全——双非上岸阿里巴巴系列相关的知识,希望对你有一定的参考价值。
东北某不知名双非本,四面成功上岸阿里巴巴,在这里把自己整理的面经分享出来,欢迎大家阅读。
序号 | 文章名 | 超链接 |
---|---|---|
1 | 操作系统面经大全——双非上岸阿里巴巴系列 | 2021最新版面经——>传送门1 |
2 | 计算机网络面经大全——双非上岸阿里巴巴系列 | 2021最新版面经——>传送门2 |
3 | Java并发编程面经大全——双非上岸阿里巴巴系列 | 2021最新版面经——>传送门3 |
4 | Java虚拟机(JVM)面经大全——双非上岸阿里巴巴系列 | 2021最新版面经——>传送门4 |
5 | 面试阿里,你必须知道的背景知识——双非上岸阿里巴巴系列 | 2021最新版面经——>传送门5 |
本博客内容持续维护,如有改进之处,还望各位大佬指出,感激不尽!
文章目录
- Java集合
- 集合框架概述
- Collection接口
- List接口
- Queue
- Set接口
- Map接口
- HashMap的put方法的具体流程?
- HashMap的扩容操作是怎么实现的?
- 解决哈希冲突:
- 能否使用任何类作为 Map 的 key?
- 为什么HashMap中String、Integer这样的包装类适合作为K?
- 6 HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
- HashMap 的长度为什么是2的幂次方
- 为什么是两次扰动呢?
- HashMap 与 HashTable 有什么区别?
- 如何决定使用 HashMap 还是 TreeMap?
- HashMap 和 ConcurrentHashMap 的区别
- ConcurrentHashMap 和 Hashtable 的区别?
- HashMap闭环(死循环)
- 辅助工具类
Java集合
集合框架概述
1 集合和数组的区别
- 数组是固定长度的;集合可变长度的。
- 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
- 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
2 使用集合框架的好处
- 容量自增长;
- 提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;
- 允许不同 API 之间的互操作,API之间可以来回传递集合;
- 可以方便地扩展或改写集合,提高代码复用性和可操作性。
- 通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。
常用的集合类有哪些?
Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
*集合框架图
Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List,Map
-
List:有序容器
- 常用:ArrayList、LinkedList、Vector
-
Set:无序容器,去重。
- HashSet、LinkedHashSet、TreeSet
-
Map:键值对集合,Key无序唯一。
- HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap
集合框架底层数据结构
Collection
List
- Arraylist: Object数组
- Vector: Object数组
- LinkedList: 双向循环链表
Set
- HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
- LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
- TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
Map
- HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
- LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
- TreeMap: 红黑树(自平衡的排序二叉树)
- HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
哪些集合类是线程安全的?
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
stack:堆栈类,先进后出。
hashtable:就比hashmap多了个线程安全。
enumeration:枚举,相当于迭代器。
*Java集合的快速失败机制 “fail-fast”?
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,若线程2修改了集合A的结构,就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
解决办法:
-
在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。
-
使用CopyOnWriteArrayList(并发容器)来替换ArrayList
怎么确保一个集合不能被修改?
可以使用 Collections. unmodifiableCollection(Collection c)(不可修改的集合) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。
Collection接口
List接口
Iterator 怎么使用?有什么特点?
Iterator 使用代码如下:
List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
特点:只能单向遍历,但是非常安全。
遍历List方式
*遍历方式有以下几种:
-
for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
-
迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。
-
foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。
最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。
如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。
如果没有实现该接口,表示不支持 Random Access,如LinkedList。
推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。
如何实现数组和 List 之间的转换?
- 数组转 List:使用 Arrays. asList(array) 进行转换。
- List 转数组:使用 List 自带的 toArray() 方法。
*ArrayList 和 Vector 的区别是什么?
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合
- 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
- 性能:ArrayList 在性能方面要优于 Vector。
- 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。
* 为什么ArrayList扩容是1.5倍
1、合适的扩容范围是1-2倍。若扩容容量太大,会频繁扩容,导致频繁申请内存空间。若扩容容量太大,则不能充分利用空间,造成浪费
2、取1.5的原因是因为1.5可以充分利用移位操作
max_size = max_size + (max_size >> 1)
多线程场景下如何使用 ArrayList?
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样:
为什么 ArrayList 的 elementData 加上 transient 修饰?
- 序列化:把对象转换为字节序列
- 反序列化:字节序列恢复为对象的过程
- 序列化作用
- 当想把内存中对象状态保存在一个文件或数据库中时
- 想用套接字在网络上传送对象时
- 想通过RMI传输对象时
- 如何实现序列化:添加Serializable接口
- transient属性:他修饰的属性不会被序列化
- 为什么要加:因为elementData中有很多空元素,即未存入的元素,当对ArrayList序列化时,先调用defaultWriteObject()方法序列化ArrayList中非transient元素,然后遍历elementData,只序列化已经存入的元素,加快速度,提高效率
List 和 Set 的区别
相同点:List , Set 都是继承自Collection 接口
不同点:一个有序,一个无序去重,一个支持for和迭代器,一个只支持迭代器。
Set:检索效率低,插入删除效率高。
List:顺序添加or删除效率高,遍历效率高。
Queue
BlockingQueue
概念:阻塞队列,生产者-消费者模式,更安全
移除元素:若队列为空,则该线程阻塞等待,直到有数据被存入,会唤醒进程,进行操作
添加元素:若队列为full,则进程阻塞,直到队列中有空间,进程被唤醒并操作
在 Queue 中 poll()和 remove()有什么区别?
- 相同点:都是返回第一个元素,并在队列中删除返回的对象。
- 不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
Set接口
HashSet 的实现原理?
HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。
HashSet && HashMap
实现原理:数组+链表
查找元素:先hashcode再queal
如何保证不可重复:覆盖
如何添加元素:拉链法(JDK1.8前)、顺序查找法和平方探测法(重写)、红黑树(JDK1.8及之后)
HashMap与HashSet: HashMap快一点,并且存储的是键值对,而HashSet慢一些,因为存储的是对象
JDK1.8以后的红黑树的实现:当链表长度大于阈值(8)但是数组长度小于64时,首先进行扩容,若不够,则转化为红黑树,减少搜索时间。
1.7与1.8的比较
-
resize扩容优化(将初始化集成到了resize中)
-
1.7为头插法,1.8为尾插法(兼容红黑树)
HashMap的put方法流程?
<< >> 与 >>>
> >
- 对应二进制码整体右移,左边用标志位(正负位)补充,右边超出部分舍弃(要用补码操作)
-5>>1=-3:1111 1011——>1111 1101(右移一位的补码)——>1000 0011(转为原码)——>-3(十进制)
>>>
- 右移,左边用0补充,右边超出部分舍弃
-3>>>1 = 2147483646
原码、反码、补码
原码转补码:取反+1
补码转原码:取反+1
Map接口
HashMap的put方法的具体流程?
将key的hashcode值(由native方法计算得到)再与该值的高16位进行异或运算得到最终的hash值。(扰动函数)
HashMap的扩容操作是怎么实现的?
①.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;
②.每次扩展的时候,都是扩展2倍;
③.扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。
解决哈希冲突:
- 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
- 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
- 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;
能否使用任何类作为 Map 的 key?
可以使用任何类作为 Map 的 key,然而在使用之前,需要考重写equals()和hashCode()或只重写equals()
为什么HashMap中String、Integer这样的包装类适合作为K?
答:String、Integer等包装类不可更改(final、内部重写了euqal等)减少Hash碰撞的几率
6 HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
答:hashCode()
方法返回的是int整数类型,没有这么多存储空间(大约40e)
解决办法:两次扰动,区域or&
HashMap 的长度为什么是2的幂次方
取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)
而&比取余的效率高很多,因此为了提高效率,HashMap的长度为2的幂次方
为什么是两次扰动呢?
答:这样就是加大哈希值低位的随机性,使得分布更均匀,两次就够了,已经达到了高位低位同时参与运算的目的;
HashMap 与 HashTable 有什么区别?
- 线程安全和效率:HashTable内部实现了同步锁
- HashMap中允许有一个null的key键,而HashTable若有null的key,会抛异常
- HashTable默认初始大小为11,每次扩充扩大2n+1倍。Has和Map默认初始大小位16,每次扩充扩大2倍
- 底层数据结构:JDK1.8后,HashMap中加入红黑树结构,而Hashtable中无。
如何决定使用 HashMap 还是 TreeMap?
插入、删除、定位等选HashMap
遍历选TreeMap
HashMap 和 ConcurrentHashMap 的区别
Concurrent:并发
- ConcurrentHashMap对整个桶进行了分割分段。每个分段用lock锁进行保护,相对于HashTable的锁,粒度更小(参考锁优化)
- HashMap键值对允许有null,但ConCurrentHashMap不允许
ConcurrentHashMap 和 Hashtable 的区别?
- 底层数据结构:1.8前和后
- 实现线程安全的方式:1.8前分段锁(16段)。1.8后,Node数组(只锁定当前链表or红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率提升n倍)
HashMap闭环(死循环)
原因:1.7的HashMap在扩容复制时,采用头插法,导致数组中的链表反转,即正向->反向。
而多线程下,由于可见性的保证,其他先的扩容复制操作,会使反向的链表变成正向。
这样当前线程复制过程中会既有正向链表、又有fan向链表,从而产生闭环。
辅助工具类
comparable 和 comparator的区别?
comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序
自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式
Collection 和 Collections 有什么区别?
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。
Collections 工具类的 sort 方法有两种重载的形式,
第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;
第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。
以上是关于硬核!Java集合面经大全——双非上岸阿里巴巴系列的主要内容,如果未能解决你的问题,请参考以下文章