拆解:從基礎(chǔ)語法到JVM手寫代碼全攻略)
前兩天一個學(xué)弟甩給我一份文檔標(biāo)題寫著愛奇藝2020校招Java方向筆試題第二場說想讓我?guī)兔潉澲攸c(diǎn)。我翻了翻這套題第一反應(yīng)不是題目有多難而是考察面鋪得很開從Java基礎(chǔ)語法里的運(yùn)算符優(yōu)先級到面向?qū)ο蟮睦^承多態(tài)再到集合框架、異常處理、JVM內(nèi)存最后還有手寫排序算法。一位同學(xué)在校招季反復(fù)背的java基礎(chǔ)、java面試題、java八股文基本都能在這套卷子里找到對應(yīng)位置。如果你也正在準(zhǔn)備Java校招或者打算系統(tǒng)過一遍Java核心知識這篇文章就是沖這類筆試題來的。我會站在當(dāng)時考生的視角拆解第二場這類卷子里的高頻考點(diǎn)、常見出題套路和答題邏輯再結(jié)合我這些年看簡歷、面人的經(jīng)驗(yàn)把真正拉分的細(xì)節(jié)講透尤其是那些看起來像送分題、實(shí)際上最容易丟分的地方。1. 從這套題的命題地圖看Java校招筆試的底層規(guī)律1.1 大廠筆試為什么偏愛基礎(chǔ)八股加手寫代碼的組合很多人看到八股文三個字就皺眉覺得這是死記硬背。但如果你站在出題人的角度想一場筆試要在有限時間里篩出基礎(chǔ)扎實(shí)、能寫出正確代碼、能快速定位問題的候選人最有效率的方式就是組合拳選擇題考知識面的寬度手寫題考代碼落地的能力。愛奇藝2020校招Java方向的第二場筆試題給我的感覺就是非常典型的基礎(chǔ)廣度加代碼深度并重的卷子。它不會問你做過什么項(xiàng)目這種開放性話題筆試階段也沒法做深度項(xiàng)目考察所以只能把Java語言規(guī)范、JDK核心類庫、JVM基礎(chǔ)、常用算法這些硬通貨拿出來考。這些知識點(diǎn)有一個共同特點(diǎn)會就是會不會就是不會很難蒙。也正因?yàn)槿绱诉@類筆試題對科班和非科班選手的區(qū)分度極高??瓢嗤瑢W(xué)在大學(xué)課程里接觸過Java但未必系統(tǒng)刷過題培訓(xùn)班出身或者自學(xué)轉(zhuǎn)行的同學(xué)往往在框架層面知道很多反而在語言底層細(xì)節(jié)上容易栽跟頭。我的建議始終是不要因?yàn)轭}目看起來基礎(chǔ)就輕視它越是基礎(chǔ)的地方越要較真。1.2 從第二場的題型分布推斷考察權(quán)重雖然我沒有辦法把當(dāng)年原卷逐字復(fù)述出來但這類校招筆試的題型分布一直很穩(wěn)定基本可以歸納為以下幾塊考察模塊典型出題形式難度復(fù)習(xí)性價比語法與運(yùn)算符單選、代碼結(jié)果判斷低高面向?qū)ο筇匦詥芜x、多選中高集合框架單選、對比類題目中高異常處理代碼判斷題、改錯中高JVM與內(nèi)存單選、概念判斷中中枚舉與Lambda代碼題、補(bǔ)全代碼中低中排序與算法手寫代碼中高高從這個表格能看出來真正拉開差距的不是那些偏難怪的知識點(diǎn)反而是Java基礎(chǔ)里最常見的類庫和語法細(xì)節(jié)。很多同學(xué)一到手寫排序就懵或者對HashMap的底層原理說不清楚這些才是筆試失分的重災(zāi)區(qū)。所以接下來的篇幅我會重點(diǎn)講這些高頻模塊的實(shí)際考法和答題思路。2. 編譯報錯、環(huán)境變量與標(biāo)識符送分題里最容易丟分的細(xì)節(jié)2.1 源發(fā)行版17需要目標(biāo)發(fā)行版17背后的Java版本知識如果你在IDEA里寫過Java 17的代碼一定見過這樣的提示java: 警告: 源發(fā)行版 17 需要目標(biāo)發(fā)行版 17。筆試不會直接讓你配環(huán)境但它完全可以把一段Java代碼和一段Maven配置放在一起讓你判斷這段代碼為什么編譯不通過。這個問題的本質(zhì)其實(shí)是Java編譯器對source和target兩個參數(shù)的要求。source代表你寫代碼時用的語言版本target代表編譯產(chǎn)物所支持的JVM最低版本。當(dāng)你把source設(shè)置成17而target還停留在8或者更低時編譯器會認(rèn)為你要求用新版本的語法卻產(chǎn)出一個舊版本JVM能識別的字節(jié)碼這在某些語法特性上是做不到的于是就會給出上面的警告甚至直接報錯。放在筆試?yán)锼鋵?shí)是在考察你對Java版本演進(jìn)和編譯過程是否有概念。項(xiàng)目里最常見的情況是IDEA的Project Structure里把SDK切到了17但Java Compiler面板里的Target bytecode version還留在8或者M(jìn)aven的maven.compiler.source和maven.compiler.target不一致。解決辦法就是把這兩者對齊或者干脆在pom.xml里用maven.compiler.release統(tǒng)一指定版本。這類題目真正想測試的是你有沒有在實(shí)際開發(fā)中遇到過環(huán)境問題并且有沒有能力快速定位根因。很多應(yīng)屆生在簡歷上寫熟悉Java但從來沒自己配過JAVA_HOME、沒處理過編譯版本沖突遇到這種題就只能靠猜了。2.2 標(biāo)識符命名規(guī)則與運(yùn)算符優(yōu)先級更基礎(chǔ)的語言題標(biāo)識符命名規(guī)則屬于那種一看就會、一考就錯的知識點(diǎn)。Java規(guī)定標(biāo)識符由字母、數(shù)字、下劃線_和美元符$組成不能以數(shù)字開頭不能是Java關(guān)鍵字也不能是字面量true、false、null。但有一個細(xì)節(jié)大家經(jīng)常忽略Java 9開始單獨(dú)的下劃線_已經(jīng)被保留為關(guān)鍵字不能作為標(biāo)識符使用了。筆試題很喜歡在這種細(xì)節(jié)上做文章。它給你一組選項(xiàng)比如123abc、_name、$value、class、String、int_問哪些是合法標(biāo)識符。很多人一看String是類名就覺得不能用作變量名但實(shí)際上Java里的類名并不是關(guān)鍵字String s hello完全合法只是不推薦這么寫。同理int才是關(guān)鍵字int_不是。運(yùn)算符優(yōu)先級這塊就更有意思了。筆試常出這類題int x 5; int y x x; System.out.println(y);如果你算出來的結(jié)果是12說明你理解得很穩(wěn)。過程是這樣的x先用原值5參與運(yùn)算之后x變成6x先把x從6變成7再參與運(yùn)算所以結(jié)果是5加7等于12。這類題目反復(fù)出現(xiàn)的意義不是真的要你寫這種代碼而是考察你對優(yōu)先級和自增自減的理解是否清晰。2.3 環(huán)境變量配置與java命令筆試中的隱藏考點(diǎn)環(huán)境變量這塊筆試題一般不會白紙黑字問你JAVA_HOME怎么配但會出現(xiàn)下面哪個命令能正確執(zhí)行一個帶外部依賴的Java程序這類問題。這時候java -cp或者java -classpath必須出現(xiàn)在你的知識儲備里。CLASSPATH的作用是告訴JVM去哪里找類和jar包。如果你在命令行下運(yùn)行一個需要依賴第三方庫的程序只寫java Main大概率會報ClassNotFoundException。正確寫法是java -cp .:lib/commons-lang3.jar Main在Windows下路徑分隔符是分號Linux和macOS下是冒號。這個細(xì)節(jié)也是筆試喜歡挖坑的點(diǎn)選項(xiàng)里把路徑分隔符寫錯或者把-cp和-D的位置搞混。JAVA_HOME和PATH的關(guān)系也要拎清楚。JAVA_HOME通常配置成JDK安裝目錄PATH里加入%JAVA_HOME%/bin別問為什么問就是很多工具如Maven、Gradle、Tomcat默認(rèn)會從JAVA_HOME去定位Java環(huán)境。如果筆試題目描述一個java -version有輸出但javac找不到的場景你就要立刻想到PATH里可能只配了JRE的bin目錄或者配了多個版本的JDK發(fā)生了沖突。3. 面向?qū)ο?、枚舉與Lambda八股文背后的設(shè)計思維考察3.1 面向?qū)ο笕筇匦栽诠P試題中的出題形式面向?qū)ο笫荍ava筆試永遠(yuǎn)繞不開的主題但考的絕不是什么是封裝這種問答題而是代碼層面的理解。比如多態(tài)的經(jīng)典題目class Animal { public void shout() { System.out.println(animal); } } class Dog extends Animal { Override public void shout() { System.out.println(dog); } } Animal a new Dog(); a.shout();輸出結(jié)果是dog這一點(diǎn)大多數(shù)人都知道。但如果把一個成員變量也設(shè)計成同名比如父類和子類都有String name再通過父類引用調(diào)用a.name那訪問到的就是父類的name因?yàn)镴ava中成員變量不存在多態(tài)只有方法才存在多態(tài)。這種變量與方法行為不一致的題目命中率特別高。還有重載和重寫的區(qū)別。筆試題會給你一堆方法簽名讓你判斷哪些能構(gòu)成重載、哪些是重寫、哪些是非法定義。核心判斷標(biāo)準(zhǔn)只有兩條方法名相同且參數(shù)列表不同是重載方法簽名相同且返回類型兼容、訪問權(quán)限不縮小是重寫。別被throws異常、泛型這些次要點(diǎn)干擾。3.2 枚舉類型不只是常量類很多同學(xué)對枚舉的理解停留在用來定義常量這個層面其實(shí)Java的枚舉是個完整類它有構(gòu)造器、字段、方法甚至可以繼承接口。筆試中常見的一個坑是問枚舉是否可以使用比較答案是可以因?yàn)槊總€枚舉常量在JVM中只有一個實(shí)例和equals效果相同。再比如這個場景定義一個帶屬性的枚舉。enum Status { SUCCESS(200, 成功), ERROR(500, 失敗); private final int code; private final String msg; Status(int code, String msg) { this.code code; this.msg msg; } public int getCode() { return code; } }這種寫法在很多項(xiàng)目里其實(shí)很常見筆試也喜歡讓你根據(jù)枚舉定義反推構(gòu)造器調(diào)用方式或者判斷values()和valueOf()的用法。values()是編譯器自動生成的方法返回所有枚舉常量數(shù)組valueOf(String)可以根據(jù)名字找到對應(yīng)常量找不到會拋IllegalArgumentException。還有一個冷門考點(diǎn)枚舉類型是可以用在switch里的Java會為你做類似switch on Enum的優(yōu)化直接按枚舉常量跳轉(zhuǎn)效率很高。3.3 Lambda與函數(shù)式接口代碼怎么寫才符合規(guī)范Java 8的Lambda表達(dá)式是面試和筆試的???。要理解Lambda先理解函數(shù)式接口只有一個抽象方法的接口比如Runnable、Comparator、Consumer。Lambda表達(dá)式本質(zhì)上就是這個接口的匿名實(shí)現(xiàn)對象。筆試?yán)镪P(guān)于Lambda的坑主要在兩個地方。第一個lambda表達(dá)式里引用的局部變量必須是effectively final也就是變量初始化之后不再被修改。如果你在lambda內(nèi)部嘗試修改外部局部變量的值編譯器會直接給error。第二個方法引用的寫法。list.sort(Comparator.comparingInt(String::length))和list.sort((a, b) - a.length() - b.length())表達(dá)的是同一個意思但前者可讀性更強(qiáng)也顯得你更懂函數(shù)式編程。還有一個實(shí)用場景經(jīng)常出現(xiàn)在筆試題里把某個指定元素排到集合最前面。很多人第一時間想的是遍歷然后remove再add但其實(shí)用Comparator一行就能寫完list.sort(Comparator .comparing((String s) - !target.equals(s)) .thenComparing(Comparator.naturalOrder()));思路是利用boolean排序false自然排在true前面所以target.equals(s)的結(jié)果為true的元素反而會被排在后面。那我們先讓條件對目標(biāo)元素為false即!target.equals(s)目標(biāo)元素就排到最前了。這個寫法很巧妙筆試?yán)锬芙o閱卷人留下不錯的印象。3.4 Lombok編譯期警告注解處理器的延伸考點(diǎn)熱搜詞里有一條很經(jīng)典的報錯you arent using a compiler supported by lombok, so lombok will not work with your project。這個報錯的意思是Lombok注解處理器在當(dāng)前編譯環(huán)境中沒有生效導(dǎo)致它無法在編譯期幫你生成getter、setter、構(gòu)造器等方法。為什么筆試會關(guān)注這個因?yàn)長ombok涉及注解的RetentionPolicy。Java注解的保留策略有SOURCE、CLASS、RUNTIME三種。Lombok的注解之所以能在編譯期自動生成代碼靠的正是自定義注解處理器Annotation Processor它作用于編譯階段最終生成的代碼也不會出現(xiàn)在源碼里。當(dāng)你用IDE運(yùn)行時需要在Annotation Processing里開啟對應(yīng)選項(xiàng)當(dāng)你用Maven編譯時需要確保依賴和插件配置正確。這類題目真正考察的是你不僅會用Lombok還知道它為什么能這么用。知道了注解處理器和編譯期代碼生成這件事以后遇到其他類似庫比如MapStruct也就不會覺得神秘了。4. 集合框架與容器從ArrayList到HashMap的追問邏輯4.1 容器類的基本功橫向?qū)Ρ阮}怎么答集合框架在筆試?yán)镎急炔恍〕鲱}風(fēng)格也非常穩(wěn)定要么橫向?qū)Ρ纫瓷钔谠创a。橫向?qū)Ρ茸畛R姷木褪茿rrayList和LinkedList。ArrayList底層是Object數(shù)組查詢快按下標(biāo)訪問時間復(fù)雜度O(1)中間插入或刪除需要搬運(yùn)元素是O(n)。LinkedList底層是雙向鏈表插入和刪除在頭尾時是O(1)但隨機(jī)訪問要遍歷也是O(n)。一旦題目加一個限定條件頻繁在中間插入刪除答案就要傾向LinkedList如果題目強(qiáng)調(diào)隨機(jī)訪問多、內(nèi)存占用小那就選ArrayList。看清楚題目給的應(yīng)用場景再答別背結(jié)論。線程安全容器也是高頻考點(diǎn)。HashMap不是線程安全的Hashtable是線程安全的但它的所有方法都加了synchronized鎖并發(fā)效率很低。ConcurrentHashMap是Java 5之后推薦的并發(fā)容器JDK 8之后底層用數(shù)組加鏈表加紅黑樹鎖粒度從分段鎖優(yōu)化到了單個數(shù)組桶的synchronized加CAS并發(fā)性能提升非常明顯。筆試如果問哪種容器更適合高并發(fā)場景下的鍵值存儲答案通常是ConcurrentHashMap。4.2 HashMap源碼細(xì)節(jié)為什么它是常青樹HashMap是集合框架里被問得最頻繁的一個筆試題可以從多個角度切入。JDK 1.8之后HashMap的底層結(jié)構(gòu)是數(shù)組加鏈表加紅黑樹。put一個鍵值對時先通過鍵的hashCode計算數(shù)組下標(biāo)真正存儲時會先對key的hash做一次擾動處理即h ^ (h 16)目的是讓高16位也參與下標(biāo)計算降低哈希沖突概率。如果多個key落在同一個數(shù)組桶里就以鏈表形式串起來當(dāng)鏈表長度達(dá)到8并且數(shù)組長度達(dá)到64時鏈表會轉(zhuǎn)換成一棵紅黑樹把查找時間從O(n)降到O(logn)。這些數(shù)字和條件很容易考但它們背后的邏輯更重要。為什么是8因?yàn)榧t黑樹的節(jié)點(diǎn)對象比普通鏈表節(jié)點(diǎn)大在哈希函數(shù)足夠分散的前提下桶中鏈表長度到8的概率很低所以只有在真的出現(xiàn)大量沖突時才升級成樹用空間換時間。為什么數(shù)組長度要滿64因?yàn)槿绻麛?shù)組還不夠大優(yōu)先擴(kuò)容把元素重新散列可能比轉(zhuǎn)樹更劃算。默認(rèn)容量16和負(fù)載因子0.75也是高頻考點(diǎn)。負(fù)載因子0.75意味著當(dāng)元素數(shù)量達(dá)到容量乘以0.75時會觸發(fā)擴(kuò)容擴(kuò)容后容量翻倍所有元素要重新計算下標(biāo)位置這個過程叫rehash。0.75是時間和空間成本之間的一個折中值太小了容易頻繁擴(kuò)容浪費(fèi)空間太大了雖然省空間但沖突概率升高影響查找效率。這個為什么是0.75的說明是答案里最體現(xiàn)水平的加分項(xiàng)。4.3 Comparator與Comparable讓排序題更穩(wěn)的底層理解排序是筆試手寫題的家常便飯而懂排序不只是會寫快排還要會用Comparable和Comparator去控制排序規(guī)則。Comparable讓一個類具備天生可比較的能力需要實(shí)現(xiàn)compareTo方法比如Integer、String都是這么做的。Comparator則是外部比較器你可以在不修改類源碼的情況下臨時定義多種排序規(guī)則。兩者并不沖突使用場景不同如果這個類本身就應(yīng)該有自然順序用Comparable如果同一類在不同業(yè)務(wù)場景有不同排序方式用Comparator更合適。手寫Comparator時有個常見錯誤是返回值為a - b當(dāng)a和b非常接近Integer.MAX_VALUE時可能溢出。筆試?yán)锟梢怨室饨o你一段return o1.age - o2.age;的代碼問你有沒有問題。最佳做法是用Integer.compare(o1.age, o2.age)這看起來是小事但能體現(xiàn)你對邊界情況的敏感度。還有一條如果想讓排序結(jié)果穩(wěn)定要避免在比較邏輯里返回0以外的隨機(jī)值Java的List.sort是穩(wěn)定排序相等的元素能保持原始相對順序但這個前提是你的比較器在相等時返回0。5. 內(nèi)存溢出、數(shù)組越界與異常體系JVM題目答題框架5.1 OutOfMemoryError和數(shù)組越界到底考什么java.lang.OutOfMemoryError在熱搜詞里也出現(xiàn)過它是很多生產(chǎn)環(huán)境事故的元兇筆試當(dāng)然不會放過。常見的內(nèi)存溢出場景可以分成幾類Java堆溢出通常是創(chuàng)建了太多對象且無法回收元空間溢出JDK 8之后的方法區(qū)實(shí)現(xiàn)通常是動態(tài)生成類或加載了太多類線程棧溢出表現(xiàn)為StackOverflowError通常由無遞歸結(jié)束條件導(dǎo)致。hotspot VM里有個經(jīng)典現(xiàn)象是容器環(huán)境下報insufficient memory它往往不是Java堆不夠而是操作系統(tǒng)層面沒有足夠的內(nèi)存給JVM分配比如容器內(nèi)存限制和JVM啟動參數(shù)里的-Xmx不匹配。這道題出現(xiàn)在筆試?yán)锲鋵?shí)是在考察你對JVM運(yùn)行數(shù)據(jù)區(qū)域的理解堆、虛擬機(jī)棧、本地方法棧、方法區(qū)、程序計數(shù)器各自存什么誰會出現(xiàn)哪種異常必須對號入座。數(shù)組越界是另一個高頻異常。它的Java異常類是ArrayIndexOutOfBoundsException屬于RuntimeException也就是傳說中的非受檢異常。筆試如果給一段循環(huán)遍歷數(shù)組的代碼問你為什么拋異常十有八九是循環(huán)邊界寫錯。比如for (int i 0; i arr.length; i) { System.out.println(arr[i]); }i等于arr.length時arr[i]已經(jīng)越界。這類錯誤之所以高頻是因?yàn)槊總€人寫循環(huán)時都容易差一個邊界。筆試手寫代碼時養(yǎng)成用i arr.length的小習(xí)慣能幫你避開很多坑。5.2 手寫異常處理代碼的正確姿勢異常處理不只要認(rèn)識異常類還要會寫正確的處理代碼。筆試常出這樣的代碼題判斷下面代碼是否能編譯通過或者輸出什么。try-with-resources是Java 7引入的語法用來簡化資源關(guān)閉。筆試經(jīng)常問下面這段代碼執(zhí)行完后被打開的文件流會自動關(guān)閉嗎t(yī)ry (FileInputStream in new FileInputStream(a.txt)) { // 處理 }答案是完全會而且不需要finally塊。try-with-resources要求資源類實(shí)現(xiàn)AutoCloseable接口這里還有一個細(xì)節(jié)catch塊和finally塊仍然可以跟在try-with-resources后面且多個資源會按照聲明順序的逆序關(guān)閉。catch子句的順序也是個經(jīng)典考點(diǎn)。多異常catch時子類異常必須放在父類異常前面。比如同時catch IOException和Exception如果IOException寫在Exception后面編譯器直接報錯。原因是如果父類在前子類異常永遠(yuǎn)輪不到被捕獲這屬于不可達(dá)代碼。另外try、catch、finally里都有return時finally中的return會覆蓋try或catch中的return如果不寫return只修改返回值finally的操作也會在方法返回前執(zhí)行但具體返回值是finally修改后的值還是修改前的值要區(qū)分基本類型和引用類型這種題很容易讓粗心的人翻車。5.3 筆試題里JVM相關(guān)選擇題的常見陷阱JVM不是一門語言但Java筆試基本都會帶幾道JVM題。最??嫉氖莾?nèi)存區(qū)域劃分、類加載過程、可達(dá)性分析。類加載過程是加載、驗(yàn)證、準(zhǔn)備、解析、初始化五個階段。其中準(zhǔn)備階段會為類的靜態(tài)變量分配內(nèi)存并設(shè)置初始值比如static int a 10在準(zhǔn)備階段a的值是0到初始化階段才賦值為10。這個兩次賦值的細(xì)節(jié)是筆試選擇題非常愛挖的點(diǎn)。GC相關(guān)的題目則集中在可達(dá)性分析上。哪些對象可以作為GC Roots常見的有虛擬機(jī)棧中引用的對象、靜態(tài)變量引用的對象、本地方法棧中Native方法引用的對象。只有被GC Roots直接或間接引用的對象才會被標(biāo)記為存活其他對象會被判定為可回收。這里有個冷門陷阱兩個對象互相循環(huán)引用但它們都不再被GC Roots引用這種情況下兩者都會被回收。面試中經(jīng)常把這個問題包裝成引用計數(shù)法的缺陷筆試同樣可能考到。答題框架其實(shí)很簡單先把內(nèi)存區(qū)域按線程隔離與否分清楚再把各區(qū)域會拋什么異常對應(yīng)上最后能用一句話說清可達(dá)性分析大部分JVM選擇題就能穩(wěn)拿分了。6. 冒泡排序與快速排序手寫代碼題的滿分書寫規(guī)范6.1 冒泡排序的優(yōu)化寫法與復(fù)雜度推導(dǎo)手寫冒泡排序是很多Java筆試題的第一道代碼題因?yàn)樗T檻低但可以玩的花樣不少?;A(chǔ)寫法很簡單兩層循環(huán)相鄰比較大的往后冒。但如果你直接按最樸素方式寫只能算及格因?yàn)椴簧匍喚砣诉€想看到優(yōu)化意識。優(yōu)化手段是加一個布爾標(biāo)志位。如果某一輪循環(huán)中沒有任何元素交換說明整個數(shù)組已經(jīng)有序可以提前終止。public static void bubbleSort(int[] arr) { if (arr null || arr.length 2) { return; } for (int i 0; i arr.length - 1; i) { boolean swapped false; for (int j 0; j arr.length - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped true; } } if (!swapped) { break; } } }為什么最好情況時間復(fù)雜度是O(n)因?yàn)楫?dāng)數(shù)組已經(jīng)是有序的時候第一輪掃描一個元素都沒交換循環(huán)直接退出掃描了n個元素所以是O(n)。最壞和平均都是O(n^2)。冒泡排序是穩(wěn)定排序因?yàn)橄嗟鹊脑夭粫粨Q位置這是它和選擇排序的重要區(qū)別。筆試如果追問穩(wěn)定性的含義和應(yīng)用場景可以說是穩(wěn)定排序的算法在按多個字段排序時不會破壞前一個字段已經(jīng)排好的相對順序。6.2 快速排序?qū)崿F(xiàn)時最容易被扣分的三個點(diǎn)快速排序是筆試高頻手寫題難度比冒泡高不少。最常見的寫法是用Lomuto分區(qū)方案選取最右邊的元素作為pivot然后遍歷分區(qū)。public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivotIndex partition(arr, left, right); quickSort(arr, left, pivotIndex - 1); quickSort(arr, pivotIndex 1, right); } private static int partition(int[] arr, int left, int right) { int pivot arr[right]; int i left; for (int j left; j right; j) { if (arr[j] pivot) { swap(arr, i, j); i; } } swap(arr, i, right); return i; } private static void swap(int[] arr, int i, int j) { int tmp arr[i]; arr[i] arr[j]; arr[j] tmp; }這個寫法能跑通但容易在三個地方被扣分。第一遞歸終止條件只寫一個left right不夠還要考慮數(shù)組為空或者長度為1的邊界場景。上面的代碼里quickSort函數(shù)入口雖然沒有顯式判斷arr是否為null但調(diào)用方如果傳入nullpartition里直接取arr[right]會空指針。筆試閱卷人很看重邊界處理建議在quickSort里加一個if (arr null || arr.length 2) return;的守衛(wèi)。第二分區(qū)循環(huán)里用的是if (arr[j] pivot)而不是。如果寫成等于pivot的元素會頻繁交換雖然不是致命錯誤但會讓人懷疑你沒理解分區(qū)邏輯的語義。第三遞歸深度。當(dāng)輸入數(shù)組已經(jīng)有序且每次都選最后一個元素當(dāng)pivot時快速排序會退化到O(n^2)而且遞歸深度可能接近數(shù)組長度導(dǎo)致棧溢出。筆試的時候如果時間允許可以補(bǔ)一句在工程實(shí)現(xiàn)上可以隨機(jī)選擇pivot或三數(shù)取中避免退化這屬于加分表述能讓閱卷人看出你學(xué)過而不僅僅是背過。平均時間復(fù)雜度是O(nlogn)最壞是O(n^2)空間復(fù)雜度因?yàn)檫f歸調(diào)用是O(logn)??焖倥判蚴遣环€(wěn)定排序這一點(diǎn)也常被拿來出選擇題。6.3 筆試手寫代碼的時間分配與檢查順序手寫代碼題在有限時間內(nèi)最容易犯的錯反而不是算法不會而是低級錯誤。我見過太多次思路完全正確但因?yàn)檠h(huán)邊界少寫一個等于號導(dǎo)致數(shù)組越界判卷直接扣大分的案例。我的建議是先花30秒讀清楚題目要求確認(rèn)輸入輸出格式再動手寫主干邏輯最后補(bǔ)邊界條件寫完后用一兩個簡單用例在腦子里走一遍。檢查順序可以是數(shù)組是否可能為null、長度為0、長度為1循環(huán)邊界是否可能出現(xiàn)等于length的情況遞歸終止條件是否一定被觸發(fā)swap操作是否因?yàn)閮蓚€下標(biāo)相同而出問題。這些檢查點(diǎn)看起來瑣碎但真能幫你挽回不少分。筆試和日常開發(fā)不一樣日常有編譯器幫你兜底筆試全是裸寫。代碼的可讀性也很重要變量名別用a、b、c敷衍用arr、left、right、pivotIndex這種能看懂的名字閱卷印象分完全不同。7. 針對第二場這類筆試的復(fù)習(xí)路線與臨場節(jié)奏7.1 一份可執(zhí)行的Java基礎(chǔ)復(fù)習(xí)清單如果你準(zhǔn)備的時間還比較充裕建議按照下面這個順序過知識點(diǎn)這套順序基本對應(yīng)試卷的難度遞增Java語法標(biāo)識符、基本數(shù)據(jù)類型、運(yùn)算符、流程控制、數(shù)組。面向?qū)ο箢?、對象、封裝、繼承、多態(tài)、接口、抽象類、重載、重寫。常用類庫String、StringBuilder、包裝類、枚舉、日期時間API。集合框架List、Set、Map的實(shí)現(xiàn)類與底層結(jié)構(gòu)重點(diǎn)啃HashMap。異常體系Throwable、Error、Exception、RuntimeException、try-with-resources。JVM基礎(chǔ)運(yùn)行時數(shù)據(jù)區(qū)域、類加載過程、GC、內(nèi)存溢出場景。Java 8特性Lambda、Stream、函數(shù)式接口、方法引用、Optional。手寫算法冒泡、快排、二分查找、單鏈表反轉(zhuǎn)、括號匹配。每一步都配點(diǎn)頭量練習(xí)。??途W(wǎng)的Java專項(xiàng)練習(xí)刷選擇題很合適LeetCode的熱題100可以覆蓋手寫代碼題JavaGuide這類開源資料適合梳理知識框架。不建議上來就刷一堆偏難怪題筆試考的還是金字塔底部那部分。7.2 臨場做題的節(jié)奏建議校招筆試通常有時間限制合理分配時間比盲目搶時間重要得多。選擇題遇到拿不準(zhǔn)的先標(biāo)記跳過別在一道題上卡超過兩分鐘。整套卷子里手寫代碼題的分值通常最高至少要留出三分之一的時間處理。一個比較穩(wěn)的節(jié)奏是快速掃描全卷先做有把握的題建立信心再做需要動腦的計算題和代碼閱讀題最后集中攻克手寫題。手寫題如果一時間沒有完整思路先把框架搭出來方法簽名、主循環(huán)、遞歸或迭代的主體結(jié)構(gòu)再逐步完善細(xì)節(jié)。閱卷時通常按步驟給分一個能跑通主干但缺邊界處理的實(shí)現(xiàn)遠(yuǎn)好過一個寫了一半的猜想。7.3 我踩過的坑和最后想說的經(jīng)驗(yàn)在我接觸過的應(yīng)屆生里筆試翻車的常見原因往往不是不會而是基礎(chǔ)細(xì)節(jié)掌握得不精確。有人能背出HashMap的擴(kuò)容流程卻說不清i arr.length為什么不能寫成i arr.length有人能大談微服務(wù)架構(gòu)卻寫不對一個簡單的深拷貝。這類金字塔塔尖很尖、地基很虛的情況在校招筆試?yán)锾貏e吃虧。我的真實(shí)感受是筆試準(zhǔn)備這件事沒有捷徑真題和熱詞只能告訴你考什么真正的得分點(diǎn)在于你能不能把每一個常見知識點(diǎn)都理解到能講給別人聽的程度。我背八股文的方法很簡單遇到一個知識點(diǎn)試著用大白話給自己講一遍如果講著講著卡殼了說明這里還不夠熟就回去重新看。最后再分享一個小技巧準(zhǔn)備筆試時自己把??嫉氖謱戭}整理一個模板庫每道題都標(biāo)清楚時間復(fù)雜度和邊界條件??记耙煌聿灰⑿骂}只看模板。這套方法幫我度過了好幾場校招筆試也希望對你有用。