“阿里2023客户端开发面试题”我前前后后刷了三轮,一面、二面、交叉面都经历了。整体感觉是:不背八股,但比八股更狠。它问的东西基本都是Android日常开发里天天碰,但多数人从没往深想过的点。这篇文章我把这两年面阿里客户端岗遇到的高频题、答题思路、以及我自己的踩坑经历整理成一份复盘笔记,主要覆盖Java基础、Kotlin协程、Android核心机制、性能优化、网络框架、手写代码和方案设计这几块,准备面大厂客户端岗位的朋友可以直接拿来做自查清单。
1. 开场就是项目深挖,别指望靠背题混过去
1.1 自我介绍怎么讲才算有效
面试官基本都会先来一句“先做个自我介绍”,很多人上来就把简历复读一遍,这是大忌。自我介绍的核心目的不是让面试官认识你,而是给他一个印象锚点:你是做什么的、擅长什么、和这个岗位的匹配点在哪。
我自己的套路是压到一分钟以内,三段式:背景一句话带过,重点讲最近一个项目,最后落到岗位匹配点。比如我当时做购物车模块,说的是“负责购物车模块的开发和优化,核心难点是商品状态在多端同步下的并发冲突,最终通过服务端版本号加本地乐观锁的方案解决了覆盖丢失问题,线上故障率下降了百分之八十”。这个表述里包含业务场景、技术难点、解决方案、量化结果,面试官下一句大概率就会顺着这个深挖。
1.2 项目深挖的连环炮怎么接
阿里的项目深挖是真深挖,不是走个过场。常见连环问包括:为什么选这个方案不选别的、上线后有没有出过问题、出了问题怎么排查、如果再给你一次机会会怎么做。
举个例子,我当时提到购物车用了本地缓存,面试官立刻追问:本地缓存和内存缓存有什么区别,你们为什么存磁盘、怎么保证一致性、缓存失效策略是什么、多端同时改怎么处理冲突。这一串问题如果平时没想过,很容易当场卡壳。我的建议是,项目里用到的每一个技术选型都要能回答三个层面:是什么、为什么选它、不选另一个方案的原因是什么。比如不用SharedPreferences而用DataStore,不考虑Room而直接Sqlite,这些对比都要有数据支撑,哪怕是你自己预估的数值,也比一句“大家都这么用”有说服力。
2. Java和Kotlin底层,翻车重灾区
2.1 HashMap:从数据结构问到并发问题
阿里面HashMap的概率极高,而且会一路向内追问。先是数据结构:数组加链表,链表长度超过8转为红黑树,小于6退化为链表,为什么用8作为阈值,因为泊松分布下链表长度到8的概率已经低到亿分之六,同时红黑树节点占用空间比链表大,所以只有在链表足够长时才值得转换。
接着会问为什么容量是2的幂次,因为计算数组下标用的是hash & (n-1),相当于取模,但只有n是2的幂时hash & (n-1)才等价于hash % n,而且位运算比取模快得多。再往下就是线程安全性,HashMap在并发场景下为什么不能用。JDK1.7头插法扩容在多线程下可能形成环形链表,JDK1.8改成尾插法解决了死循环,但数据覆盖问题还在,两个线程同时put,后写的一方可能覆盖前者的结果。
如果继续深挖,会引到ConcurrentHashMap。JDK1.8的ConcurrentHashMap放弃了分段锁,改用CAS加synchronized,锁的粒度是单个桶。CAS失败说明有竞争,就升级为synchronized锁住这个桶。这样在绝大多数无竞争场景下是乐观锁,开销很低,真正有竞争时才锁。
2.2 volatile、synchronized和CAS,三者关系要说透
volatile的考点集中在可见性和有序性,不保证原子性。面试官会让你举例说明,典型场景就是DCL单例。为什么单例要用volatile修饰instance,因为instance = new Singleton()不是原子操作,分为分配内存、初始化对象、赋值三步,CPU和编译器可能重排为先赋值再初始化,另一个线程拿到半初始化的对象直接使用就会出问题。volatile通过内存屏障禁止了这种重排。
synchronized在JDK1.6之后做了大量优化,面试一般问锁升级过程:无锁到偏向锁,偏向锁是同一个线程反复获取锁时省去CAS,有竞争时升级为轻量级锁,轻量级锁通过自旋等待锁释放,自旋超过阈值或等待线程数太多就膨胀为重量级锁,此时线程真正挂起,涉及内核态切换。能答出偏向锁撤销的代价,基本就过关了。
CAS的坑主要是ABA问题,线程A读到值1,线程B把它改成2又改回1,线程A的CAS仍然能成功,但实际数据已经被改过。解决方式是加版本号,AtomicStampedReference就是这个思路。还有自旋的CPU开销问题,CAS失败的线程不会立即挂起,而是不断重试,高竞争场景下CPU占用会很高。
2.3 JVM内存布局和GC,Android面试也逃不掉
很多人觉得Android开发不用懂JVM,实际上大厂面试JVM是必考题。首先得把运行时数据区说清楚:堆、虚拟机栈、本地方法栈、方法区、程序计数器。Android上方法区对应的是称为“方法区/元空间”的区域,老版本叫PermGen,但核心概念一致。
对象存活判断有两种算法,引用计数法的问题是循环引用无法回收,所以JVM用可达性分析,从GC Roots出发遍历,不可达的对象判定为可回收。GC Roots包括虚拟机栈中引用的对象、静态属性引用的对象、常量引用的对象、JNI引用的对象。
垃圾回收算法也需要对比。标记清除有碎片问题,标记复制浪费一半空间,标记整理耗时长。所以分代收集不同区域用不同算法:新生代对象存活率低,用复制算法,把Eden和Survivor区角色区分;老年代对象存活率高,用标记整理或标记清除。面试官特别喜欢问为什么新生代分区是8比1比1,因为这样才能用复制算法时只浪费百分之十的空间,而不是百分之五十。但我个人在实际项目里更关注GC导致的卡顿问题,这块在后面性能优化部分详细说。
2.4 Kotlin协程的挂起和恢复,不能只会用
现在的客户端岗位,Kotlin是默认技能,协程基本是必问。面试官会问协程和线程的区别,标准答案是线程是内核态调度的,切换有内核开销,协程是用户态调度,挂起不阻塞线程,本质上是把一个方法拆成状态机来执行。
加分回答要落到suspend的实现原理上。编译器会把挂起函数编译成一个Continuation传递的状态机对象,每到一个挂起点就保存当前状态和局部变量,恢复时从对应分支继续执行。比如一个顺序请求两个接口的代码,编译后有多个label,每次resume就跳到对应label继续跑。
还有结构化并发和协程泄漏。lifecycleScope和viewModelScope的区别,就是生命周期感知,Activity销毁时会自动cancel协程,避免后台任务持有Activity引用导致泄漏。如果面试官问GlobalScope为什么不能用,核心原因是它不绑定生命周期,协程可能在组件销毁后还在跑,既浪费资源又容易引发内存泄漏。
3. Android核心机制,Handler到Binder一条链路
3.1 Handler机制和内存泄漏,答完内存泄漏才算完整
Handler是Android消息机制的核心,面试问法非常固定:Looper、MessageQueue、Handler三者关系,message的postDelayed怎么实现延时,主线程为什么不会因为死循环卡死。
先说关系。Looper负责消息循环,一个线程只有一个Looper,通过ThreadLocal保证。MessageQueue虽然是队列,但底层是单链表按时间排序,Handler发送消息时按延迟时间插入对应位置。Looper.loop()是个死循环,不断从MessageQueue取Message,取不到就阻塞。主线程不会卡死是因为阻塞用的是epoll机制,没有消息时主线程进入休眠状态,有消息时通过管道唤醒,不占用CPU。所以Android主线程必须保持Looper循环,一旦loop退出应用就挂了。
postDelayed不是开启一个定时器,而是把消息按触发时间排序插到MessageQueue里,时间到了才取出。如果主线程卡顿,这个延时是不准的,因为消息循环被阻塞,触发时间晚于预期,这也是为什么不要在子线程用Handler.postDelayed做精确计时。
Handler内存泄漏的机制也要说透。非静态内部类持有外部Activity的引用,MessageQueue里的消息如果延迟时间很长,或者短时间内大量消息堆积,会一直持有Handler引用,导致Activity无法回收。解决方式是两个方向:一个是使用静态内部类加WeakReference弱引用外部对象,另一个是在onDestroy时移除所有未处理的消息和回调。实际项目中最好两个都做,移除消息回调是兜底。
3.2 Binder为什么是Android IPC的标准答案
Binder的考点主要是三个:为什么选Binder、一次拷贝是怎么回事、四大组件和Binder的关系。
为什么不用传统管道或者Socket,阿里这边一般会引导你比较性能和安全。传统IPC需要两次拷贝,用户空间到内核空间再到另一个用户空间,而Binder只需要一次拷贝,因为Binder驱动用mmap把内核缓冲区映射到了用户空间。安全方面,Binder驱动会为每个进程分配UID,服务端可以校验调用方身份。
一次拷贝的原理听起来抽象,说白点就是:发送方把数据拷贝到内核缓冲区,这个缓冲区和接收方用户空间是同一块物理内存的映射,内核不需要再把数据从自身缓冲区拷到接收方用户空间,接收方直接就能看到数据,省了一次拷贝。这比Socket和管道快。
AIDL的transact流程也常被问。客户端调用代理接口的transact方法,把Parcel数据发给Binder驱动,驱动找到目标服务端,服务端的Binder线程池处理onTransact,返回结果再走一遍。Binder线程池默认上限是16个,超过会阻塞,如果服务端处理太慢可能导致调用端线程堆积,这点在性能调优时要注意。
3.3 Activity冷启动全流程,能画出链路才算过关
这道题考察的是对整个系统层机制的理解。冷启动链路大概是:Launcher点击图标,通过startActivity发请求到AMS,AMS验证权限和目标Activity,接着通过socket向Zygote进程发送fork请求,Zygote fork出新的应用进程,新进程入口是ActivityThread的main方法,创建Application和主线程Looper,然后创建Activity,最后走onCreate、onStart、onResume,首帧绘制完成后用户看到界面。
面试官一般会在这个链路里挑几个点深挖。例如Zygote分支出来的进程为什么要用socket而不是Binder通知,因为Zygote是native层,进程启动早期Binder还没准备好。再比如Application的onCreate里做太多事为什么会导致启动慢,因为它发生在Activity创建之前,阻塞的是首帧时间。
字节流里补充一下,启动优化的另一个关注点是首帧时间,用Profile里的“Displayed”指标观察。平时自查时可以用adb shell am start -W 包名/Activity直接看冷启动耗时,快速定位是Application的问题还是Activity初始化的问题。
3.4 事件分发和绘制流程,动手画一遍就懂了
事件分发的核心机制是嵌套责任链。dispatchTouchEvent决定事件往哪走,onInterceptTouchEvent只存在于ViewGroup,决定要不要拦截。DOWN事件必须以完整分发链走完,因为整个手势系列事件的处理者是由DOWN事件决定的,后续MOVE和UP继续交给同一个目标。
常见考法是滑动冲突怎么处理。外部拦截法是在父View的onInterceptTouchEvent里根据判断条件决定是否拦截,内部拦截法是在子View的onInterceptTouchEvent里请求父View不要拦截,配合requestDisallowInterceptTouchEvent。答题时最好先说清楚两种方法各自的适用场景,外部拦截法逻辑清晰,适合简单的纵向横向滑动冲突,内部拦截法适合需要子View先判断的情况。
绘制流程的考点包括MeasureSpec模式、三大流程顺序、invalidate和requestLayout的区别。MeasureSpec的三种模式,EXACTLY对应matchParent或固定值,AT_MOST对应wrapContent,UNSPECIFIED用于ScrollView这类特殊场景。invalidate只触发onDraw,requestLayout会走measure和layout,所以频繁requestLayout代价更高。实际项目中过度调用requestLayout是造成卡顿的常见原因之一,特别是RecyclerView嵌套时,这个习惯要在代码评审时严格把关。
4. 性能优化,把线上问题摆到桌面上聊
4.1 卡顿监控,从原理到落地
阿里的性能优化问题不会让你背概念,而是问“你们线上怎么发现卡顿的”。我当时的回答分几条线:一是在主线程Looper.loop()里加Printer,每次分发消息前后打印日志,如果两条日志间隔超过阈值说明主线程卡了,通过堆栈抓到卡顿现场;二是用Choreographer统计掉帧,Choreographer的doFrame回调里frameTimeNanos和vsync时间对比,超过16.6ms说明掉帧。
很多人的误区是只做线上监控,不重视线下定位。我的经验是线下先用Systrace和Perfetto看trace文件,找出主线程执行时间最长的方法,再针对性地做优化。Systrace能看到CPU频率和线程调度情况,Perfetto比Systrace更新,UI界面也更友好。优化方向一般是:避免主线程做IO、减少过度绘制、减少布局层级、避免在getView里创建对象、避免频繁GC。
卡顿率这个指标也是面试官爱问的。我这边常用的定义是“卡顿率=卡顿次数/总启动次数”,单次超过500ms就算一次卡顿,也有团队用SmoothPercent(流畅率)来评估。关键是让面试官看到你有一个线上监控体系,而不是只会用Profiler。
4.2 内存泄漏排查,LeakCanary原理是加分项
内存泄漏的经典场景我就不列了,Handler、静态变量、单例、匿名内部类这些是基础,我更想聊的是排查手法。LeakCanary虽然是个库,但它怎么工作的很多人说不清楚。
LeakCanary的思路是,在Activity或Fragment的onDestroy之后,把对象放进WeakReference,同时关联一个ReferenceQueue。当GC执行时,如果对象只被弱引用持有,会立刻被回收并进入ReferenceQueue,如果过了几秒这个队列里没有出现该引用,说明对象还被强引用持有,于是手动触发一次GC再检查,仍然没有回收就认为发生了泄漏,然后自动dump heap并分析引用链。
手写题里也常出类似场景,让你用WeakReference加ReferenceQueue实现一个简单的泄漏检测器,所以这个源码级别的理解能直接用来答题。
Bitmap相关的内存优化也常出现:大图的采样率inSampleSize要按原图尺寸和目标控件尺寸计算,inJustDecodeBounds先读宽高再加载,避免BitmapFactory直接解码大图OOM。还有inBitmap复用已回收的Bitmap内存,不过这个现在有局限性,需要同等像素格式,聊思路就可以。
4.3 启动优化,异步改造是核心,但不是无脑异步
启动优化的常规手段包括Application里延迟初始化、用启动器并行初始化、减少首屏布局层级、用启动主题避免白屏。但面试官想听的是你对“并行”的理解深度。
我自己做过一次启动优化,把Application里十几个初始化任务梳理了一遍,发现有依赖关系的任务非常少,大多数实际上相互独立。于是引入了一个启动器框架,本质是个有向无环图,通过拓扑排序确定执行顺序,把无依赖的任务放到线程池并行执行,有依赖的任务等前置任务完成再启动。实测冷启动时间从2.2秒降到1.4秒,效果明显。
不过“无脑异步”是坑,不是所有初始化都能放子线程,比如某些SDK初始化必须在主线程且必须在特定时机前完成,比如ContentProvider的初始化。优化前要把每个任务标注线程类型、是否阻塞、被谁依赖,这份梳理表本身就是一份技术方案。面试中能拿出这种层级来回答问题,远胜于背一堆优化手段。
4.4 包体积优化,从字节角度抠细节
包体积优化阿里问得不算多,但问到就喜欢具体数字。APK主要由dex、resources、libs、assets四部分构成,对应不同优化手段。
dex的优化主要是开启minifyEnabled和shrinkResources,用R8做代码收缩和资源收缩,删除无用代码和无用资源。资源和so层面可以做资源混淆,比如AndResGuard把资源路径缩短,R8映射到短名字,不过开混淆后要重点回归资源ID相关的逻辑。assets里的大文件,结合业务情况考虑下载到本地而非打进包里。图片统一用WebP,能转的都转,不用PNG。lib目录下只保留需要的CPU架构,arm64-v8a为主,armeabi-v7a按需保留,x86只留给模拟器用。
包体积优化没有一招制敌的方法,靠的就是把每个模块按大小排个序,从大到小逐步压。我在实际项目中会把APK拆分分析工具输出的数据直接贴到优化任务单上,每改一项记录体积变化,这样整个团队能清晰看到每一项优化的效果。
5. 网络和数据持久化,框架原理考点集中在这里
5.1 OkHttp的拦截器和连接池,重点说连接池
OkHttp在客户端面试中的地位和HashMap在Java面试中差不多。首先是把请求流程说清楚,核心是拦截器责任链:RetryAndFollowUpInterceptor负责重试和重定向,BridgeInterceptor负责补齐请求头,CacheInterceptor走缓存,ConnectInterceptor负责连接,CallServerInterceptor真正发起网络请求。每个拦截器有各自的职责,互相之间通过Chain串联,这种设计模式本身也是面试点。
连接池是比较深入的考点。OkHttp默认维护了最多5个空闲连接,每个连接空闲超过5分钟会被清理。连接复用的原理是用一个Deque存放连接,发起请求时优先找相同host且未过期的连接,找不到才新建。为什么需要连接池,因为TCP三次握手和TLS握手开销大,复用连接能显著减少延迟。
HTTP/2的多路复用也是加分点。同一个连接可以并发跑多个请求,彻底解决了HTTP/1.1队头阻塞的问题。但如果面试官问你为什么OkHttp还在用5个连接的限制,答案是因为HTTP/2的多路复用能力已经很强,一个连接就够用。
5.2 Retrofit的动态代理,两行代码背后全是原理
Retrofit的核心原理是Java动态代理。你定义接口和注解,Retrofit通过Proxy.newProxyInstance生成接口的实现类,每个接口方法调用都会被代理捕获,然后根据方法上的注解、参数、返回值类型构建请求,最终返回一个Call对象或直接是数据对象。
加分点是说清楚InvocationHandler里做了什么:解析注解得到HttpMethod、请求路径、查询参数、请求体,然后用ServiceMethod封装,最后调用OkHttp发送请求。CallAdapter的作用是把Call转成其他类型,比如协程的suspend函数或者RxJava的Observable,这就是为什么Retrofit能无缝支持协程的原因。
如果被问“动态代理和静态代理有什么区别”,要答到生成时机。静态代理是在编译期就写好的代理类,动态代理是运行时生成,Retrofit面对的接口未未知,只有运行时才知道方法签名,所以必须动态生成。
5.3 SQLite优化,索引和事务别只停留在会用
Android端数据库优化,面试官常问的是索引和事务。索引的底层是B+树,为什么用B+树而不是红黑树,因为数据库场景是磁盘存储,B+树的叶子节点形成有序链表,范围查询非常高效,树的高度低,一次查询最多三四次磁盘IO。
事务的核心作用是减少磁盘IO次数。一次事务里插入1000条数据,如果不加事务,每条都要刷盘,加了事务后在内存中累积到提交时才一次性写盘,速度差距可能达到百倍。在客户端极速场景下,比如聊天记录批量插入,这个优化非常关键。
索引失效的经典场景也要答出来:like的前置通配%xx、在索引列上使用函数或隐式类型转换、使用OR连接的条件不是所有列都有索引。实战中我见过很多次因为隐式转换导致全表扫描的问题,比如字符串字段和数字比较,SQLite会自动转型,索引就失效了。
6. 手写代码和方案设计,考验的不是你会背多少
6.1 手写LRU,HashMap加双向链表是标准解
LRU在Android面试中的出现频率极高,因为它本身就是一个真实的项目模型。实现方式就是HashMap加双向链表,HashMap负责O(1)的定位,双向链表负责维护访问顺序。每次get时把节点移到链表头部,每次put时把节点加到头部,如果缓存满了,删除链表尾部的节点。
为什么不用单链表,因为删除任意节点时需要知道前驱节点,单链表要遍历查找前驱,做不到O(1),双向链表每个节点都有prev和next指针,删除当前节点直接就能完成。面试官问你为什么不直接用LinkedHashMap的时候,要答出LinkedHashMap的原理就是HashMap加双向链表,并且构造方法里的accessOrder参数为true时就开启了LRU功能,重写removeEldestEntry控制容量即可。
6.2 线程安全的单例,DCL是必背但要说清原理
单例模式的几种写法都考过,DCL是重点。双重检查锁的核心点是第二层检查为什么需要,因为线程A和线程B同时进入同步块外层,A先获得锁创建了实例并释放锁,B进入同步块时如果不做第二层检查,会再new一个实例,破坏单例。
volatile的作用前面说了,防止指令重排。如果面试官继续问“静态内部类单例为什么天然线程安全”,答案是利用了类加载机制,静态内部类只有在被调用时才加载,由类加载器保证线程安全,两个特点都有:懒加载和线程安全。最后问“枚举单例为什么最安全”,因为枚举类在反序列化时JVM做了特殊处理,不会重新创建实例,而普通单例实现Serializable后会因反序列化出现新实例。
6.3 图片加载库设计,从缓存到线程调度全链路
设计图片加载库是阿里的经典设计题。我的回答思路是从数据流出发:加载一张图片,先查内存缓存,再查磁盘缓存,都没有就发网络请求,下载成功后写入磁盘和内存缓存,最后在主线程显示到ImageView上。
内存缓存用LRU,因为图片解码后是Bitmap,占用内存大,没有内存缓存会频繁GC。磁盘缓存也用LRU,但存的是压缩后的文件,比如WebP或JPEG,对应Glide的DiskLruCache实现。为什么要两级缓存,因为内存缓存速度快但容量小,进程重启就没了,磁盘缓存速度慢但容量大且持久。
线程模型要用主线程和IO线程分离。UI显示必须在主线程,但网络请求和磁盘读取不能阻塞主线程,所以需要线程池按任务类型分类。Glide还解决了生命周期问题:请求与Activity生命周期绑定,页面销毁时自动取消,避免Bitmap加载完成后却显示在一个已经不存在的界面上。面试官提到Glide时,把生命周期绑定这一层主动说出来,很加分。
6.4 组件化和路由,阿里系项目最爱问
组件化在阿里巴巴内部是标配,所以这里几乎必考。首先要说清楚为什么组件化:多个业务模块并行开发时互相依赖会导致编译时间爆炸、代码耦合严重、回归范围不可控。解决的思路是模块间不直接引用,通过路由表来通信和跳转。
ARouter的原理要讲明白:编译期用APT扫描注解,生成路由表文件,运行时通过path找到对应的Activity或者服务实现类,然后用Intent跳转或反射调用。路由表按组划分,加载时懒加载,不会每次启动都扫描全量。
模块间通信的方式也要准备一套自己的方案。常见方案包括:路由跳转、接口下沉+Binder或ServiceLocator、事件总线通信。面试官问公司内部怎么做的,我的回答是接口下沉加路由双轨制:跨模块调用统一走接口,跳转统一走路由,事件用协程的Channel,尽量避免引入事件总线,因为事件总线全局广播,不好排查。
7. 高频题速查表和避坑指南
7.1 高频题速查表,考前两小时过一遍
这里给一份我在面试前整理的速查表,覆盖了阿里客户端面试的高频点,建议考前一天过一遍。注意速查表不是让你背答案,而是帮你确认哪些知识点掌握得扎实、哪些需要临时查漏补缺。
| 考察方向 | 高频问题 | 核心回答要点 |
|---|---|---|
| Java基础 | HashMap底层和并发问题 | 数组+链表+红黑树,2的幂,CAS链式处理 |
| JVM | 内存分区和GC算法 | 堆栈方法区,可达性分析,分代收集 |
| 并发 | volatile和synchronized区别 | 可见性有序性,锁升级过程 |
| Kotlin | 协程和suspend原理 | 状态机,非阻塞挂起,结构化并发 |
| Handler | 消息机制和内存泄漏 | Looper循环,epoll,静态内部类+WeakReference |
| Binder | 为什么用Binder | 一次拷贝,安全校验,四大组件通信 |
| 性能 | 卡顿监控方案 | Looper Printer,Choreographer掉帧 |
| 网络 | OkHttp连接池 | 复用连接,5空闲连接,拦截器链 |
| 设计 | 图片加载库 | 内存/磁盘双层LRU,生命周期绑定 |
| 手写 | LRU和DCL单例 | 双向链表,volatile防重排 |
7.2 面试中的几个禁忌,都是我踩过的坑
第一,简历上写的每一个技术点都要能讲得比面试官预期更深。我曾把“熟悉Glide”写在简历上,结果被追问请求的线程调度模型和生命周期绑定,答得稀碎。写完简历之后把每个关键词都过一遍原理,不留死角。
第二,不会的题不要沉默,哪怕先讲一下分析思路也行。我当时被问“Android系统启动流程中AMS在zygote之前还是之后”,第一反应是不知道,但我把自己知道的Zygote fork进程和AMS启动应用的过程说了一遍,面试官反而觉得我有逻辑。
第三,拒绝模板式回答。面试官问“你做过最失败的项目是什么”,不要背网上的标准答案“没有失败过”。真实的回答是当时选型失误导致后期返工,以及你从中学到了什么,这种答案才让人信服。
第四,反问环节要问有价值的问题。不要问“公司加班多吗”,也别问“面试结果怎么样”。我当时问的是“团队现在最头疼的技术问题是哪个方向”,效果不错。
根据我个人经验,阿里的面试风格更重基础和落地,不会问太偏的冷门题,但会把最基础的知识点问到让你怀疑人生。准备面试时与其啃难偏怪题,不如把项目里用到的每个框架源码拎出来看一遍,把线上的性能数据整理成可讲述的故事。面挂了也没关系,复盘价值很大,因为这些题本质上是把日常开发中最容易忽视的底层逻辑揪出来鞭策你,认真走完这个流程,技术底子会实打实厚一圈。
最后再分享一个小技巧。复盘的时候给自己录个音,或者对着镜子把项目讲一遍,你会发现好多听起来顺滑的技术点,真要开口讲就像嘴里含了沙子。讲顺了,面试的紧张感能消掉一半。希望这篇整理能帮你少走点弯路。