抖音、騰訊、阿里、美團春招服務端開發崗位硬核面試(完結)

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"link","attrs":{"href":"http://blueskykong.com/2020/04/18/4-interview/","title":null},"content":[{"type":"text","text":"上一篇"}]},{"type":"text","text":" 我們中,我們分享了幾大互聯網公司面試的題目,本文就來"},{"type":"text","marks":[{"type":"strong"}],"text":"詳細分析面試題答案以及複習參考和整理的面試資料"},{"type":"text","text":",小民同學的私藏珍品🐶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先是面試題答案公佈,在講解時我們主要分成如下幾塊:語言的基礎知識、中間件、操作系統、計算機網絡、手寫算法、開放題和項目經歷。對面試題和涉及的知識點進行整理,這樣更容易讓各位同學理解。不會按照提問的順序進行講解,還請見諒。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次是 Java 複習參考和整理的面試資料。由於內容比較多,學習有 "},{"type":"codeinline","content":[{"type":"text","text":"道"}]},{"type":"text","text":" 非常重要,我們介紹一下其中的要點和目錄,完整文件可以參見筆者提供的 pdf 資料。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"面試題解析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"jenkins 如何使用?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"jenkins 涉及到 DevOps 相關的知識,主要用於自動化集成,持續、自動地構建/測試軟件項目,監控一些定時任務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"持續集成(CI)已成爲當前許多軟件開發團隊在整個軟件開發生命週期內側重於保證代碼質量的常見做法。它是一種實踐,旨在緩和和穩固軟件的構建過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"MVCC 在讀方面有什麼用途"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MySQL的大多數事務型存儲引擎實現的都不是簡單的行級鎖。基於提升併發性能的考慮,它們一般都同時實現了多版本併發控制(MVCC)。不同存儲引擎的 MVCC 實現是不同的,典型的有樂觀(optimistic)併發控制和悲觀(pessimistic)併發控制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MVCC 簡單來講就是對數據庫的任何修改的提交都不會直接覆蓋之前的數據,而是產生一個新的版本與老版本共存,使得讀取時可以完全不加鎖。這樣讀某一個數據時,事務可以根據隔離級別選擇要讀取哪個版本的數據。過程中完全不需要加鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以認爲 MVCC 是行級鎖的一個變種,但是它在很多情況下避免了加鎖操作,因此開銷更低。大多數的 MVCC 都實現了非阻塞的讀操作,寫操作也只鎖定必要的行。MVCC只能在可重複讀和可提交讀的隔離級別下生效。不可提交讀不能使用它的原因是不能讀取符合事務版本的行版本。它們總是讀取最新的行版本。可序列化不能使用MVCC的原因是,它總是要鎖定行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MVCC 的實現,是通過保存數據在某個時間點的快照來實現的。也就是說,不管需要執行多長時間,每個事務看到的數據是一致的。根據事務開始的時間不同,每個事務對同一張表,同一時刻看到的數據可能是不一樣的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"mysql orderby 如何實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MySQL有兩種方式可以實現ORDER BY:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過索引掃描生成有序的結果"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用文件排序(filesort)"}]}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"利用有序索引獲取有序數據"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"取出滿足過濾條件作爲排序條件的字段,以及可以直接定位到行數據的行指針信息,在 Sort Buffer 中進行實際的排序操作,然後利用排好序的數據根據行指針信息返回表中取得客戶端請求的其他字段的數據,再返回給客戶端。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種方式,在使用explain分析查詢的時候,顯示Using index。而文件排序顯示Using filesort。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SQL語句中,WHERE子句和ORDER BY子句都可以使用索引:WHERE 子句使用索引避免全表掃描,ORDER BY 子句使用索引避免 filesort(用“避免”可能有些欠妥,某些場景下全表掃描、filesort 未必比走索引慢),以提高查詢效率。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然索引能提高查詢效率,但在一條 SQL 裏,對於一張表的查詢 一次只能使用一個索引(注:排除發生 index merge 的可能性),也就是說當 WHERE 子句與ORDER BY子句要使用的索引不一致時,MySQL 只能使用其中一個索引(B+樹)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說,一個既有WHERE又有ORDER BY的SQL中,使用索引有三個可能的場景:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只用於WHERE子句 篩選出滿足條件的數據"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只用於ORDER BY子句 返回排序後的結果"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既用於WHERE又用於ORDER BY,篩選出滿足條件的數據並返回排序後的結果"}]}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"文件排序(filesort)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"filesort僅僅是排序而已,是否會放入磁盤看情況而定。filesort是否會使用磁盤取決於它操作的數據量大小。總結來說就是,filesort按排序方式來劃分 分爲兩種:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據量小時,在內存中快排"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據量大時,在內存中分塊快排,再在磁盤上將各個塊做歸併"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據量大的情況下涉及到磁盤 io,所以效率會低一些。根據回表查詢的次數,filesort又可以分爲兩種方式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回表讀取兩次數據(two-pass):兩次傳輸排序"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回表讀取一次數據(single-pass):單次傳輸排序"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單次傳輸排序的弊端在於會將所有涉及到的列都放入排序緩衝區,排序緩衝區一次能放下的tuples更少了,進行歸併排序的概率增大。列數據量越大,需要的歸併路數更多,增加了額外的I/O開銷。所以列數據量太大時,單次傳輸排序的效率可能還不如兩次傳輸排序。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"兩次傳輸排序會進行兩次回表操作:第一次回表用於在WHERE子句中篩選出滿足條件的rowid以及rowid對應的ORDER BY的列值;第二次回表發生在ORDER BY子句對指定列進行排序之後,通過rowid回表查出SELECT子句需要的字段信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"mysql 索引 orderby 之後的字段要不要加進去"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於order by字段加入索引本身這個問題,如果最終的結果集是以order by字段爲條件篩選的,將order by字段加入索引,並放在索引中正確的位置,會有明顯的性能提升。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"eureka源碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka 是 Spring Cloud 推薦的服務註冊與發現組件,包括Eureka Server 和 Eureka Client:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka Server提供服務註冊服務,各個節點啓動後,會在Eureka Server中註冊,這樣Server中的服務註冊表中將會存儲所有可用的服務節點的信息;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka Client是一個Java客戶端,用於簡化與Eureka Server交互,客戶端同時具備一個內置的、使用輪詢負載均衡算法的負載均衡器;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"在應用啓動後,將會向Eureka Server發送心跳(默認週期30s),如果Eureka Server在多個心跳週期沒有收到某個節點的心跳,Eureka Server會從服務註冊表中把這個服務節點刪除(默認爲90s);"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka Server之間通過複製的方式完成數據的同步;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka Client具有緩衝機制,如果Eureka Server全部宕機的情況,客戶端依然可以利用緩存的信息消費其他服務API;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka region可以理解爲地理上的分區,沒有具體大小的限制;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"Eureka zone可以理解爲region內具體的機房信息;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null},"content":[{"type":"text","text":"使用Jersey框架實現自身的Restful HTTP接口,peer之間同步與服務註冊通過HTTP協議實現,定時任務(發送心跳、定時清理過期服務、節點同步等)通過JDK自帶的Timer實現,內存緩存實現Google的guava實現;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":9,"align":null,"origin":null},"content":[{"type":"text","text":"當服務註冊中心Server檢測服務提供者宕機時,在服務中心將服務置爲DOWN狀態,並將該服務向其他訂閱者發佈,訂閱者更新本地緩存信息;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":10,"align":null,"origin":null},"content":[{"type":"text","text":"當Eureka Server節點在短時間內丟失過多的客戶端時,這個節點會進入自我保護模式,不再註銷任何服務;"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Hystrix源碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分佈式系統環境下,服務間類似依賴非常常見,一個業務調用通常依賴多個基礎服務。如下圖,對於同步調用,當庫存服務不可用時,商品服務請求線程被阻塞,當有大批量請求調用庫存服務時,最終可能導致整個商品服務資源耗盡,無法繼續對外提供服務。並且這種不可用可能沿請求調用鏈向上傳遞,這種現象被稱爲雪崩效應。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Hystrix 中文含義是豪豬,因其背上長滿棘刺,從而擁有了自我保護的能力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Hystrix設計目標"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對來自依賴的延遲和故障進行防護和控制——這些依賴通常都是通過網絡訪問的"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻止故障的連鎖反應"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"快速失敗並迅速恢復"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回退並優雅降級"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提供近實時的監控與告警"}]}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Hystrix遵循的設計原則"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"防止任何單獨的依賴耗盡資源(線程)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"過載立即切斷並快速失敗,防止排隊"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"儘可能提供回退以保護用戶免受故障"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用隔離技術(例如隔板,泳道和斷路器模式)來限制任何一個依賴的影響"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過近實時的指標,監控和告警,確保故障被及時發現"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過動態修改配置屬性,確保故障及時恢復"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"防止整個依賴客戶端執行失敗,而不僅僅是網絡通信"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"分佈式鎖實現方式?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分佈式鎖在微服務架構中很常用,主要有幾下實現方式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"基於數據庫做分佈式鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"基於表主鍵唯一做分佈式鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"思路:利用主鍵唯一的特性,如果有多個請求同時提交到數據庫,數據庫只會保證只有一個操作可以成功,那麼就可以認爲操作成功的線程獲取到了該方法的鎖。當方法執行完畢之後,通過刪除該行數據就可釋放鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單點數據庫導致強依賴,可以通過多數據庫主從切換"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過定時器刪除超時數據避免死鎖"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過自旋CAS的方式插入實現阻塞"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可重入可以通過檢查對應的記錄是否存在實現"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"公平鎖可以通過等待線程表的方式實現"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在大併發的情況下,通過主鍵衝突防重容易導致鎖表,儘量在程序中生產主鍵進行防重"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"基於表字段版本號做分佈式鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於mysql的mvcc機制,只有版本號一致才能進行對應的修改,修改後版本號加1,通過CAS的方式進行修改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"基於數據庫排他鎖做分佈鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過事務和 for update 語句實現,數據庫會在該事務下給數據庫增加排他鎖。在 InnoDB 引擎加鎖的時候,只有通過索引進行檢索的時候纔會使用行級鎖,否則使用表級鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"基於Redis做分佈鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"基於Redis的SETNX()、EXPIRE()方法做分佈式鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"setnx 方法的語義爲 SET if Not Exists,其主要有兩個參數,setnx(key, value)。該方法是原子的,如果 key 不存在,則設置當前的 key 成功,返回1;如果當前 key 已經存在,則設置當前 key 失敗,返回 0。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"expire 方法設置過期時間,setnx 命令不能設置 key 的超時時間,只能通過 expire() 來對 key 設置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在 setnx 執行成功後,在 expire 命令執行成功,執行的線程出現宕機的現象,就可能出現死鎖現象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於 Redis 的 SETNX()、GET()、GETSET() 方法做分佈式鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"getset方法有兩個參數 getset(key,newValue)。該方法是原子的,對 key 設置 newValue 這個值,並且返回 key 原來的舊值。假設 key 原來是不存在的,那麼多次執行這個命令,會出現下邊的效果:getset(key, “value1”) 返回 null 此時 key 的值會被設置爲 value1;getset(key, “value2”) 返回 value1 此時 key 的值會被設置爲 value2。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用步驟:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"setnx(lockkey,當前時間+過期超時時間),如果返回 1,則獲取鎖成功;如果返回0則沒有獲取到鎖,轉向2。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"get(lockkey) 獲取值 oldExpireTime,並將這個 value 值與當前的系統時間進行比較,如果小於當前系統時間,則認爲這個鎖已經超時,可以允許別的請求重新獲取,轉向 3。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"計算 newExpireTime = 當前時間 + 過期超時時間,然後 getset(lockkey, newExpireTime) 會返回當前 lockkey 的值 currentExpireTime。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"判斷 currentExpireTime 與 oldExpireTime 是否相等,如果相等,說明當前 getset 設置成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那麼當前請求可以直接返回失敗,或者繼續重試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在獲取到鎖之後,當前線程可以開始自己的業務處理,當處理完畢後,比較自己的處理時間和對於鎖設置的超時時間,如果小於鎖設置的超時時間,則直接執行 delete 釋放鎖;如果大於鎖設置的超時時間,則不需要再鎖進行處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"基於 REDLOCK 做分佈式鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redlock 是 Redis 的作者開發的集羣模式的 Redis 分佈式鎖,它基於 N 個完全獨立的 Redis 節點."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端獲取當前時間,以毫秒爲單位。客戶端嘗試獲取N個節點的鎖(每個節點獲取鎖的方式和前面說的緩存鎖一樣),N 個節點以相同的 key 和 value 獲取鎖。客戶端需要設置接口訪問超時,接口超時時間需要遠遠小於鎖超時時間,比如鎖自動釋放的時間是 10s,那麼接口超時大概設置5-50ms。這樣可以在有redis節點宕機後,訪問該節點時能儘快超時,而減小鎖的正常使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端計算在獲得鎖的時候花費了多少時間,方法是用當前時間減去在步驟一獲取的時間,只有客戶端獲得了超過3個節點的鎖,而且獲取鎖的時間小於鎖的超時時間,客戶端才獲得了分佈式鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端獲取的鎖的時間爲設置的鎖超時時間減去步驟三計算出的獲取鎖花費時間。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果客戶端獲取鎖失敗了,客戶端會依次刪除所有的鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"基於 REDISSON 做分佈式鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redisson 是 redis 官方的分佈式鎖組件。在Redisson中,如果如果線程獲取鎖成功,Redisson會在後臺起開一個定時任務watchdog,定時任務會定時檢查調用renewExpirationAsync(threadId)對鎖進行續約。定時調度的時間差爲internalLockLeaseTime/3,即10秒,默認加鎖時間爲30s。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"基於 Consul 做分佈式鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於Consul的分佈式鎖主要利用Key/Value存儲API中的acquire和release操作來實現。acquire和release操作是類似Check-And-Set的操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"acquire操作只有當鎖不存在持有者時纔會返回true,並且set設置的Value值,同時執行操作的session會持有對該Key的鎖,否則就返回false"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"release操作則是使用指定的session來釋放某個Key的鎖,如果指定的session無效,那麼會返回 false,否則就會set設置 Value 值,並返回 true。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"zk的原理 源碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Zookeeper 有如下的角色:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"領導者(leader),負責進行投票的發起和決議,更新系統狀態"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"學習者(learner),包括跟隨者(follower)和觀察者(observer),follower用於接受客戶端請求並想客戶端返回結果,在選主過程中參與投票"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Observer可以接受客戶端連接,將寫請求轉發給leader,但observer不參加投票過程,只同步leader的狀態,observer的目的是爲了擴展系統,提高讀取速度"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端(client),請求發起方"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Zookeeper 的核心是原子廣播,這個機制保證了各個server之間的同步。實現這個機制的協議叫做Zab協議。Zab協議有兩種模式,它們分別是恢復模式和廣播模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當服務啓動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數server的完成了和leader的狀態同步以後,恢復模式就結束了。狀態同步保證了leader和server具有相同的系統狀態"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一旦leader已經和多數的follower進行了狀態同步後,他就可以開始廣播消息了,即進入廣播狀態。這時候當一個server加入zookeeper服務中,它會在恢復模式下啓動,發現leader,並和leader進行狀態同步。待到同步結束,它也參與消息廣播。Zookeeper服務一直維持在 Broadcast 狀態,直到leader崩潰了或者leader失去了大部分的followers支持。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了保證事務的順序一致性,zookeeper採用了遞增的事務id號(zxid)來標識事務。所有的提議(proposal)都在被提出的時候加上了zxid。實現中zxid是一個64位的數字,它高32位是epoch用來標識 leader關係是否改變,每次一個leader被選出來,它都會有一個新的epoch,標識當前屬於那個leader的統治時期。低32位用於遞增計數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"高級開發工程師的面試一般都會涉及源碼,爲什麼很多同學覺得原理、源碼是造火箭,其實這個和自己的經歷是有很大關係的。首先不排除,確實又一部分面試題的確是造火箭。很多同學做的項目比較簡單,就拿 Java 舉例,業務可能增刪改查居多,長久就形成了工作並不需要看源碼,甚至覺得閱讀源碼、熟悉原理對自己幫助並不大的錯覺。看優秀的源碼是一種更深入的學習。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"序列化方式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先理解序列化和反序列化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"序列化:可以將對象轉化成一個字節序列,便於存儲。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"反序列化:將序列化的字節序列還原"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優點:可以實現對象的\"持久性”, 所謂持久性就是指對象的生命週期不取決於程序。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java 中的序列化方式包括Java原生以流的方法進行的序列化、Json序列化、FastJson序列化、Protobuff序列化。Protobuff序列化支持跨語言。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Java原生序列化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java原生序列化方法即通過Java原生流(InputStream和OutputStream之間的轉化)的方式進行轉化。需要注意的是JavaBean實體類必須實現Serializable接口,否則無法序列化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Json序列化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Json序列化一般會使用jackson包,通過ObjectMapper類來進行一些操作,比如將對象轉化爲byte數組或者將json串轉化爲對象。現在的大多數公司都將json作爲服務器端返回的數據格式。比如調用一個服務器接口,通常的請求爲xxx.json?a=xxx&b=xxx的形式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"FastJson序列化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"fastjson 是由阿里巴巴開發的一個性能很好的Java 語言實現的 Json解析器和生成器。特點:速度快,測試表明fastjson具有極快的性能,超越任其他的java json parser。功能強大,完全支持java bean、集合、Map、日期、Enum,支持範型和自省。無依賴,能夠直接運行在Java SE 5.0以上版本支持Android。使用時候需引入FastJson第三方jar包。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"ProtoBuff序列化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ProtocolBuffer是一種輕便高效的結構化數據存儲格式,可以用於結構化數據序列化。適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"優點:跨語言;序列化後數據佔用空間比JSON小,JSON有一定的格式,在數據量上還有可以壓縮的空間。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺點:它以二進制的方式存儲,無法直接讀取編輯,除非你有 .proto 定義,否則無法直接讀出 Protobuffer的任何內容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其與thrift的對比:兩者語法類似,都支持版本向後兼容和向前兼容,thrift側重點是構建跨語言的可伸縮的服務,支持的語言多,同時提供了全套RPC解決方案,可以很方便的直接構建服務,不需要做太多其他的工作。 Protobuffer主要是一種序列化機制,在數據序列化上進行性能比較,Protobuffer相對較好。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ProtoBuff序列化對象可以很大程度上將其壓縮,可以大大減少數據傳輸大小,提高系統性能。對於大量數據的緩存,也可以提高緩存中數據存儲量。原始的ProtoBuff需要自己寫.proto文件,通過編譯器將其轉換爲java文件,顯得比較繁瑣。百度研發的jprotobuf框架將Google原始的protobuf進行了封裝,對其進行簡化,僅提供序列化和反序列化方法。其實用上也比較簡潔,通過對JavaBean中的字段進行註解就行,不需要撰寫.proto文件和實用編譯器將其生成.java文件,百度的jprotobuf都替我們做了這些事情了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"redis 持久化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"redis是一個內存數據庫,數據保存在內存中,但是我們都知道內存的數據變化是很快的,也容易發生丟失。幸好 Redis 還爲我們提供了持久化的機制,分別是RDB(Redis DataBase)和 AOF(Append Only File)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"RDB 機制"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RDB 其實就是把數據以快照的形式保存在磁盤上。快照可以理解成把當前時刻的數據拍成一張照片保存下來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RDB 持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤。也是默認的持久化方式,這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名爲 dump.rdb。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RDB 機制是通過把某個時刻的所有數據生成一個快照來保存,那麼就應該有一種觸發機制,是實現這個過程。對於 RDB 來說,提供了三種機制:save、bgsave、自動化。我們分別來看一下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"save:該命令會阻塞當前 Redis 服務器,執行 save 命令期間,Redis 不能處理其他命令,直到 RDB 過程完成爲止。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"bgsave 觸發方式:執行該命令時,Redis 會在後臺異步進行快照操作,快照同時還可以響應客戶端請求。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自動觸發:自動觸發是由我們的配置文件來完成的。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RDB 具有如下的優勢:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RDB 文件緊湊,全量備份,非常適合用於進行備份和災難恢復。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成 RDB 文件的時候,redis 主進程會 fork() 一個子進程來處理所有保存工作,主進程不需要進行任何磁盤 IO 操作。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也存在不足之處,RDB 快照是一次全量備份,存儲的是內存數據的二進制序列化形式,存儲上非常緊湊。當進行快照持久化時,會開啓一個子進程專門負責快照持久化,子進程會擁有父進程的內存數據,父進程修改內存子進程不會反應出來,所以在快照持久化期間修改的數據不會被保存,可能丟失數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"AOF"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"全量備份總是耗時的,有時候我們提供一種更加高效的方式 AOF,工作機制很簡單,redis 會將每一個收到的寫命令都通過 write 函數追加到文件中。通俗的理解就是日誌記錄。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AOF 的方式也同時帶來了另一個問題。持久化文件會變的越來越大。爲了壓縮 AOF 的持久化文件。redis 提供了 bgrewriteaof 命令。將內存中的數據以命令的方式保存到臨時文件中,同時會 fork 出一條新進程來將文件重寫。重寫aof文件的操作,並沒有讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點類似。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AOF也有三種觸發機制:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每修改同步always:同步持久化 每次發生數據變更會被立即記錄到磁盤 性能較差但數據完整性比較好"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每秒同步everysec:異步操作,每秒記錄 如果一秒內宕機,有數據丟失"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同no:從不同步"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AOF 可以更好的保護數據不丟失,一般AOF會每隔1秒,通過一個後臺線程執行一次fsync操作,最多丟失1秒鐘的數據;AOF日誌文件沒有任何磁盤尋址的開銷,寫入性能非常高,文件不容易破損;AOF 日誌文件即使過大的時候,出現後臺重寫操作,也不會影響客戶端的讀寫;AOF 日誌文件的命令通過非常可讀的方式進行記錄,這個特性非常適合做災難性的誤刪除的緊急恢復。比如某人不小心用 flushall 命令清空了所有數據,只要這個時候後臺rewrite還沒有發生,那麼就可以立即拷貝 AOF 文件,將最後一條 flushall 命令給刪了,然後再將該AOF文件放回去,就可以通過恢復機制,自動恢復所有數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,對於同一份數據來說,AOF日誌文件通常比RDB數據快照文件更大;AOF開啓後,支持的寫QPS會比RDB支持的寫QPS低,因爲AOF一般會配置成每秒fsync一次日誌文件,當然,每秒一次fsync,性能也還是很高;以前AOF發生過bug,就是通過AOF記錄的日誌,進行數據恢復的時候,沒有恢復一模一樣的數據出來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"redis 主從複製"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和Mysql主從複製的原因一樣,Redis雖然讀取寫入的速度都特別快,但是也會產生讀壓力特別大的情況。爲了分擔讀壓力,Redis支持主從複製,Redis的主從結構可以採用一主多從或者級聯結構,Redis主從複製可以根據是否是全量分爲全量同步和增量同步。下圖爲級聯結構。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/de/de8310da0ab9f5bc18641ff46970bf90.png","alt":"","title":"","style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"全量同步"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis全量複製一般發生在Slave初始化階段,這時Slave需要將Master上的所有數據都複製一份。具體步驟如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從服務器連接主服務器,發送SYNC命令;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主服務器接收到SYNC命名後,開始執行BGSAVE命令生成RDB文件並使用緩衝區記錄此後執行的所有寫命令;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主服務器BGSAVE執行完後,向所有從服務器發送快照文件,並在發送期間繼續記錄被執行的寫命令;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從服務器收到快照文件後丟棄所有舊數據,載入收到的快照;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主服務器快照發送完畢後開始向從服務器發送緩衝區中的寫命令;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從服務器完成對快照的載入,開始接收命令請求,並執行來自主服務器緩衝區的寫命令;"}]}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"增量同步"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis增量複製是指Slave初始化後開始正常工作時主服務器發生的寫操作同步到從服務器的過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"增量複製的過程主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收並執行收到的寫命令。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Redis主從同步策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主從剛剛連接的時候,進行全量同步;全同步結束後,進行增量同步。當然,如果有需要,slave 在任何時候都可以發起全量同步。redis 策略是,無論如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis主從複製的配置十分簡單,它可以使從服務器是主服務器的完全拷貝。需要清除Redis主從複製的幾點重要內容:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis使用異步複製。但從Redis 2.8開始,從服務器會週期性的應答從複製流中處理的數據量。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個主服務器可以有多個從服務器。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從服務器也可以接受其他從服務器的連接。除了多個從服務器連接到一個主服務器之外,多個從服務器也可以連接到一個從服務器上,形成一個圖狀結構。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis主從複製不阻塞主服務器端。也就是說當若干個從服務器在進行初始同步時,主服務器仍然可以處理請求。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主從複製也不阻塞從服務器端。當從服務器進行初始同步時,它使用舊版本的數據來應對查詢請求,假設你在redis.conf配置文件是這麼配置的。否則的話,你可以配置當複製流關閉時讓從服務器給客戶端返回一個錯誤。但是,當初始同步完成後,需要刪除舊的數據集和加載新的數據集,在這個短暫的時間內,從服務器會阻塞連接進來的請求。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主從複製可以用來增強擴展性,使用多個從服務器來處理只讀的請求(比如,繁重的排序操作可以放到從服務器去做),也可以簡單的用來做數據冗餘。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用主從複製可以爲主服務器免除把數據寫入磁盤的消耗:在主服務器的redis.conf文件中配置“避免保存”(註釋掉所有“保存“命令),然後連接一個配置爲“進行保存”的從服務器即可。但是這個配置要確保主服務器不會自動重啓"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主從複製結構,一般slave服務器不能進行寫操作,但是這不是死的,之所以這樣是爲了更容易的保證主和各個從之間數據的一致性,如果slave服務器上數據進行了修改,那麼要保證所有主從服務器都能一致,可能在結構上和處理邏輯上更爲負責。不過你也可以通過配置文件讓從服務器支持寫操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主從服務器之間會定期進行通話,但是如果master上設置了密碼,那麼如果不給slave設置密碼就會導致slave不能跟master進行任何操作,所以如果你的master服務器上有密碼,那麼也給slave相應的設置密碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"爲什麼要使用 Spring Cloud"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"spring Cloud 通過 Spring Boot 封裝了各家公司開發的一系列成熟、穩定的服務框架,去除了繁瑣的配置,給開發者提供了一套簡單易用、方便部署、易於維護的分佈式系統開發工具包。spring Boot 是 Spring 的一套快速配置腳手架,可以基於 Spring Boot 快速開發單個微服務,Spring Boot 專注於快速、方便的集成單個個體,Spring Cloud 關注全局的服務治理框架。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Boot 可以離開 Spring Cloud 獨立使用開發項目,但是 Spring Cloud 離不開 Spring Boot,屬於依賴關係。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"微服務註冊中心原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"註冊中心主要涉及到三大角色:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務提供者"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務消費者"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"註冊中心"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它們之間的關係大致如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"各個微服務在啓動時,將自己的網絡地址等信息註冊到註冊中心,註冊中心存儲這些數據。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務消費者從註冊中心查詢服務提供者的地址,並通過該地址調用服務提供者的接口。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"各個微服務與註冊中心使用一定機制(例如心跳)通信。如果註冊中心與某微服務長時間無法通信,就會註銷該實例。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微服務網絡地址發送變化(例如實例增加或IP變動等)時,會重新註冊到註冊中心。這樣,服務消費者就無需人工修改提供者的網絡地址了。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"註冊服務怎麼判斷上線下線"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務啓動之時如果端口部署成功,會調用註冊中心提供的服務註冊API將自身網絡地址等數據提交到服務註冊與發現中完成註冊;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"註冊中心會通過一定的機制(例如心跳)與註冊表中的微服務實例保持通信,如果註冊中心長時間無法與該微服務實例通信,將會從註冊表中剔除該服務實例信息;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"微服務實例在關閉時會主動調用註冊中心提供的下線接口,將自身微服務實例數據從註冊表中註銷。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"操作系統與計算機網絡"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"訪問一個 url 發生了什麼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當我們在瀏覽器的地址欄輸入 www.google.com ,然後回車,回車這一瞬間到看到頁面到底發生了什麼呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"根據屬於的域名,進行DNS域名解析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Chrome 瀏覽器會首先搜索瀏覽器自身的 DNS 緩存(緩存時間比較短,且只能容納 1000 條緩存),看自身的緩存中是否有 www.google.com 對應的條目,而且沒有過期,如果有且沒有過期則解析到此結束。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果瀏覽器自身的緩存裏面沒有找到對應的條目,那麼 Chrome 會搜索操作系統自身的DNS緩存,如果找到且沒有過期則停止搜索解析到此結束。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在系統的DNS緩存也沒有找到,那麼嘗試讀取 hosts 文件,看看這裏面有沒有該域名對應的IP地址,如果有則解析成功。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在 hosts 文件中也沒有找到對應的條目,瀏覽器就會發起一個 DNS 的系統調用,就會向本地配置的首選 DNS 服務器發起域名解析請求,運營商的 DNS 服務器首先查找自身的緩存,找到對應的條目,且沒有過期,則解析成功。如果沒有找到對應的條目,則有運營商的 DNS 代我們的瀏覽器發起迭代 DNS 解析請求。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"解析到IP地址,建立TCP連接"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"拿到域名對應的IP地址之後,User-Agent(一般是指瀏覽器)會以一個隨機端口(1024 < 端口 < 65535)向服務器的WEB程序(常用的有 httpd,nginx等)80 端口發起 TCP 的連接請求。這個連接請求(原始的http請求經過TCP/IP4層模型的層層封包)到達服務器端後(這中間通過各種路由設備,局域網內除外),進入到網卡,然後是進入到內核的 TCP/IP 協議棧(用於識別該連接請求,解封包,一層一層的剝開),還有可能要經過 Netfilter 防火牆(屬於內核的模塊)的過濾,最終到達WEB程序(本文就以Nginx爲例),最終建立了 TCP/IP 的連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"發送HTTP請求"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP 3 次握手之後,瀏覽器發起了http的請求(看第包),使用的http的方法 GET 方法,請求的URL是 / ,協議是HTTP/1.0。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"服務器處理請求,並返回響應結果"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務器端 WEB 程序接收到 http 請求以後,就開始處理該請求,處理之後就返回給瀏覽器 html 文件。關閉TCP連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"瀏覽器解析HTML"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瀏覽器拿到index.html文件後,就開始解析其中的html代碼,遇到js/css/image等靜態資源時,就向服務器端去請求下載(會使用多線程下載,每個瀏覽器的線程數不一樣),這個時候就用上 keep-alive 特性了,建立一次 HTTP 連接,可以請求多個資源,下載資源的順序就是按照代碼裏的順序,但是由於每個資源大小不一樣,而瀏覽器又多線程請求請求資源,所以從下圖看出,這裏顯示的順序並不一定是代碼裏面的順序。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瀏覽器在請求靜態資源時(在未過期的情況下),向服務器端發起一個http請求(詢問自從上一次修改時間到現在有沒有對資源進行修改),如果服務器端返回304狀態碼(告訴瀏覽器服務器端沒有修改),那麼瀏覽器會直接讀取本地的該資源的緩存文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"瀏覽器佈局渲染"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,瀏覽器利用自己內部的工作機制,把請求到的靜態資源和html代碼進行渲染,渲染之後呈現給用戶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"操作系統兩個進程寫共享內存中一個位置 會不會出現不一致?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"共享內存就是允許兩個或多個進程共享一定的存儲區。就如同 malloc() 函數向不同進程返回了指向同一個物理內存區域的指針。當一個進程改變了這塊地址中的內容的時候,其它進程都會察覺到這個更改。因爲數據不需要在客戶機和服務器端之間複製,數據直接寫到內存,不用若干次數據拷貝,所以這是最快的一種 IPC。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是,當有多個程序同時向共享內存中讀寫數據時,問題就會出現。共享內存沒有提供同步的機制,這使得我們在使用共享內存進行進程間通信時,往往要藉助其他的手段來進行進程間的同步工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"http 請求 api 超時如何實現的"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"網關和 rpc 負載均衡進行配置,比如在 API 網關統一配置接口的超時時間。在單個微服務中,服務之間的調用配置超時時間,如 Ribbon Timeout。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"http 協議是什麼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP協議是Hyper Text Transfer Protocol(超文本傳輸協議)的縮寫,是用於從萬維網(WWW:World Wide Web )服務器傳輸超文本到本地瀏覽器的傳送協議。基於TCP的應用層協議,它不關心數據傳輸的細節,HTTP(超文本傳輸協議)是一個基於請求與響應模式的、無狀態的、應用層的協議,只有遵循統一的 HTTP 請求格式,服務器才能正確解析不同客戶端發的請求,同樣地,服務器遵循統一的響應格式,客戶端才得以正確解析不同網站發過來的響應。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP 請求由請求行、請求頭、空行、請求體組成"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"請求行:請求方式 + URL + 協議版本"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"常見的請求方法有 GET、POST、PUT、DELETE、HEAD"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端要獲取的資源路徑(所謂的 URL)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端使用的 HTTP 協議版本號(使用的是 http1.1)"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"請求頭:客戶端向服務器發送請求的補充說明:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"host:請求地址"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"User-Agent: 客戶端使用的操作系統和瀏覽器的名稱和版本."}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Content-Length:發送給HTTP服務器數據的長度。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Content-Type:參數的數據類型"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Cookie:將cookie的值發送給HTTP 服務器"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Accept-Charset:自己接收的字符集"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Accept-Language:瀏覽器自己接收的語言"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Accept:瀏覽器接受的媒體類型"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"請求體:一般攜帶的請求參數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"application/json:{\"name\":\"value\",\"name1\":\"value2”}application/x-www-form-urlencoded: name1=value1&name2=value2multipart/from-data:表格形式text/xmlcontent-type:octets/stream"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"POST 請求有哪些字段"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在HTTP的請求頭中,可以使用Content-type來指定不同格式的請求信息。HTTP協議規定 POST 提交的數據必須放在消息主體(entity-body)中,但協議並沒有規定數據必須 使用什麼編碼方式 。實際上,開發者完全可以自己決定消息主體的格式,只要最後發送的 HTTP 請求滿足上面的格式就可以。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據發送出去,還要服務端解析成功纔有意義。一般服務端語言如 php、python 等,以及它們的 framework,都內置了自動解析常見數據格式的功能。服務端通常是根據請求頭(headers)中的 Content-Type 字段來獲知請求中的消息主體是用何種方式編碼,再對主體進行解析。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"form表單中enctype屬性可以用來控制對錶單數據的發送前的如何進行編碼,enctype有三種,分別爲:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"multipart/form-data不對字符編碼,用於發送二進制的文件,其他兩種類型不能用於發送文件;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"text/plain用於發送純文本內容,空格轉換爲 \"+\" 加號,不對特殊字符進行編碼,一般用於email之類的;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"application/x-www-form-urlencoded,在發送前會編碼所有字符,即在發送到服務器之前,所有字符都會進行編碼(空格轉換爲 \"+\" 加號,\"+\"加號轉換爲空格,特殊符號轉換爲 ASCII HEX 值)。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中 application/x-www-form-urlencoded 爲默認類型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"HTTPS 和HTTP區別是什麼?HTTPS 客戶端服務器怎麼交互的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTPS:是以安全爲目標的 HTTP 通道,簡單講是 HTTP 的安全版,即 HTTP 下加入 SSL 層,HTTPS 的安全基礎是 SSL,因此加密的詳細內容就需要 SSL。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTPS 協議的主要作用可以分爲兩種:一種是建立一個信息安全通道,來保證數據傳輸的安全;另一種就是確認網站的真實性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP 協議傳輸的數據都是未加密的,也就是明文的,因此使用HTTP協議傳輸隱私信息非常不安全,爲了保證這些隱私數據能加密傳輸,於是網景公司設計了SSL(Secure Sockets Layer)協議用於對HTTP協議傳輸的數據進行加密,從而就誕生了HTTPS。簡單來說,HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,要比http協議安全。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTPS和HTTP的區別主要如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https協議需要到ca申請證書,一般免費證書較少,因而需要一定費用。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"http是超文本傳輸協議,信息是明文傳輸,https則是具有安全性的ssl加密傳輸協議。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,後者是443。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"http的連接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0b/0bdb3cc76e3583d0da4fda6c35966169.jpeg","alt":"","title":"","style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端在使用HTTPS方式與Web服務器通信時有以下幾個步驟:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶使用 https 的 URL 訪問 Web 服務器,要求與 Web 服務器建立 SSL 連接。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Web 服務器收到客戶端請求後,會將網站的證書信息(證書中包含公鑰)傳送一份給客戶端。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端的瀏覽器與 Web 服務器開始協商SSL連接的安全等級,也就是信息加密的等級。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端的瀏覽器根據雙方同意的安全等級,建立會話密鑰,然後利用網站的公鑰將會話密鑰加密,並傳送給網站。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Web服務器利用自己的私鑰解密出會話密鑰。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Web服務器利用會話密鑰加密與客戶端之間的通信。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"儘管HTTPS並非絕對安全,掌握根證書的機構、掌握加密算法的組織同樣可以進行中間人形式的攻擊,但HTTPS仍是現行架構下最安全的解決方案。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"滑動窗口 -> 客戶端和服務器端分別有哪些區域(已確認 傳輸未確認 未傳輸)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大家都知道,我們從一臺機器向另外一臺機器發送數據的時候,數據並不是一口氣也不可能一口氣傳輸給接收方。這個並不難理解,因爲網絡環境特別的複雜,有些地方快有些地方慢。所以,操作系統把這些數據寫成連續的數據包,並且以一定的速率發給對方。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們要考慮到帶寬緩衝區等因素,如果一下子發送所有的數據只會加大網絡壓力,造成丟包重試,輕則傳輸更慢,重則網絡崩潰。因爲TCP是順序發送的,操作系統將這些數據包一批一批的發送給對方,就像一個窗口,不停地往後移動,所以,我們稱之爲TCP滑動窗口協議。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在TCP中,窗口的大小是在TCP三次握手後協定的,並且窗口的大小並不是固定的,而是會隨着網絡的情況進行調整。TCP爲了更好的傳輸效率,就會調整窗口的大小。TCP 通過滑動窗口機制檢測丟包,並在丟包發生時調整數據傳輸速率。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fd/fd1a5622e046033ebcf6f7579c6d8175.jpeg","alt":"","title":"","style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於發送端來說,即將要發送的數據包排成一個隊列,對於發送者來說,數據包總共分成四類。分別是在窗口前的,已經發送給接收方,並且收到了接收方的答覆,我們稱之爲已發送。在窗口中的,有兩種狀態,一個是已經發送給接收方,但是接收方還沒確認送達,我們稱之爲已發送未確認,另外一個是可以發送了,但是還沒有發送,我們稱之爲允許發送未發送。最後的是在窗口外面的,我們稱之爲不可發送,除非窗口滑到此處,否則不會進行發送。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一旦前面的數據已經得到服務端確認了,這個窗口就會慢慢地往後滑,如下圖所示,P1,P2兩個數據包被確認之後,窗口就往後移動,後面新的數據包就由不可發送待發送變成了可發送狀態了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP的滑動窗口協議有什麼意義呢?首先當然是可靠性,滑動窗口只有在隊列前部的被確認之後,纔會往後移動,保證數據包被接收方確認並接收。其次是傳輸效率,假如沒有窗口,服務端是雜亂無章地進行發包,因爲TCP的隊首效應,如果有前面的包沒有發送成功,就會不停的重試,反而造成更差的傳輸效率。最後是穩定性,TCP的滑動窗口大小,是整個複雜網絡商榷的結果,會進行動態調整,可以儘量地避免網絡擁塞,更加穩定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"dns 是什麼原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當前大部分的網絡訪問都是基於 TCP/IP 協議開發,而 TCP/IP 是基於 IP 尋址的。又因爲大多數人記不住沒有意義的 ip 地址,因此希望使用一些簡單的域名代替地址,而 DNS 就充當了這樣一個“翻譯器”。我們輸入域名,DNS幫我們查詢對應的域名綁定的IP地址。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"http 握手的 wait time"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據 tcp 關閉時四次握手流程,主動關閉方會在發送完最後的ACK包後進入time_wait 狀態,該狀態持續時間爲 2MSL (MSL 是指報文的最大生存時間,超過 MSL 時間沒被接受的數據包將會被丟棄,RFC793 中建議爲 2 min),時間到達後將進入 close 狀態,關閉本次 tcp 連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主動關閉方進入2MSL的 time_wait 狀態的目的有二:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"保證本次tcp連接中產生的所有數據包都在網絡中消亡,避免本次tcp連接中產生但延遲到達的數據包影響到新建 tcp 連接的使用;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"保證被動關閉方能夠收到最後的 ACK。如果最後的 ACK 在網絡傳遞中丟失了,那麼被動關閉方就會重傳FIN包。而主動關閉方就能在這 2MSL 時間內接受到這個 FIN 包,並重新發送 ACK 包,重新啓動 2MSL 計時。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"延伸一下,如果在tcp客戶端出現大量的time_wait狀態該如何處理?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提示一下連接複用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"算法相關"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"計算題:撲克牌兩張王的概率"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一百次抽取可以從54張牌中抽大小王中度的一張,概率爲2/54=1/27。第二次只能從53張牌中抽到那張知剩下的王,概率道爲1/53。所以抽專兩張是大小王的概率=(1/27)*(1/53)=1/1431。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"算法穩定性的實際作用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序中,Ai=Aj,且Ai在Aj之前,在排序後的序列中,Ai仍在Aj之前,則稱這種排序算法是穩定的;否則該算法是不穩定的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要排序的內容是一個複雜對象的多個數字屬性,且其原本的初始順序存在意義,那麼我們需要在二次排序的基礎上保持原有排序的意義,才需要使用到穩定性的算法,例如要排序的內容是一組原本按照價格高低排序的對象,如今需要按照銷量高低排序,使用穩定性算法,可以使得想同銷量的對象依舊保持着價格高低的排序展現,只有銷量不同的纔會重新排序。(當然,如果需求不需要保持初始的排序意義,那麼使用穩定性算法依舊將毫無意義)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果只是簡單的進行數字的排序,那麼穩定性將毫無意義。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果排序的內容僅僅是一個複雜對象的某一個數字屬性,那麼穩定性依舊將毫無意義(正如上面說的,會增大一定的開銷,但並不起決定性因素,算法本身的開銷纔是關鍵——比如換種算法)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果要排序的內容是一個複雜對象的多個數字屬性,但是其原本的初始順序毫無意義,那麼穩定性依舊將毫無意義。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"算法:第 K 大的元素"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在長度爲 N 的亂序數組中尋找第k(n>=k)大的元素。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有好幾種方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方法一:排序法,先把無序數組從大到小進行排序,排序後的第k個元素自然就是數組中的第k大元素。但是這種方法的時間複雜度是O(nlogn),性能有些差。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方法二:插入法,維護一個長度爲k的數組A的有序數組,用於存儲已知的K個較大的元素。然後遍歷無序數組,每遍歷到一個元素,和數組A中的最小元素進行比較,如果小於等於數組A中的最小元素,繼續遍歷;如果大於數組A中的最小元素,則插入到數組A中,並把曾經的最小元素\"擠出去\"。時間複雜度爲O(N*k),適用於k相對於N很小的情況。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方法三:小頂堆法,維護一個容量爲K的小頂堆,堆中的K個節點代表着當前最大的K個元素,而堆頂顯然是這K個元素中的最小值。遍歷原數組,每遍歷一個元素,就和堆頂比較,如果當前元素小於等於堆頂,則繼續遍歷;如果元素大於堆頂,則把當前元素放在堆頂位置,並調整二叉堆(下沉操作)。遍歷結束後,堆頂就是數組的最大K個元素中的最小值,也就是第K大元素。當k遠小於n的情況下,也可以近似地認爲時間複雜度是O(nlogk)。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方法四:分治法,快速排序利用分治法,每一次把數組分成較大和較小元素兩部分。我們在尋找第K大元素的時候,也可以利用這個思路,以某個元素A爲基準,把大於A的元素都交換到數組左邊,小於A的元素交換到數組右邊。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們基於方法三進行實現算法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"public static int findNumberK(int[] array, int k) {"}]},{"type":"codeinline","content":[{"type":"text","text":"        //1.用前k個元素構建小頂堆"}]},{"type":"codeinline","content":[{"type":"text","text":"        buildHeap(array, k);"}]},{"type":"codeinline","content":[{"type":"text","text":"        //2.繼續遍歷數組,和堆頂比較"}]},{"type":"codeinline","content":[{"type":"text","text":"        for (int i = k; i  array[0]) {"}]},{"type":"codeinline","content":[{"type":"text","text":"                array[0] = array[i];"}]},{"type":"codeinline","content":[{"type":"text","text":"                downAdjust(array, 0, k);"}]},{"type":"codeinline","content":[{"type":"text","text":"            }"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        //3.返回堆頂元素"}]},{"type":"codeinline","content":[{"type":"text","text":"        return array[0];"}]},{"type":"codeinline","content":[{"type":"text","text":"    }"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"    private static void buildHeap(int[] array, int length) {"}]},{"type":"codeinline","content":[{"type":"text","text":"        //從最後一個非葉子節點開始,依次下沉調整"}]},{"type":"codeinline","content":[{"type":"text","text":"        for (int i = (length - 2) / 2; i >= 0; i--) {"}]},{"type":"codeinline","content":[{"type":"text","text":"            downAdjust(array, i, length);"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"    }"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"    /**"}]},{"type":"codeinline","content":[{"type":"text","text":"     * 下沉調整"}]},{"type":"codeinline","content":[{"type":"text","text":"     * @param array 待調整的堆"}]},{"type":"codeinline","content":[{"type":"text","text":"     * @param index 要下沉的節點"}]},{"type":"codeinline","content":[{"type":"text","text":"     * @param length 堆的有效大小"}]},{"type":"codeinline","content":[{"type":"text","text":"     */"}]},{"type":"codeinline","content":[{"type":"text","text":"    private static void downAdjust(int[] array, int index, int length) {"}]},{"type":"codeinline","content":[{"type":"text","text":"        //temp保存父節點的值,用於最後的賦值"}]},{"type":"codeinline","content":[{"type":"text","text":"        int temp = array[index];"}]},{"type":"codeinline","content":[{"type":"text","text":"        int childIndex = 2 * index + 1;"}]},{"type":"codeinline","content":[{"type":"text","text":"        while (childIndex  list = new ArrayList();"}]},{"type":"codeinline","content":[{"type":"text","text":"        //得到字符串的所有後綴"}]},{"type":"codeinline","content":[{"type":"text","text":"        for(int i = s.length()-1;i>=0;i--) {"}]},{"type":"codeinline","content":[{"type":"text","text":"            list.add(s.substring(i));"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        Collections.sort(list);"}]},{"type":"codeinline","content":[{"type":"text","text":"        int maxLen = 0;"}]},{"type":"codeinline","content":[{"type":"text","text":"        for(int i = 0;iresult.length()) result = temp;"}]},{"type":"codeinline","content":[{"type":"text","text":"        return i;"}]},{"type":"codeinline","content":[{"type":"text","text":"    }"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"}"}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"算法:反轉每組K個節點"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單鏈表每k個節點爲一組進行反轉(最後不滿k個時不反轉)"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"leetcode 原題,提供其中的一種解法如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"public class LinkReverse {"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"    public static Node mhead=null;"}]},{"type":"codeinline","content":[{"type":"text","text":"    public static Node mtail=null;"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"    public static Node newLink(int n){"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node head = new Node();"}]},{"type":"codeinline","content":[{"type":"text","text":"        head.setData(1);"}]},{"type":"codeinline","content":[{"type":"text","text":"        head.setNext(null);"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node tmp = head;"}]},{"type":"codeinline","content":[{"type":"text","text":"        for(int i=2;i<=n;i++){"}]},{"type":"codeinline","content":[{"type":"text","text":"            Node newNode = new Node();"}]},{"type":"codeinline","content":[{"type":"text","text":"            newNode.setData(i);"}]},{"type":"codeinline","content":[{"type":"text","text":"            newNode.setNext(null);"}]},{"type":"codeinline","content":[{"type":"text","text":"            tmp.setNext(newNode);"}]},{"type":"codeinline","content":[{"type":"text","text":"            tmp = newNode;"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        return head;"}]},{"type":"codeinline","content":[{"type":"text","text":"    }"}]},{"type":"codeinline"},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"    public static void main(String[] args) {"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node node = newLink(10);"}]},{"type":"codeinline","content":[{"type":"text","text":"        pritNode(node);"}]},{"type":"codeinline","content":[{"type":"text","text":"//        Node node1 = reverseKLink(node,3);"}]},{"type":"codeinline","content":[{"type":"text","text":"//        Node node1 = reverse(node,2);"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node node1 = reverseLink3(node,4);"}]},{"type":"codeinline","content":[{"type":"text","text":"        pritNode(node1);"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"    }"}]},{"type":"codeinline","content":[{"type":"text","text":"    public static void pritNode(Node head){"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node temp = head;"}]},{"type":"codeinline","content":[{"type":"text","text":"        while(temp !=null){"}]},{"type":"codeinline","content":[{"type":"text","text":"            System.out.print(temp.getData()+\"->\");"}]},{"type":"codeinline","content":[{"type":"text","text":"            temp = temp.getNext();"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        System.out.println();"}]},{"type":"codeinline","content":[{"type":"text","text":"    }"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"    /*public static Node reverseLink(Node head){"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node pre=null,cur=head,next=null;"}]},{"type":"codeinline","content":[{"type":"text","text":"        while(cur!=null){"}]},{"type":"codeinline","content":[{"type":"text","text":"            next=cur.getNext();"}]},{"type":"codeinline","content":[{"type":"text","text":"            cur.setNext(pre);"}]},{"type":"codeinline","content":[{"type":"text","text":"            pre=cur;"}]},{"type":"codeinline","content":[{"type":"text","text":"            cur=next;"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        return pre;"}]},{"type":"codeinline","content":[{"type":"text","text":"    }*/"}]},{"type":"codeinline"},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"    /*public static Node reverseKLink(Node head,int k){"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node pre=null,cur=head,next=null;"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"        Node pnext=null,global_head=null;"}]},{"type":"codeinline","content":[{"type":"text","text":"        int i=0;"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node tmp=null;"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"        while(cur!=null){"}]},{"type":"codeinline","content":[{"type":"text","text":"            next = cur.getNext();"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"            if(tmp==null) tmp=cur;   //tmp記錄當前組反轉完最後一個節點"}]},{"type":"codeinline","content":[{"type":"text","text":"            if(pnext!=null) pnext.setNext(cur);  //pnext是上一組反轉完最後一個節點"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"            cur.setNext(pre);"}]},{"type":"codeinline","content":[{"type":"text","text":"            pre=cur;"}]},{"type":"codeinline","content":[{"type":"text","text":"            cur = next;"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"            i++;"}]},{"type":"codeinline","content":[{"type":"text","text":"            if(i>=k){  //當前組反轉完成的時候"}]},{"type":"codeinline","content":[{"type":"text","text":"                if(global_head==null){"}]},{"type":"codeinline","content":[{"type":"text","text":"                    global_head=pre;"}]},{"type":"codeinline","content":[{"type":"text","text":"                }"}]},{"type":"codeinline","content":[{"type":"text","text":"                if(pnext!=null){  //將上一組反轉完的最後一個節點指向 當前組反轉完後的第一個節點"}]},{"type":"codeinline","content":[{"type":"text","text":"                    pnext.setNext(pre);"}]},{"type":"codeinline","content":[{"type":"text","text":"                }"}]},{"type":"codeinline","content":[{"type":"text","text":"                pnext=tmp; //迭代"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"                i=0;  //新的一組反轉時 關鍵數據初始化"}]},{"type":"codeinline","content":[{"type":"text","text":"                tmp=null;"}]},{"type":"codeinline","content":[{"type":"text","text":"                pre=null;"}]},{"type":"codeinline","content":[{"type":"text","text":"            }"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        return global_head;"}]},{"type":"codeinline","content":[{"type":"text","text":"    }*/"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"    //反轉每組"}]},{"type":"codeinline","content":[{"type":"text","text":"    public static void inreverse(Node left,Node right){"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node pre=null,cur=left,next=null;"}]},{"type":"codeinline","content":[{"type":"text","text":"        while(pre!=right){"}]},{"type":"codeinline","content":[{"type":"text","text":"            next = cur.getNext();"}]},{"type":"codeinline","content":[{"type":"text","text":"            cur.setNext(pre);"}]},{"type":"codeinline","content":[{"type":"text","text":"            pre=cur;"}]},{"type":"codeinline","content":[{"type":"text","text":"            cur=next;"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        if(mtail!=null) mtail.setNext(right);"}]},{"type":"codeinline","content":[{"type":"text","text":"        mhead=right;"}]},{"type":"codeinline","content":[{"type":"text","text":"        mtail=left;"}]},{"type":"codeinline","content":[{"type":"text","text":"    }"}]},{"type":"codeinline","content":[{"type":"text","text":"    //每k個節點爲一組反轉"}]},{"type":"codeinline","content":[{"type":"text","text":"    public static Node reverseLink3(Node head,int k){"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node cur=head,global_head=head;"}]},{"type":"codeinline","content":[{"type":"text","text":"        int i=1;"}]},{"type":"codeinline","content":[{"type":"text","text":"        Node left=null,right=null;"}]},{"type":"codeinline","content":[{"type":"text","text":"        while(cur!=null){"}]},{"type":"codeinline","content":[{"type":"text","text":"            if(i%k==1)"}]},{"type":"codeinline","content":[{"type":"text","text":"                left=cur;"}]},{"type":"codeinline","content":[{"type":"text","text":"            right=cur;"}]},{"type":"codeinline","content":[{"type":"text","text":"            cur=cur.getNext();"}]},{"type":"codeinline","content":[{"type":"text","text":"            if(i%k==0){"}]},{"type":"codeinline","content":[{"type":"text","text":"                inreverse(left,right);"}]},{"type":"codeinline","content":[{"type":"text","text":"                if(mtail!=null){"}]},{"type":"codeinline","content":[{"type":"text","text":"                    mtail.setNext(cur);"}]},{"type":"codeinline","content":[{"type":"text","text":"                }"}]},{"type":"codeinline","content":[{"type":"text","text":"                if(global_head==head){"}]},{"type":"codeinline","content":[{"type":"text","text":"                    global_head = mhead;"}]},{"type":"codeinline","content":[{"type":"text","text":"                }"}]},{"type":"codeinline","content":[{"type":"text","text":"            }"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"            i++;"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        return global_head;"}]},{"type":"codeinline","content":[{"type":"text","text":"    }"}]},{"type":"codeinline"},{"type":"codeinline","content":[{"type":"text","text":"}"}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"算法題:判斷一個IP是否在國內"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸入:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據庫中有幾十萬的國內 IP 段 (start_ip, end_ip)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個待驗證的 IP"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"YES or NO"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,整理 IP 段配置。爲了方便 IP 進行比較,這裏將 IP 轉換爲 long 格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把數據load進來,取第一第二行,ip2long 處理"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一行保存在left,第二行保存在 right 中"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後根據left進行排序"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"得到類似如下的結果:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"('16842752','16843007'),('16843264','16859135')"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來使用二分法查找即可,較爲簡單。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"手寫代碼:合併N個鏈表 -> 優化爲 log(n) -> null 判斷 -> 不允許修改數據結構怎麼實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"出自"},{"type":"link","attrs":{"href":"https://leetcode-cn.com/problems/merge-k-sorted-lists/","title":null},"content":[{"type":"text","text":"LeetCode第二十三題-合併n個有序鏈表"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最簡單粗暴的方法是建立一個集合,遍歷所有鏈表,將其元素添加到集合中,將集合通過數組的方式升序排序,將其添加到一個新的鏈表中並返回。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種方法的時間複雜度:o(n2)外層遍歷一遍數組內層遍歷鏈表的元素,即雙層遍歷,還有一個單層遍歷,所以結果近似於o(n2);空間複雜度:o(n)定義了數組長度多的變量listNode,定義了集合長度的鏈表長度即o(n)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"考慮優化如上的方法,用分治的方法進行合併。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b0/b0f07e81d3622292c7470b1643717e44.jpeg","alt":"","title":"","style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2c/2cb8dac86871cf39afdab985b753ab8c.png","alt":"","title":"","style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"public ListNode mergeKLists(List lists) {"}]},{"type":"codeinline","content":[{"type":"text","text":"        // write your code here"}]},{"type":"codeinline","content":[{"type":"text","text":"        if (lists == null || lists.size() == 0) {"}]},{"type":"codeinline","content":[{"type":"text","text":"            return null;"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        ListNode dummy = new ListNode(0);"}]},{"type":"codeinline","content":[{"type":"text","text":"        ListNode p = dummy;"}]},{"type":"codeinline","content":[{"type":"text","text":"        Queue queue = new PriorityQueue(lists.size(), new Comparator()"}]},{"type":"codeinline","content":[{"type":"text","text":"        {   "}]},{"type":"codeinline","content":[{"type":"text","text":"            @Override"}]},{"type":"codeinline","content":[{"type":"text","text":"            public int compare(ListNode o1, ListNode o2) {"}]},{"type":"codeinline","content":[{"type":"text","text":"                return o1.val - o2.val;"}]},{"type":"codeinline","content":[{"type":"text","text":"            }"}]},{"type":"codeinline","content":[{"type":"text","text":"        });"}]},{"type":"codeinline","content":[{"type":"text","text":"        for (ListNode node : lists) {"}]},{"type":"codeinline","content":[{"type":"text","text":"            if (node != null) {"}]},{"type":"codeinline","content":[{"type":"text","text":"                queue.offer(node);"}]},{"type":"codeinline","content":[{"type":"text","text":"            }"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        while (!queue.isEmpty()) {"}]},{"type":"codeinline","content":[{"type":"text","text":"             ListNode node = queue.poll();"}]},{"type":"codeinline","content":[{"type":"text","text":"                 p.next = node;"}]},{"type":"codeinline","content":[{"type":"text","text":"                 if (node.next != null) {"}]},{"type":"codeinline","content":[{"type":"text","text":"                     queue.offer(node.next);"}]},{"type":"codeinline","content":[{"type":"text","text":"                 }"}]},{"type":"codeinline","content":[{"type":"text","text":"                 p = p.next;"}]},{"type":"codeinline","content":[{"type":"text","text":"        }"}]},{"type":"codeinline","content":[{"type":"text","text":"        return dummy.next;"}]},{"type":"codeinline","content":[{"type":"text","text":"}"}]},{"type":"codeinline","content":[{"type":"text","text":"public ListNode mergeTwoLists(ListNode l1, ListNode l2) {"}]},{"type":"codeinline","content":[{"type":"text","text":"        ListNode dummy = new ListNode(-1);"}]},{"type":"codeinline","content":[{"type":"text","text":"        ListNode p = dummy;"}]},{"type":"codeinline","content":[{"type":"text","text":"        ListNode p1 = l1;"}]},{"type":"codeinline","content":[{"type":"text","text":"        ListNode p2 = l2;"}]},{"type":"codeinline","content":[{"type":"text","text":"        while (p1 != null && p2 != null) {"}]},{"type":"codeinline","content":[{"type":"text","text":"            if (p1.val 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章