復(fù)盤:那些被你忽略的底層原理與常見知識盲區(qū))
1. 為什么這些知識點(diǎn)總被我們順手漏掉最近在做技術(shù)復(fù)盤的時候我突然意識到一個挺尷尬的事實(shí)很多知識點(diǎn)我并不是不會而是它們被我條件反射式地跳過了。比如說你天天寫0.1 0.2知道結(jié)果不對但要是真讓你講講為什么不對、怎么在業(yè)務(wù)里規(guī)避很多人會卡殼。我管這類東西叫最近遺漏的知識點(diǎn)——不是剛學(xué)的忘記了而是學(xué)得太早、用得太順結(jié)果連原理都被順手丟在了角落。1.1 不是不會是知識變成了條件反射人在長期使用一個東西以后會進(jìn)入一種自動駕駛狀態(tài)。就像你開車時不記得自己什么時候換過擋寫代碼時也不記得自己為什么會寫出if (x null) throw ...這種判斷。知識一旦變成條件反射就很難被重新審視。我舉個非常典型的例子幾乎每個后端開發(fā)都能脫口而出HTTP是 80 端口、HTTPS是 443 端口。但你再追問一句HTTPS 建立連接的時候第一步先做什么很多人會愣住然后含糊地說先加密。加密什么用什么加密公鑰從哪來證書是怎么驗(yàn)證的這些問題就是典型的遺漏知識點(diǎn)——它們躺在你大腦的某個角落但你已經(jīng)很久沒有調(diào)用過它們了。這類知識點(diǎn)有幾個共同特征太基礎(chǔ)基礎(chǔ)到你覺得不用想就知道于是真的不再想。太常用每天在用反而從來沒去查過底層機(jī)制。被高層工具遮蔽框架幫你封裝好了你只調(diào)接口不看實(shí)現(xiàn)。我在復(fù)盤的時候給自己定了一個規(guī)矩凡是能毫不猶豫說出結(jié)論但講不出推導(dǎo)過程的知識點(diǎn)全部算遺漏。這個標(biāo)準(zhǔn)聽起來苛刻但確實(shí)能幫你挖出不少平時懶得深想的東西。1.2 夠用就好與知識盲區(qū)的形成另一個隱蔽的原因是夠用就好的心態(tài)。很多知識點(diǎn)我們只掌握了夠用的那一層下面還有一整座冰山?jīng)]看過。拿 Redis 來說絕大多數(shù)人都知道它是內(nèi)存數(shù)據(jù)庫快支持多種數(shù)據(jù)結(jié)構(gòu)。但問一個稍微深入一點(diǎn)的問題Redis 的字符串類型底層到底用的是 SDS簡單動態(tài)字符串還是直接 C 字符串為什么不用 C 字符串這時候很多人就沉默了。平時用 Redis 做緩存、做分布式鎖確實(shí)不需要關(guān)心底層實(shí)現(xiàn)但這些底層設(shè)計(jì)決定了它為什么快、為什么能支持某些操作這是排查性能問題時的關(guān)鍵線索。我把這個現(xiàn)象總結(jié)成一個知識水位模型掌握層次典型表現(xiàn)算不算會聽說過知道名字不知道干嘛的不算會用能寫對 API能調(diào)通流程算基礎(chǔ)會懂原理能講清底層機(jī)制與取舍算真會能優(yōu)化能基于原理解決疑難問題算精通大部分人的會停留在第二層。而最近遺漏的知識點(diǎn)恰恰就藏在第三、第四層的區(qū)域里。你不需要每個知識點(diǎn)都精通但你得知道自己遺漏了哪些。知道自己不知道這本身就是一種很稀缺的能力。2. 我的查漏補(bǔ)缺思路與整理框架發(fā)現(xiàn)遺漏只是第一步怎么系統(tǒng)地把它們撈回來才是重點(diǎn)。我以前也犯過收藏即學(xué)會的毛病——看到一篇好文章先收藏收藏完就再也不看了。這次我用了一套新的整理方法實(shí)測下來效率高很多。2.1 用主動回憶代替被動翻閱主動回憶Active Recall是一個認(rèn)知心理學(xué)里的老概念但在技術(shù)學(xué)習(xí)里特別管用。最簡單的做法是不看書、不搜資料直接在紙上或者文檔里把你腦子里認(rèn)為的知識結(jié)構(gòu)畫出來、寫出來。我自己的操作流程是這樣打開一個空白 Markdown 文件。寫下最近半年工作中高頻使用的所有技術(shù)名詞HTTP、HTTPS、Redis、數(shù)據(jù)庫索引、TCP、進(jìn)程線程、哈希、JVM 或 Node.js 事件循環(huán)看你的技術(shù)棧。對每個名詞用 30 秒時間憑記憶寫下我知道了什么越具體越好。寫完以后再逐個去翻文檔或者源碼把寫錯、寫漏、寫模糊的地方全部標(biāo)紅。這個過程非常裸——你沒有參考只能依賴大腦里真正存下來的東西。很多遺漏知識點(diǎn)在這個環(huán)節(jié)就會暴露出來。比如我寫 TCP 的時候只寫了三次握手但為什么是三次而不是兩次這個關(guān)鍵問題我完全沒寫出來。這就說明我根本沒理解握手的意義只是背住了結(jié)論。2.2 以面試題和教學(xué)場景作為照妖鏡另一個很有效的方法是給自己出題或者拿經(jīng)典面試題來檢驗(yàn)。我不太建議直接去背面試題而是建議把面試題當(dāng)成知識盲區(qū)的探測針。比如這些題為什么浮點(diǎn)數(shù)不能直接用比較數(shù)據(jù)庫索引為什么會失效最左前綴匹配到底是什么意思HashMap的默認(rèn)大小為什么是 16而不是 10進(jìn)程和線程的根本區(qū)別是什么協(xié)程又是怎么回事如果一個接口變慢了你會從幾個方向排查這些問題有一個共同點(diǎn)它們都在逼你解釋為什么。如果你只能答出就是這樣記住就行那恭喜你你找到了一個遺漏的知識點(diǎn)。我還特別喜歡用能不能講給別人聽來檢驗(yàn)自己的掌握程度。有一次我嘗試不查資料白板講一遍 HTTPS 的連接過程講了三分鐘就開始卡殼——證書鏈驗(yàn)證那塊我根本沒搞明白。于是我老老實(shí)實(shí)回去翻了 RFC 文檔和相關(guān)博客把那塊補(bǔ)上。教是最好的學(xué)這句話在查漏補(bǔ)缺的場景里尤其成立。2.3 建立自己的知識索引卡片很多人做筆記喜歡大段大段摘抄結(jié)果整個筆記變成一本書的復(fù)印件看起來厚實(shí)際上沒法用。我的做法是建立知識索引卡片每張卡片只寫一個知識點(diǎn)結(jié)構(gòu)固定為一句話結(jié)論用最簡單的話說明這個知識點(diǎn)是什么。底層原理寫清楚關(guān)鍵機(jī)制最好帶一張手畫示意圖或簡單代碼。常見坑點(diǎn)這個知識點(diǎn)經(jīng)常導(dǎo)致什么問題。一句話類比用生活場景幫助自己快速回憶。比如我之前對哈希沖突的理解就很模糊每次聽到開放尋址鏈地址法都似懂非懂。后來我用卡片的形式整理了一遍寫了一句話類比哈希表就像一排儲物柜哈希函數(shù)決定你的東西放哪個柜子沖突就是兩個人看中了同一個柜子。開放尋址是去旁邊找空柜子鏈地址法是同一個柜子里多掛幾個袋子。寫完這張卡片以后再遇到HashMap的源碼分析我理解起來就順暢多了。這種卡片的好處是丟失的只是記憶的檢索路徑卡片可以幫你重建路徑而不是重新學(xué)一遍整個知識體系。3. 最近梳理出的幾個典型遺漏知識點(diǎn)接下來這部分是全文的重頭戲。我把自己最近整理卡片時發(fā)現(xiàn)的幾個典型遺漏知識點(diǎn)列出來不追求大而全只講那些平時以為會、實(shí)際沒搞懂的東西。3.1 浮點(diǎn)數(shù)精度不只是0.1 0.2的問題很多人知道0.1 0.2 ! 0.3但問為什么只能答出浮點(diǎn)數(shù)有精度問題。這個答案太籠統(tǒng)了真正的關(guān)鍵是浮點(diǎn)數(shù)的二進(jìn)制表示本身就是不精確的。0.1轉(zhuǎn)換成二級制小數(shù)是什么是一個無限循環(huán)小數(shù)。就像十進(jìn)制里1/3 0.3333...永遠(yuǎn)寫不完一樣二進(jìn)制里0.1也寫不完。計(jì)算機(jī)的存儲位數(shù)是有限的所以只能截?cái)嘟財(cái)嗑彤a(chǎn)生了誤差。在 JS 里0.1 0.2的結(jié)果是0.30000000000000004是因?yàn)閮蓚€不精確的數(shù)相加誤差被放大了。在 Java 里float和double同樣有這個問題。實(shí)操建議金額計(jì)算千萬不要用float/double要用BigDecimalJava、DecimalPython或整數(shù)分單位存儲。比較浮點(diǎn)數(shù)時不要用用差值小于一個極小閾值如Math.abs(a - b) 1e-9。如果數(shù)據(jù)庫存金額用decimal類型不要用float。這個知識點(diǎn)我早就知道結(jié)論但直到最近整理為什么的底層邏輯時我才真正意識到精度問題不是某個語言的 bug而是 IEEE 754 標(biāo)準(zhǔn)下所有二進(jìn)制浮點(diǎn)數(shù)的宿命。理解了這一點(diǎn)看任何語言的浮點(diǎn)坑都一通百通。3.2 HTTP 與 HTTPS 的底細(xì)別只記端口號我剛才提到 HTTPS 的過程這里展開講一下。很多人能流利說出HTTPS 比 HTTP 多了 SSL/TLS 加密但再深入一層就卡殼。HTTPS 的核心是混合加密傳輸數(shù)據(jù)用對稱加密速度快但對稱加密的密鑰需要用非對稱加密來傳遞。整個連接大致分這幾步客戶端發(fā)起請求告訴服務(wù)器自己支持的加密協(xié)議版本。服務(wù)器返回自己的證書證書里有公鑰??蛻舳蓑?yàn)證證書是否可信看證書鏈?zhǔn)欠裼墒苄湃蔚?CA 簽發(fā)、域名是否匹配、是否過期。驗(yàn)證通過后客戶端生成一個隨機(jī)對稱密鑰用服務(wù)器公鑰加密后發(fā)給服務(wù)器。服務(wù)器用自己的私鑰解開得到對稱密鑰。后續(xù)雙方都用這個對稱密鑰加密通信。這里的證書驗(yàn)證是最容易被遺漏的環(huán)節(jié)。以前我以為瀏覽器看到 HTTPS 就代表安全其實(shí)不是——HTTPS 不等于絕對安全它依賴于證書系統(tǒng)。如果證書鏈有問題或者某個 CA 被攻破HTTPS 的保護(hù)就會打折扣。這個知識在實(shí)際開發(fā)和接口聯(lián)調(diào)中有個很實(shí)際的用處當(dāng)你遇到HTTPS 證書過期導(dǎo)致接口請求失敗的時候你能快速定位問題而不是一臉懵地重啟服務(wù)。我印象很深的一次是生產(chǎn)環(huán)境突然報了一堆證書錯誤同事都以為是網(wǎng)絡(luò)問題我檢查了證書有效期發(fā)現(xiàn)正好過期續(xù)期后馬上恢復(fù)。這就是懂原理和只會用的差別。3.3 數(shù)據(jù)庫索引的本質(zhì)與失效場景索引是大廠面試必問、日常開發(fā)必修的知識點(diǎn)。但絕大多數(shù)人停留在建索引能讓查詢變快的層面。我這次復(fù)盤把索引的知識點(diǎn)重新理了一遍挑出兩個最容易被漏掉的部分。第一個索引為什么能快索引的底層主要是 B 樹。B 樹的優(yōu)勢在于它是多路平衡搜索樹樹的高度很低。一個三層高的 B 樹就能存儲幾百萬條數(shù)據(jù)意味著查詢時最多只需要做幾次磁盤 I/O就能定位到目標(biāo)。而全表掃描相當(dāng)于一本書從頭讀到尾索引相當(dāng)于配合目錄去翻頁。你以為你在用目錄其實(shí)你用的是 B 樹的葉子節(jié)點(diǎn)鏈表——這個細(xì)節(jié)就足夠聊半天了。第二個索引為什么會失效這是最容易在實(shí)際中踩坑的。常見的失效場景包括對索引列使用函數(shù)或計(jì)算比如WHERE YEAR(create_time) 2024索引會失效。隱式類型轉(zhuǎn)換比如索引列是字符串但查詢條件傳了數(shù)字可能無法走索引。不符合最左前綴匹配原則。如果你建了(a, b, c)聯(lián)合索引查詢條件是b 1 AND c 2這個索引基本用不上因?yàn)樘^了最左的a。使用LIKE %關(guān)鍵詞這種前置模糊匹配大概率失效。我身邊的真實(shí)案例一個列表頁比較慢排查后發(fā)現(xiàn)查詢條件里寫了WHERE status 1 AND DATE_FORMAT(create_time, %Y-%m-%d) 2025-01-01。這個DATE_FORMAT直接讓create_time的索引失效改成create_time 2025-01-01 AND create_time 2025-01-02之后查詢從 800ms 降到 20ms。這就是典型的沒搞懂原理靠直覺寫 SQL。3.4 哈希沖突你以為的 O(1) 其實(shí)是 O(n)HashMap的get操作號稱平均 O(1)但這個前提是哈希函數(shù)足夠均勻。一旦發(fā)生哈希沖突最壞情況下多個 key 落在同一個桶里鏈表的查詢會退化到 O(n)。Java 8 里鏈表長度超過 8 會轉(zhuǎn)紅黑樹就是為了緩解這個問題。但很多人不知道的是自定義對象的 hashCode 寫得不好會導(dǎo)致 HashMap 性能災(zāi)難性下降。比如兩個對象返回相同的 hashCode每次查詢都會遍歷鏈表。我在一次代碼評審里見過一個類把hashCode()固定返回1當(dāng)時那個接口平時數(shù)據(jù)量小看不出來上線后數(shù)據(jù)量一大接口直接超時。排查半天才發(fā)現(xiàn)是 HashMap 里的鏈表已經(jīng)長到幾十萬每次 get 都是線性掃。所以這個知識點(diǎn)的實(shí)操結(jié)論是重寫equals()一定要重寫hashCode()。hashCode()要盡量分散別用太簡單的規(guī)則。如果 key 是自定義對象可以直接用不可變對象比如String、Integer做 key別自己造輪子。哈希沖突還有一個衍生知識點(diǎn)HashMap 的默認(rèn)容量是 16負(fù)載因子是 0.75。這個16和0.75背后是有講究的。0.75 是空間和時間的一個折中太大容易頻繁沖突太小浪費(fèi)空間。很多人只記住這兩個數(shù)字不理解它們是經(jīng)驗(yàn)的平衡點(diǎn)遇到為什么默認(rèn) 16這種追問就露餡了。4. 實(shí)操過程我是怎么把知識點(diǎn)真正補(bǔ)進(jìn)腦子里的知道了遺漏的知識點(diǎn)怎么把它們真正變成自己的這部分的實(shí)操經(jīng)驗(yàn)是我最近覺得最有價值的也是建議大家直接抄作業(yè)的部分。4.1 把知識點(diǎn)寫進(jìn)代碼驗(yàn)證一次光看文章、做筆記知識點(diǎn)永遠(yuǎn)是別人的知識。我的做法是把每個遺漏的知識點(diǎn)變成一個最小可運(yùn)行的小例子親自跑一遍。比如浮點(diǎn)數(shù)精度問題我寫了一個 Java 小程序BigDecimal a new BigDecimal(0.1); BigDecimal b new BigDecimal(0.2); System.out.println(a.add(b)); // 0.3 double c 0.1; double d 0.2; System.out.println(c d); // 0.30000000000000004運(yùn)行結(jié)果一擺出來原理就變成很直觀的感受。再比如 TCP 的三次握手我沒有裝 Wireshark直接在一臺 Linux 服務(wù)器上用了tcpdump抓包看 SYN、SYN-ACK、ACK 的過程。實(shí)踐一次比背十遍都管用。還有一次為了理解數(shù)據(jù)庫索引失效我建了一張測試表插了十萬條數(shù)據(jù)分別用explain看兩種寫法的執(zhí)行計(jì)劃??吹絫ype ALL全表掃描和計(jì)算結(jié)果條數(shù)變化的時候我對隱式類型轉(zhuǎn)換導(dǎo)致索引失效的理解徹底脫敏了。抽象的原理一旦落成明確的輸出記憶會牢固得多。4.2 用講給別人聽來檢驗(yàn)掌握程度第二步是我最近才養(yǎng)成的習(xí)慣。每次整理完一個知識點(diǎn)的卡片我會試著把它講給同事聽或者在沒有聽眾的情況下自言自語錄音。講的時候注意能不能用大白話講清楚能不能回答別人的追問有一回我給別人解釋 B 樹為什么比二叉樹更適合做數(shù)據(jù)庫索引。我原本準(zhǔn)備了一大堆術(shù)語從高扇出到葉子節(jié)點(diǎn)順序存儲結(jié)果才開口講兩句自己就覺得不對勁——扇出這個詞本身就解釋不清楚。于是我去查了資料才知道高扇出意味著同樣的數(shù)據(jù)量B樹高度更低、查找路徑更短并且葉子節(jié)點(diǎn)用鏈表串起來天然適合范圍查詢。這幾個點(diǎn)是我以前沒真正內(nèi)化過的。檢驗(yàn)標(biāo)準(zhǔn)的經(jīng)驗(yàn)之談如果你講一個知識點(diǎn)的時候句子結(jié)構(gòu)是因?yàn)?..所以...而且能連續(xù)講出三個以上的因?yàn)檎f明你基本掌握了。如果只能講出它就是這樣設(shè)計(jì)的說明還沒搞懂。4.3 定期回爐與遺忘曲線遺忘是人的天性遺漏的知識點(diǎn)補(bǔ)回來以后還會再次漏掉。我的解決方案是間隔復(fù)習(xí)。我不喜歡用復(fù)雜的記憶軟件就用一個最簡單的辦法在日歷上每兩周設(shè)置一個知識回爐日。到了這一天不干別的專門翻看自己整理的知識卡片重點(diǎn)看常見坑點(diǎn)和一句話結(jié)論。看到能秒懂的就過看到模糊的就立刻去查資料當(dāng)場把卡片更新一遍。還有一個我強(qiáng)烈推薦的做法——寫月復(fù)盤。每個月底花二十分鐘寫下這個月踩過的坑、修復(fù)過的 bug、學(xué)到的知識點(diǎn)。不用寫很長三五條也行。這個月復(fù)盤不是給別人看的是給你自己以后的自己看的。三個月后再回頭看你會發(fā)現(xiàn)自己已經(jīng)忘掉了很多當(dāng)時以為學(xué)會了的東西而那些真正留下的、依然能在腦子里清晰復(fù)現(xiàn)的知識點(diǎn)才是你真正掌握的。5. 常見問題與避坑指南最后分享一些實(shí)際執(zhí)行過程中遇到的問題和我的處理思路這些經(jīng)驗(yàn)比單純的知識點(diǎn)更值錢。5.1 光看不練回頭就忘這是最常見的問題也是我反復(fù)掉進(jìn)去的坑。看文章的時候覺得這個我懂了合上文章就忘了。原因很簡單你看懂的是作者的思路不是你的思路。我的破解方法是三步法看完一篇文章立刻把瀏覽器標(biāo)簽頁關(guān)掉。用一句話在卡片上寫下這個知識點(diǎn)是干嘛的。嘗試不看原文寫出一個最小示例或畫出流程。三步都能完成才關(guān)卡片。三步完不成說明還是沒學(xué)會回頭再看一遍原文但這次不要往下讀先自己再試一遍。試不出來有哪些步驟就說明那一步是你的知識缺口重點(diǎn)補(bǔ)那一步就行。5.2 知識點(diǎn)太多整理變成負(fù)擔(dān)有些人一復(fù)盤就給自己列了一份十大必學(xué)清單結(jié)果壓力太大堅(jiān)持了兩天就放棄了。我一開始也犯過這個毛病清單列了二十多個知識點(diǎn)每天看到都焦慮。后來我調(diào)整策略一次只整理一個領(lǐng)域。這個月重點(diǎn)補(bǔ)網(wǎng)絡(luò)基礎(chǔ)那就只看 HTTP、TCP、DNS 這些下個月再看數(shù)據(jù)庫、并發(fā)。把一個領(lǐng)域的知識點(diǎn)卡片都建立起來以后再換下一個。領(lǐng)域之間確實(shí)有交叉但不用刻意求全先一個個打透比同時挖淺坑強(qiáng)得多。另外整理時不用追求完美筆記??ㄆ灰獙δ阌袡z索價值就行不需要排版精美、圖文并茂。我之前花很多時間給筆記畫各種花哨的流程圖結(jié)果畫完就忘了內(nèi)容?,F(xiàn)在我用最樸素的文字加一兩個關(guān)鍵代碼塊反而復(fù)盤的頻率更高。5.3 深度、廣度、性價比的取舍最后一個建議是關(guān)于學(xué)習(xí)策略的。不是所有知識點(diǎn)都需要往深挖也不是所有遺漏都必須立刻補(bǔ)。我給自己定了一個三問標(biāo)準(zhǔn)這個知識點(diǎn)和工作內(nèi)容相關(guān)嗎它影響我排查問題或做技術(shù)決策的頻率高嗎它能否幫助我理解其他關(guān)聯(lián)知識三個問題里至少答出兩個是才值得花時間深挖。如果只是單純覺得不懂心里癢那就控制時間比如給自己限時 30 分鐘查資料了解個大致框架就收手。知識海洋是無窮的與其什么都略懂皮毛不如在核心領(lǐng)域打深幾個點(diǎn)其他領(lǐng)域保持知道它在、需要時能找到資料的狀態(tài)就好。我個人在實(shí)際操作中的體會是知識點(diǎn)的遺漏并不可怕可怕的是你連自己漏了什么都不知道。定期復(fù)盤、建立卡片、動手驗(yàn)證、講給別人聽這套組合拳幫我解決了不少自己都沒意識到的盲區(qū)。如果你最近也一直在輸出卻總覺得心里空空的不妨停下來花一個下午做一次最近遺漏的知識點(diǎn)梳理。這個過程剛開始可能有點(diǎn)痛苦因?yàn)槟憧赡軙l(fā)現(xiàn)自己什么都不會但堅(jiān)持一兩輪以后那種越學(xué)越踏實(shí)的感覺是刷一百篇文章都換不來的。