终于秋招告一段落了,感谢蚂蚁offer!
开心田螺
2025-09-24 21:23:22
0

面试刷题网站:

大家好,我是小林。

互联网大厂的秋招启动得早,面试效率也高。一般来说,3 轮技术面加 1 轮 HR 面,整个流程大概 2-3 周就能完成。

所以最近能拿到秋招 offer 的同学,基本都是互联网中大厂的 offer。

而中小公司的面试时间会相对靠后,因为这类公司的主要招聘场景,很多是在高校举办的双选会上 , 通常当天面试完就能当场发 offer。

10 到 11 月份是双选会的高峰期,同学们可以抓住这波机会,在线下集中收割 offer。

这不,我们就有位同学传来了喜讯,秋招终于告一段落了,成功熬出头了!他面完蚂蚁二面后,一度以为自己凉了,没想到一周后收到了 HR 面试通知,并最终顺利通关,拿下了蚂蚁的 Offer,激动得不行。

拿到了录用通知,剩下就坐等统一薪资开奖了。

说到这,顺便给大家同步一下蚂蚁 25 届的薪资情况,供 26 届的同学参考。后端开发基本能拿到 35W+ 的年薪,还是相当香的。

虽然拿到一个大厂 offer 已经很棒了,但我真心建议大家:先别急着躺平

手里的 offer 越多,你谈薪的底气就越足。

举个例子:你只有一个 offer,想跟 HR 要 28k 可能有点虚。但如果你手上有好几个 offer,其中一个已经开到 26k,你再去要 28k 的期望薪资,成功率会大得多。手里的 offer 就是你最好的筹码。

目前蚂蚁的秋招还在进行中,我们正好来看看一份新鲜出炉的蚂蚁 Java 一面面经

蚂蚁和阿里的风格很像,喜欢深挖 Java 八股文,尤其是 Java 集合和并发这两块,面试官会往原理问,其次就是 MySQL 和网络了,当然最后还是手撕算法,大厂终究还是要考察算法的。

蚂蚁一面1. HashMap如何解决hash冲突?

在 JDK 1.7 版本之前, HashMap 数据结构是数组和链表,HashMap通过哈希算法将元素的键(Key)映射到数组中的槽位(Bucket)。如果多个键映射到同一个槽位,它们会以链表的形式存储在同一个槽位上,因为链表的查询时间是O(n),所以冲突很严重,一个索引上的链表非常长,效率就很低了。

所以在 JDK 1.8版本的时候做了优化,当一个链表的长度超过8的时候就转换数据结构,不再使用链表存储,而是使用红黑树,查找时使用红黑树,时间复杂度O(log n),可以提高查询性能,但是在数量较少时,即数量小于6时,会将红黑树转换回链表。

2. 用HashMap举例说明什么是线程安全?

HashMap是 Java 中常用的哈希表实现,但它不是线程安全的。这意味着当多个线程同时对同一个HashMap实例进行修改操作(如添加、删除元素)时,可能会导致数据不一致、死循环甚至程序崩溃等问题。

下面是一个演示HashMap线程不安全的例子:

importjava.util.HashMap;

importjava.util.Map;

importjava.util.concurrent.ExecutorService;

importjava.util.concurrent.Executors;

publicclassHashMapThreadSafetyExample{

publicstaticvoidmain(String[] args){

// 创建一个非线程安全的HashMap

Map map = newHashMap<>;

// 创建线程池

ExecutorService executor = Executors.newFixedThreadPool(5);

// 多个线程同时向HashMap中添加元素

for(inti = 0; i < 1000; i++) {

finalintkey = i;

executor.execute( -> {

map.put(key, "value"+ key);

});

}

// 关闭线程池并等待所有任务完成

executor.shutdown;

while(!executor.isTerminated) {

// 等待所有任务完成

}

// 理论上map的大小应该是1000,但实际可能小于1000

System.out.println("HashMap的大小: "+ map.size);

}

}

在这个例子中,我们创建了一个HashMap,并使用 5 个线程同时向其中添加 1000 个元素。由于HashMap不是线程安全的,多个线程同时修改它时,可能会出现以下问题:

  1. 数据丢失:某些元素可能没有被正确添加到 map 中,导致最终 map 的大小小于 1000。

  2. 死循环:在极端情况下,可能会导致链表形成环形结构,从而在 get 操作时陷入死循环。

  3. 数据不一致:获取元素时可能得到错误的结果。

相比之下,ConcurrentHashMap是线程安全的哈希表实现。它通过内部的分段锁机制,允许多个线程同时访问和修改不同的段,从而在保证线程安全的同时提供较好的性能。

如果我们将上面例子中的HashMap替换为ConcurrentHashMap,程序的输出将始终是 1000,因为它保证了多线程环境下的数据一致性。

