将 脚本之家 设为“星标⭐”
第一时间收到文章更新
本文经JavaGuide(id:JavaGuide)授权转载
理想汽车 25 届的校招薪资,Base 还是非常高的:
-
后端:28K*16,北京,硕士 985
-
算法:40K*16,杭州,硕士 985
-
大模型:42K*16,北京,海归 985
-
嵌入式:32K*16,上海,硕士 985
-
安全工程师:32K*14,杭州,硕士 211
下面,分享一位北京工业大学的同学面试理想汽车 Java 岗位的一面面经,大家感受一下难度如何。我会对其中涉及到的面试题添加详细的参考答案。
概览:
多提一嘴:理想的后端面试一般考察纯粹的八股较少,重点是考察项目,结合项目提问。不过,这个也要看具体的面试官,像这位北工大同学的面试,常规八股就比较多。
自我介绍
一个好的自我介绍应该包含这几点要素:
用简单的话说清楚自己主要的技术栈于擅长的领域,例如 Java 后端开发、分布式系统开发;
把重点放在自己的优势上,重点突出自己的能力比如自己的定位的 bug 的能力特别厉害;
避免避实就虚,适当举例体现自己的能力,例如过往的比赛经历、实习经历;
自我介绍的时间不宜过长,一般是 1~2 分钟之间。
介绍一下你的实习经历
如果你有实习经历的话,自我介绍之后,第二个问题一般就是聊你的实习经历。面试之前,一定要提前准备好对应的话术,突出介绍自己实习期间的贡献。
很多同学实习期间可能接触不到什么实际的开发任务,大部分时间可能都是在熟悉和维护项目。对于这种情况,你可以适当润色这段实习经历,找一些简单的功能研究透,包装成自己参与做的,大部分同学都是这么做的。不用担心面试的时候会露馅,只要不挑选那种明显不会交给实习生做的任务,你自己也能讲明白就行了。不过,还是更建议你在实习期间尽量尝试主动去承担一些开发任务,这样整个实习经历对个人提升也会更大一些。
项目拷打
项目就拷打了几个问题,主要是缓存 Redis 相关的,难度较低。一面面试官更多的是在八股拷打,并发编程问了很多问题。有很多我都忘了,回答的不是很好。
作为求职者,我们可以从这些方案去准备项目经历的回答:
你对项目基本情况(比如项目背景、核心功能)以及整体设计(比如技术栈、系统架构)的了解(面试官可能会让你画系统的架构图、让你讲解某个模块或功能的数据库表设计)
你在这个项目中你担任了什么角色?负责了什么?有什么贡献?(具体说明你在项目中的职责和贡献)
你在这个项目中是否解决过什么问题?怎么解决的?收获了什么?(展现解决问题的能力)
你在这个项目用到了哪些技术?这些技术你吃透了没有?(举个例子,你的项目经历使用了 Seata 来做分布式事务,那 Seata 相关的问题你要提前准备一下吧,比如说 Seata 支持哪些配置中心、Seata 的事务分组是怎么做的、Seata 支持哪些事务模式,怎么选择?)
你在这个项目中犯过的错误,最后是怎么弥补的?(承认不足并改进才能走的更远)
从这个项目中你学会了那些东西?学会了那些新技术的使用?(总结你在这个项目中的收获)
项目出现过 Maven 依赖冲突吗?怎么解决的?
单纯依赖 Maven 来进行依赖调解,在很多情况下是不适用的,需要我们手动排除依赖。
举个例子,当前项目存在下面这样的依赖关系:
依赖链路一:A -> B -> C -> X(1.5) // dist = 3依赖链路二:A -> D -> X(1.0) // dist = 2
根据路径最短优先原则,X(1.0) 会被解析使用,也就是说实际用的是 1.0 版本的 X。
但是!!!这会一些问题:如果 C 依赖用到了 1.5 版本的 X 中才有的一个类,运行项目就会报NoClassDefFoundError错误。如果 C 依赖用到了 1.5 版本的 X 中才有的一个方法,运行项目就会报NoSuchMethodError错误。
现在知道为什么你的 Maven 项目总是会报NoClassDefFoundError和NoSuchMethodError错误了吧?
如何解决呢? 我们可以通过exclusion标签手动将 X(1.0) 给排除。
<dependency> ...... <exclusions> <exclusion> <artifactId>x</artifactId> <groupId>org.apache.x</groupId> </exclusion> </exclusions></dependency>
一般我们在解决依赖冲突的时候,都会优先保留版本较高的。这是因为大部分 jar 在升级的时候都会做到向下兼容。
如果高版本修改了低版本的一些类或者方法的话,这个时候就不能直接保留高版本了,而是应该考虑优化上层依赖,比如升级上层依赖的版本。
还是上面的例子:
依赖链路一:A -> B -> C -> X(1.5) // dist = 3依赖链路二:A -> D -> X(1.0) // dist = 2
我们保留了 1.5 版本的 X,但是这个版本的 X 删除了 1.0 版本中的某些类。这个时候,我们可以考虑升级 D 的版本到一个 X 兼容的版本。
为什么要用 Maven 多模块?
模块管理简单地来说就是将一个项目分为多个模块,每个模块只负责单一的功能实现。直观的表现就是一个 Maven 项目中不止有一个 pom.xml 文件,会在不同的目录中有多个 pom.xml 文件,进而实现多模块管理。
多模块管理除了可以更加便于项目开发和管理,还有如下好处:
降低代码之间的耦合性(从类级别的耦合提升到 jar 包级别的耦合);
减少重复,提升复用性;
每个模块都可以是自解释的(通过模块名或者模块文档);
模块还规范了代码边界的划分,开发者很容易通过模块确定自己所负责的内容。
多模块管理下,会有一个父模块,其他的都是子模块。父模块通常只有一个 pom.xml,没有其他内容。父模块的 pom.xml 一般只定义了各个依赖的版本号、包含哪些子模块以及插件有哪些。不过,要注意的是,如果依赖只在某个子项目中使用,则可以在子项目的 pom.xml 中直接引入,防止父 pom 的过于臃肿。
如下图所示,Dubbo 项目就被分成了多个子模块比如 dubbo-common(公共逻辑模块)、dubbo-remoting(远程通讯模块)、dubbo-rpc(远程调用模块)。
HTTP 是什么?位于哪一层?
HTTP(Hypertext Transfer Protocol,超文本传输协议) 位于应用层,基于 TCP 协议,是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
除了 HTTP,你还知道应用层哪些协议?
-
SMTP(Simple Mail Transfer Protocol,简单邮件发送协议):基于 TCP 协议,是一种用于发送电子邮件的协议。注意 ⚠️:SMTP 协议只负责邮件的发送,而不是接收。要从邮件服务器接收邮件,需要使用 POP3 或 IMAP 协议。
-
POP3/IMAP(邮件接收协议):基于 TCP 协议,两者都是负责邮件接收的协议。IMAP 协议是比 POP3 更新的协议,它在功能和性能上都更加强大。IMAP 支持邮件搜索、标记、分类、归档等高级功能,而且可以在多个设备之间同步邮件状态。几乎所有现代电子邮件客户端和服务器都支持 IMAP。
-
FTP(File Transfer Protocol,文件传输协议) : 基于 TCP 协议,是一种用于在计算机之间传输文件的协议,可以屏蔽操作系统和文件存储方式。注意 ⚠️:FTP 是一种不安全的协议,因为它在传输过程中不会对数据进行加密。建议在传输敏感数据时使用更安全的协议,如 SFTP。
-
Telnet(远程登陆协议):基于 TCP 协议,用于通过一个终端登陆到其他服务器。Telnet 协议的最大缺点之一是所有数据(包括用户名和密码)均以明文形式发送,这有潜在的安全风险。这就是为什么如今很少使用 Telnet,而是使用一种称为 SSH 的非常安全的网络传输协议的主要原因。
-
SSH(Secure Shell Protocol,安全的网络传输协议):基于 TCP 协议,通过加密和认证机制实现安全的访问和文件传输等业务
-
RTP(Real-time Transport Protocol,实时传输协议):通常基于 UDP 协议,但也支持 TCP 协议。它提供了端到端的实时传输数据的功能,但不包含资源预留存、不保证实时传输质量,这些功能由 WebRTC 实现。
-
DNS(Domain Name System,域名管理系统): 基于 UDP 协议,用于解决域名和 IP 地址的映射问题。
HTTP 5XX 状态码代表什么?
HTTP 状态码用于描述 HTTP 请求的结果,比如 2xx 就代表请求被成功处理。
常见 HTTP 状态码
RESTful API 和 HTTP 有什么联系吗?
RESTful API 是一种基于 HTTP 协议的软件架构风格,用于构建网络应用程序。它利用 HTTP 协议的各种方法(GET、POST、PUT、DELETE 等)、状态码、URL 和消息头等元素来表示对资源的操作。虽然理论上 RESTful API 可以基于其他协议实现,但在实践中绝大多数都是基于 HTTP 构建的。
Java 集合的种类?
Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collection接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。对于Collection 接口,下面又有三个主要的子接口:List、Set 和 Queue。
Java 集合框架如下图所示:
Java 集合框架概览
注:图中只列举了主要的继承派生关系,并没有列举所有关系。比方省略了AbstractList, NavigableSet等抽象类以及其他的一些辅助类,如想深入了解,可自行查看源码。
-
List(对付顺序的好帮手): 存储的元素是有序的、可重复的。
-
Set(注重独一无二的性质): 存储的元素不可重复的。
-
Queue(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
-
Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),"x" 代表 key,"y" 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
ConcurrentHashMap 为什么线程安全?
ConcurrentHashMap 是 HashMap 的线程安全版本,它提供了更高效的并发处理能力。
在 JDK1.7 的时候,ConcurrentHashMap 对整个桶数组进行了分割分段(Segment,分段锁),每一把锁只锁容器其中一部分数据(下面有示意图),多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
Java7 ConcurrentHashMap 存储结构
到了 JDK1.8 的时候,ConcurrentHashMap 取消了 Segment 分段锁,采用 Node + CAS + synchronized 来保证并发安全。数据结构跟 HashMap 1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))。
Java 8 中,锁粒度更细,synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,就不会影响其他 Node 的读写,效率大幅提升。
Java8 ConcurrentHashMap 存储结构
使用 ConcurrentHashMap 代表不会出现线程安全问题吗?
ConcurrentHashMap 是线程安全的,意味着它可以保证多个线程同时对它进行读写操作时,不会出现数据不一致的情况,也不会导致 JDK1.7 及之前版本的 HashMap 多线程操作导致死循环问题。但是,这并不意味着它可以保证所有的复合操作都是原子性的,一定不要搞混了!
复合操作是指由多个基本操作(如put、get、remove、containsKey等)组成的操作,例如先判断某个键是否存在containsKey(key),然后根据结果进行插入或更新put(key, value)。这种操作在执行过程中可能会被其他线程打断,导致结果不符合预期。
例如,有两个线程 A 和 B 同时对 ConcurrentHashMap 进行复合操作,如下:
// 线程 Aif (!map.containsKey(key)) {map.put(key, value);}// 线程 Bif (!map.containsKey(key)) {map.put(key, anotherValue);}
如果线程 A 和 B 的执行顺序是这样:
线程 A 判断 map 中不存在 key
线程 B 判断 map 中不存在 key
线程 B 将 (key, anotherValue) 插入 map
线程 A 将 (key, value) 插入 map
那么最终的结果是 (key, value),而不是预期的 (key, anotherValue)。这就是复合操作的非原子性导致的问题。
那如何保证 ConcurrentHashMap 复合操作的原子性呢?
ConcurrentHashMap 提供了一些原子性的复合操作,如 putIfAbsent、compute、computeIfAbsent 、computeIfPresent、merge等。这些方法都可以接受一个函数作为参数,根据给定的 key 和 value 来计算一个新的 value,并且将其更新到 map 中。
上面的代码可以改写为:
// 线程 Amap.putIfAbsent(key, value);// 线程 Bmap.putIfAbsent(key, anotherValue);
或者:
// 线程 Amap.computeIfAbsent(key, k -> value);// 线程 Bmap.computeIfAbsent(key, k -> anotherValue);
很多同学可能会说了,这种情况也能加锁同步呀!确实可以,但不建议使用加锁的同步机制,违背了使用 ConcurrentHashMap 的初衷。在使用 ConcurrentHashMap 的时候,尽量使用这些原子性的复合操作方法来保证原子性。
解释一下事务中经常说的幻读
幻读发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
例如:事务 2 读取某个范围的数据,事务 1 在这个范围插入了新的数据,事务 2 再次读取这个范围的数据发现相比于第一次读取的结果多了新的数据。
MySQL 默认的事务隔离级别能解决幻读吗?
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。我们可以通过SELECT @@tx_isolation;命令来查看,MySQL 8.0 该命令改为SELECT @@transaction_isolation;
MySQL> SELECT @@tx_isolation;+-----------------+| @@tx_isolation |+-----------------+| REPEATABLE-READ |+-----------------+
标准的 SQL 隔离级别定义里,REPEATABLE-READ(可重复读)是不可以防止幻读的。但是!InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的,主要有下面两种情况:
-
快照读:由 MVCC 机制来保证不出现幻读。
-
当前读:使用 Next-Key Lock 进行加锁来保证不出现幻读,Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的结合,行锁只能锁住已经存在的行,为了避免插入新行,需要依赖间隙锁。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 READ-COMMITTED ,但是你要知道的是 InnoDB 存储引擎默认使用 REPEATABLE-READ 并不会有任何性能损失。
Spring 中如何进行事务管理?
Spring 中的事务管理主要分为 编程式事务管理 和 声明式事务管理 两种方式:
编程式事务管理:通过 TransactionTemplate或者TransactionManager手动管理事务,代码侵入性较高,实际应用中较少使用,但有助于理解事务管理的原理。
声明式事务管理:推荐使用,代码侵入性低,基于 AOP,通过 @Transactional 注解实现,实际应用最广泛。
Spring AOP 什么场景下用?
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
推荐阅读:
严禁加班,强制下班,真的发生了。。。
我工资1w,跳槽到新公司直接开出了1.5w,我象征性地说:我考虑一下。结果当天下午,HR电话给我说可以涨到2w,可我反而不敢去了
逃离大厂去小公司的人,真的能适应吗?
京东提前发年终奖!最高 8 倍月薪!
一个程序员的水平能差到什么程度?