最近讀了一篇好文:【微信高併發資金交易系統設計方案——百億紅包背後的技術支撐】,其中關於高併發效能問題的解決方案中,有應用 hash 演算法的思想。想起公眾號後臺裡斷斷續續有讀者提起演算法方面的問題,覺得可以寫篇文章聊聊演算法中的 hash 演算法。順道科普下演算法與資料結構的重要性。
開講前,先跑題閒聊下程式設計師的技術功底。我常說每個程式設計師都有自己獨特的技術視野和知識盲區,不同程式設計師之間很難因為某些知識點儲備不一樣而分個高低好壞。但我們工作當中,又能明顯感覺不同團隊成員之間的技術水平存在差異,到底差在哪呢?很多人調侃批量生產的培訓程式設計師,那這些人和四年的大學本科之間又有多少距離?僅僅是時間嗎?
差在基本功,基本功有很多項,資料結構與演算法就是其中之一。雖然是基本功,卻是最難儲備和最易忽視的。行業越浮躁,變化越快,開發平臺越便捷,高階 API 越多,基本功的重要性就越容易被忽視。即使能意識到基礎薄弱,肯下定決心騰出幾個月時間惡補基本功不是件容易的事,尤其是參加工作後,瑣事繁多,一時熱血下定的決心能堅持一週都屬不易。後臺偶爾有人問及程式設計師如何進階的問題,以我這些年所經驗,回過頭來夯實下基礎,對大部分人都會有奇效。
資料結構與演算法的學習難度經常被誇大,不少人甚至談演算法色變,尤其無法忍受在面試當中問及演算法問題。其實多點兒耐心,多投入些時間,學習演算法並不難。至少學習基礎的演算法並不難,理解演算法和去 leetcode 刷題是兩回事,刷題所涉及的演算法多需要技巧,基礎的演算法知識和其他計算機知識一樣,不需要特別「聰明」的大腦,大多數人都能學會。Peak 君沒刷過題,但對演算法方面的知識也比較有自信。
資料結構和演算法是相輔相成的,基礎的其實就那麼些:時間複雜度的概念,List,Array,Stack,Queue,Tree 等。Graph 實際應用中較少遇到,可以不做深入瞭解,但 BFS,DFS,Dijkstra 還是應該知道。基礎的演算法需要能達到手寫的程度,比如排序至少能寫出兩種時間複雜度為 N*logN 的演算法。理解這些比去 leetcode 刷題重要,學習難度也並不高。學習這些的意義在於掌握解決問題的基礎思路,形成計算機思維,比如 divide and conque,recursive 等常規思想。
再回到本文重點 hash 演算法。關於 hash 演算法的實現原理和關鍵概念,網路上已有不少好文加以介紹。本文不做原理層面的解釋,只談應用。對實現感興趣的可以搜尋關鍵字:hash,load factor,擴容,hash 衝突解決等。
Objective C 中對於 hash 的應用主要封裝在兩個資料類當中:NSDictionary 和 NSSet。這點大家都知道,hash 演算法能以空間換時間,在 NSDictionary 和 NSSet 中,判斷一個元素是否存在只需要 O(1) 的時間複雜度。這一特點也使得在一些需要快速存取元素的場景,比如 Cache 設計,也能看到 NSDictionary 的身影。當然 hash 的應用遠不止如此,做的應用越多,解決問題越深入,碰到 hash 演算法的概率也會更高。
「The Algorithm Design Manual」一書中提到,雅虎的 Chief Scientist ,Udi Manber 曾說過,在 yahoo 所應用的演算法中,最重要的三個是:hash,hash 和 hash。其重要性不言而喻。書中還舉了一個很有趣的應用例子,請聽題:
一場拍賣會中,物品是價高者得,如果每個人只有一次出價機會,同時提交自己的價格後,最後一起公佈,出價最高則勝出。這種形式存在作弊的可能,如果有出價者能 hack 進後臺,然後將自己的價格改為最高價 + 1,則能以最低的代價獲得勝利。如何杜絕這種作弊呢?
三分鐘思考時間,一,二,三。
參與者都提交自身出價的 hash 值就可以了,即使有人能黑進後臺也無法得知明文價格,等到公佈之時,再對比原出價與 hash 值是否對應即可。是不是很巧妙?
看到這,可能有朋友想到了 MD5,SHA。是的,上面的做法,和我們在 server 端儲存密碼的 MD5 值而非明文,是同一種思想,殊途同歸。hash 演算法包含有多種解決問題的思路,這裡可以歸納為【通過 hash,生成不可逆的資訊摘要】。
書裡還有關於 hash 應用的其他有趣的場景(比如論文內容抄襲檢測),都值得一讀。
回到微信紅包的例子,後臺工程師為了防止搶紅包時,使用者的流量都湧進同個伺服器,在同個 DB 上讀寫而導致的效能下降,採用了通過 hash 演算法來分流的策略。每個紅包建立的時候分配一個 ID,通過演算法將 ID 對映到不同的邏輯伺服器,一氣呵成的解決方案。這裡體現的是 hash 演算法的另一種思想:【hash 能以 O(1)的複雜度將內容對映到位置】。這種應用 hash 的思路非常常見,還有不少例子。
去年寫過一篇多執行緒文章【正確使用多執行緒同步鎖@synchronized()】,當時閱讀 OC 原始碼的時候也看到了 hash 的身影。@synchronized(token) 中的 token 通過 hash 演算法儲存到了一份手動維護的 cache 中,cache 的 key 使用的是 token 的記憶體地址。@synchronized 使用多了之後,如何快速的通過 token 取出對應的鎖,對多執行緒的效能至關重要。hash 演算法恰能以 O(1)的時間複雜度,以 token 為 key 取出對應的鎖,和上面紅包的例子本質上是同一種思想。即內容與位置之間的快速對映關係。
我還見到過很多例子,多多少少都有 hash 演算法的影子。大家說 hash 演算法是不是很重要?資料結構與演算法是不是要學?不懂演算法,有時候看別人程式碼就真如「過眼雲煙」了,觀其形而不知其意。別人賞雪,心中所念是:「都城十日雪,庭戶皓已盈」,你只能一句「我靠!好美!」以抒胸臆,豈不煞風景?
之前有幾位讀者在後臺留言,求演算法書推薦。演算法方面的好書有不少,不過大多是英文的,除去一些專業術語外,多是一些簡單的詞彙,閱讀難度不算大,維堅持二字。推薦一本現今還留有印象的:《程式設計珠璣》,英文名《Programming Pearls》,不是大部頭,建議啃英文原版。
最近有些忙了,不過至少還是會堅持一週一到兩篇,和大家分享些技術心得和職場感悟。堅持做一件事不容易,寫公眾號這事,怎麼都得挺住。寫年終總結時,就多了件可以吹噓的事 :)
歡迎關注公眾號:MrPeakTech