所以,线程安全就是指在多线程环境下,某个数据结构或方法能够保证其操作的原子性、可见性和有序性,从而避免出现数据不一致等问题。HashMap不是线程安全的,而ConcurrentHashMap是线程安全的实现。

3. Java如何解决HashMap的线程安全问题?

方式一:使用 Collections.synchronizedMap包装。

java.util.Collections提供了 synchronizedMap方法,能将普通 HashMap包装为线程安全的集合。其原理是对 HashMap的所有方法添加同步锁(使用 synchronized关键字),确保同一时刻只有一个线程能操作集合。代码如下:

importjava.util.Collections;

importjava.util.HashMap;

importjava.util.Map;

publicclassSynchronizedMapExample{

publicstaticvoidmain(String[] args){

// 创建普通HashMap,并用synchronizedMap包装

Map syncMap = Collections.synchronizedMap(newHashMap<>);

// 多线程环境下使用syncMap

// ...

}

}

特点是实现简单,直接包装即可。性能较差,因为所有方法都用同一把锁,多线程操作时会频繁阻塞,适合并发量低的场景。

方式二:使用 ConcurrentHashMap(推荐)

ConcurrentHashMap是 Java 并发包(java.util.concurrent)提供的线程安全哈希表,专为高并发场景设计。

  • JDK 1.7 及之前:采用「分段锁」机制,将哈希表分为多个段(Segment),每个段独立加锁。多线程访问不同段时不会冲突,效率较高。

  • JDK 1.8 及之后:优化为「CAS + synchronized」机制,移除了分段锁,直接对哈希表的节点(Node)加锁,进一步提升并发性能。

import java.util.Map;

importjava.util.concurrent.ConcurrentHashMap;

publicclassConcurrentHashMapExample{

publicstaticvoidmain(String[] args){

// 创建线程安全的ConcurrentHashMap

Map concurrentMap = newConcurrentHashMap<>;

// 多线程环境下安全操作

concurrentMap.put(1, "a");

String value = concurrentMap.get(1);

}

}

特点是性能优异,支持多线程同时读写,锁粒度更细(JDK 1.8 中为节点级锁),推荐用于高并发场景(如服务器端程序)。

方式三:手动加锁(不推荐)

在操作 HashMap时,手动使用 synchronizedReentrantLock加锁,确保同一时刻只有一个线程修改集合。

importjava.util.HashMap;

importjava.util.Map;

importjava.util.concurrent.locks.Lock;

importjava.util.concurrent.locks.ReentrantLock;

publicclassManualLockExample{

privatefinalMap map = newHashMap<>;

privatefinalLock lock = newReentrantLock; // 可重入锁

publicvoidput(Integer key, String value){

lock.lock; // 加锁

try{

map.put(key, value);

} finally{

lock.unlock; // 释放锁,确保异常时也能释放

}

}

publicString get(Integer key){

lock.lock;

try{

returnmap.get(key);

} finally{

lock.unlock;

}

}

}

灵活性高,但需手动控制锁的获取和释放,容易因遗漏 unlock导致死锁。性能与 synchronizedMap类似,不如 ConcurrentHashMap

4. CAS是什么?

CAS 是一种乐观锁机制,用于在多线程环境下实现无锁并发控制,确保数据操作的原子性。

CAS 操作包含三个核心参数:

  • 内存地址(V):要操作的变量在内存中的地址。

  • 预期值(A):线程认为操作前,变量的预期值。

  • 新值(B):如果预期值与内存中的实际值一致,就将变量更新为新值。

执行时,线程先读取内存地址 V 中的值并记录为预期值 A,接着计算要更新的新值 B,然后再次读取 V 中的实际值并与预期值 A 比较,如果相等,说明值未被其他线程修改,就将 V 更新为 B,操作成功;如果不等,说明值已被其他线程修改,就放弃更新,操作失败,通常会重试或放弃。整个过程是由 CPU 指令直接支持的原子操作,无需使用 synchronized 等重量级锁。

5. 线程池的好处是?

线程在正常执行或者异常中断时会被销毁,如果频繁的创建很多线程,不仅会消耗系统资源,还会降低系统的稳定性,一不小心把系统搞崩了。

使用线程池可以带来以下几个好处:

  • 线程池可以降低资源消耗。线程的创建和销毁需要消耗系统资源(如 CPU 时间、内存),线程池通过复用已创建的线程,避免了频繁创建和销毁线程带来的开销,尤其在需要频繁使用线程的场景下效果显著。

  • 线程池能提高响应速度。当任务到达时,无需等待线程创建就能立即执行(如果池中有空闲线程),减少了任务的等待时间,提升了系统的即时响应能力。

  • 线程池便于管理线程。通过线程池可以统一控制线程的数量、优先级等,避免因无限制创建线程导致系统资源耗尽(如内存溢出、CPU 过度占用),还能对线程进行监控、调优和扩展,使线程管理更加规范和灵活。

所以,线程池是为了减少频繁的创建线程和销毁线程带来的性能损耗。

6. 创建线程池时需要考虑哪些参数?

ThreadPoolExecutor的构造函数:

publicThreadPoolExecutor(

intcorePoolSize,

intmaximumPoolSize,

longkeepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler

)

  • 核心线程数(corePoolSize):线程池长期维持的最小线程数量,即使线程空闲也不会被销毁,对于 CPU 密集型任务,建议设置为 CPU 核心数附近;对于 I/O 密集型任务,可以设置得更大一些。

  • 最大线程数(maximumPoolSize):线程池允许创建的最大线程数量,当核心线程忙碌且任务队列满时会创建新线程直到该值,需考虑系统能承受的最大并发量和任务峰值压力

  • 空闲线程存活时间(keepAliveTime):超过核心线程数的 “临时线程” 空闲时的存活时间,超时后会被销毁,需结合任务波动频率设置

  • 时间单位(unit):配合 keepAliveTime 使用,指定时间单位(如毫秒、秒等)

  • 任务队列(workQueue):用于缓存等待执行任务的阻塞队列,需根据任务大小和数量选择,如无界队列、有界队列或优先级队列

  • 线程工厂(threadFactory):用于创建线程的工厂,可自定义线程名称、优先级等,需考虑线程可追溯性和优先级设置

  • 拒绝策略(handler):当任务队列满且线程数达最大值时,对新提交任务的处理策略,需根据业务对任务丢失的容忍度选择,如抛出异常、丢弃任务等

7. 线程池的工作原理?

线程池的工作原理如下图:

当用户提交了一个任务,接下来这个任务将如何执行都是由这个阶段决定的。首先,所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其执行过程如下:

  1. 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。

  2. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。

  3. 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。

  4. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。

  5. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

8. B+树索引原理?为什么能提高查询效率?

B + 树是一种多路平衡查找树:

  • 树结构特征:B + 树由根节点、中间节点和叶子节点组成,所有节点按层次有序排列。每个非叶子节点(根、中间节点)只存储索引键和子节点指针,不存储实际数据;叶子节点则按索引键顺序串联成一个双向链表,既存储索引键,也存储对应数据的物理地址(或直接存储数据记录)。

  • 多路平衡特性:每个节点可以包含多个索引键,通过多个分支减少树的高度(通常 3-4 层即可支持千万级数据)。同时,树始终保持平衡状态,即从根到任意叶子节点的路径长度相同,避免了二叉树可能出现的极端倾斜问题。

  • 查询路径固定:所有查询最终都会落到叶子节点,因为非叶子节点仅作为索引导航,不存储实际数据。叶子节点的双向链表结构支持范围查询(如betweenin等),只需找到范围的起始和结束位置,即可通过链表快速遍历中间所有记录。

B + 树通过优化结构减少了磁盘 I/O、利用了磁盘存储特性,并针对性优化了范围查询,从而显著提升了数据库的查询效率,因此MySQL 默认的存储引擎 InnoDB 采用的是 B+ 作为索引的数据结构。

9. HTTPS实现安全通信的原理

传统的 TLS 握手基本都是使用 RSA 算法来实现密钥交换的,在将 TLS 证书部署服务端时,证书文件其实就是服务端的公钥,会在 TLS 握手阶段传递给客户端,而服务端的私钥则一直留在服务端,一定要确保私钥不能被窃取。

在 RSA 密钥协商算法中,客户端会生成随机密钥,并使用服务端的公钥加密后再传给服务端。根据非对称加密算法,公钥加密的消息仅能通过私钥解密,这样服务端解密后,双方就得到了相同的密钥,再用它加密应用消息。

我用 Wireshark 工具抓了用 RSA 密钥交换的 TLS 握手过程,你可以从下面看到,一共经历了四次握手:

TLS 第一次握手

首先,由客户端向服务器发起加密通信请求,也就是 ClientHello 请求。在这一步,客户端主要向服务器发送以下信息:

  • (1)客户端支持的 TLS 协议版本,如 TLS 1.2 版本。

  • (2)客户端生产的随机数(Client Random),后面用于生成「会话秘钥」条件之一。

  • (3)客户端支持的密码套件列表,如 RSA 加密算法。

TLS 第二次握手

服务器收到客户端请求后,向客户端发出响应,也就是 SeverHello。服务器回应的内容有如下内容:

  • (1)确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信。

  • (2)服务器生产的随机数(Server Random),也是后面用于生产「会话秘钥」条件之一。

  • (3)确认的密码套件列表,如 RSA 加密算法。(4)服务器的数字证书。

TLS 第三次握手

客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。

如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:

  • (1)一个随机数(pre-master key)。该随机数会被服务器公钥加密。

  • (2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。

  • (3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。

上面第一项的随机数是整个握手阶段的第三个随机数,会发给服务端,所以这个随机数客户端和服务端都是一样的。

服务器和客户端有了这三个随机数(Client Random、Server Random、pre-master key),接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」

TLS 第四次握手

服务器收到客户端的第三个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。

然后,向客户端发送最后的信息:

  • (1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。

  • (2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。

至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。

10. 非对称加密原理是什么?

非对称加密有两个密钥,分别是公钥和私钥。可以用公钥加密数据,然后要用私钥解密,也可以用私钥加密,然后用公钥加密数据。

具体来说,非对称加密的工作流程是:发送方使用接收方公开的公钥对数据进行加密,加密后的密文只能通过接收方独有的私钥才能解密;反之,若发送方用自己的私钥对数据(通常是数据的哈希值)加密,接收方则可使用对应的公钥验证签名,确认数据未被篡改且发送者身份真实。

这种 “公钥加密、私钥解密”“私钥签名、公钥验签” 的机制,解决了对称加密中密钥传输的安全问题。

11. 对称加密与非对称加密的区别是什么?

  • 密钥机制:对称加密使用单一密钥(密钥 A)进行加密和解密,加密和解密过程使用同一密钥,且密钥需要在通信双方之间提前共享。非对称加密使用一对密钥(公钥和私钥),公钥可公开传播,私钥由持有者保密;用公钥加密的数据只能用对应私钥解密,用私钥加密的数据(通常用于签名)只能用对应公钥验证,无需共享私钥。

  • 安全性基础:对称加密的安全性依赖于密钥的保密性,一旦密钥泄露,加密数据会被破解;其加密算法(如 AES)本身数学复杂度较低,主要通过密钥长度(如 128 位、256 位)保证安全性。非对称加密的安全性基于数学难题(如 RSA 依赖大整数分解,ECC 依赖椭圆曲线离散对数问题),即使公钥公开,也难以从公钥推导出私钥,安全性更高,但算法本身计算复杂。

  • 性能与适用场景:对称加密算法(如 AES、DES)计算简单、速度快,适合大量数据加密(如文件、流数据传输),但密钥传输过程存在泄露风险。非对称加密(如 RSA、ECC)计算量大、速度慢,不适合大文件加密,主要用于密钥交换(如用公钥加密对称密钥)、数字签名(验证身份和数据完整性)等场景。

12. 算法
  • 数组中三个连续子数组的最大和。

相关内容

热门资讯

嘉兴市实验小学建校120周年高... 百廿弦歌不辍,红船畔育桃李。12月27日上午,嘉兴市实验小学建校120周年高质量办学成果展暨“新时代...
拆解历史老师定哥的“第二大脑”... 导语: AI已经不是一道“用不用”的选择题,而是一道“怎么用”的必答题。 2025年,AI进课堂的声...
教师园地 | 写课,与学科深度... 当你开始有意识地关注这些细节,提笔记录便会发现,你不仅在书写文字,更是在“写”短迷茫与摸索的成长期,...
匠心育人,培根铸魂丨2025年... 对外经济贸易大学 导师风采 编者说 为深入贯彻落实习近平总书记关于教育的重要论述和研究生教育工作的重...
幼儿园收费新规来了!2026年... 幼儿园收费是大家非常关注的 好消息来了 国家发展改革委、教育部、财政部 联合发布《关于完善幼儿园收费...
如何养出一个无条件相信自己、爱... ADHD互助联盟 “作为父母,无意中的言行对孩子会造成很大的影响。比如有时候孩子能很敏感的感知到我...
四川省成都市2026届高三第一... 一、阅读(72分) (一)阅读Ⅰ(本题共5小题,19分) 阅读下面的文字,完成1~5题。 材料一: ...
2025广州留学中介权威榜单,... 一、2025年广州留学中介如何选择?高录取案例背后有何门道? 在搜索引擎上,“广州哪家留学中介靠谱...
市青年夜校古典舞课程结课 当夜色渐浓,舞步轻启。近日,为期4节的青年夜校古典舞课程圆满落幕。从零基础的懵懂试探,到演绎完整舞蹈...
家长警惕:这些风险,别让孩子措... 防范性侵 呵护成长 尊敬的家长朋友: 您好!孩子是家庭的希望,是祖国的未来,他们的笑容本应在阳光下灿...