大家好,今天給大家分享一篇CSDN的高贊帖子,該博主從騰訊面試之后思考的一個面試題:“百度/google的搜索為什么那么快?”帖子說得十分詳細,值得大家一看▼▼▼
我還記得去年面騰訊時,面試官最后輕飄飄的問:“百度/google的搜索為什么那么快?”
這個問題我懵了,我從來沒想過搜索引擎的原理是什么。
然后我回答:“百度爬取了各個網站的信息,然后進行排序,當輸入關鍵詞的時候進行文檔比對……”
面試官:“這不是我想要的答案。”
我內心:
這個問題我一直耿耿于懷,終于今天,我把它寫出來,以后再問,我直接把這篇文章甩給面試官!!!
兩個字:倒排,這兩個字將貫穿整篇文章,也是面試官想要的答案。
首先我們知道,百度肯定是有爬蟲,到處爬取網頁,進行某種處理,然后通過你輸入的關鍵詞進行某種計算再返回給你的。
我們先來看看什么是某種處理:
#某種處理
當百度爬取了海量網頁后,每一個網頁我們稱為”文檔“,不可能就雜亂無章的放著,它使用了文檔集合,就是類似的文檔放在一個集合中。
那什么樣的文檔算類似呢?相信你猜到了,文檔中有相同關鍵字的就可以放在一個集合中。
來舉例說明:
假設全世界只有下面5個文檔(網頁),文檔內容也很簡單,就一句話(注意是內容,不是標題)。
百度爬取后,將他們進行編號,然后對文檔進行掃描分詞,因為百度內部有詞庫,匹配上的詞將被切分,所以文檔1號將被切分為【谷歌,地圖,之父,跳槽,FaceBook】,后面的文檔也一樣,然后對切分出來的單詞進行倒排處理,形成倒排列表。
啥是倒排處理?右邊這堆雜亂無章的數字咋來的?別急,仔細看,1號單詞“谷歌”是不是在1,2,3,4,5號文檔都出現過?9號單詞“離開”是不是只在3號文檔出現過?
是的,倒排列表所做的,就是保存對應單詞所出現過的文檔編號。
我想你開始明白他的目的了,當我們搜索“谷歌”的時候,他就會獲得“谷歌”這一單詞對應的倒排列表,知道哪些文檔包含他,然后將這些文檔提取出來返回給你,這就是一種單詞映射文檔的方法。
但是,沒那么簡單,因為只有這樣的話,我在一篇博客上把所有的單詞都寫上,這樣雜亂無章的文章豈不是要被推薦給全體中國人???
所以倒排列表還要保存下列信息:
保留的信息變成了二元組,比如16號單詞“網站”的(5:1),5表示出現的文檔編號,1表示出現的次數,也就是說,有了這個信息,如果一個單詞在文檔中頻率越高(英文縮寫TF),搜索引擎就可以把他排在前面推給你。
除了頻率,還有位置,比如”谷歌“就是在1號文檔中出現了一次的單詞,位置在第一個,用<1>表示:
可能到這你有點記不住有哪些網頁了,再看一遍比對下:
這樣子,搜素引擎就可以根據你的關鍵詞在倒排列表中找到含有這個關鍵詞的文檔集合,然后根據關鍵詞在文檔集合中各個文檔出現的頻率和位置綜合判斷返回給你排序后的文檔。
實際上很多搜索引擎基本就是這樣做的,只不過各家還有別的參考標準,比如百度還會參考熱度,你的搜索記錄,還有網站給的錢(你懂的)等等綜合打分,按評分高低返回搜索結果的排序。
上面的所有記錄處理好后都會存放在磁盤中,然后等你關鍵詞來后再調入內存。
假設世界上只有5個文檔,那么上面的東西完全夠了,但實際上,世界上有億萬個文檔,此時問題的性質已經變了。
不是找不找得到的問題,而是怎么找更快,更準的問題,這需要算法,也就是我們上面提到的某種計算。
#某種計算
第一個問題:詞庫那么多,當你輸入“蘋果”的時候,百度如何將你的關鍵詞和他內部倒排列表的“蘋果”一詞聯系起來?
計算機是不認識“蘋果”的,這里,可以通過哈希的方法將“蘋果”轉換為一個編號。
所謂哈希,即使將一個詞通過某種算法映射為一個符號,比如“將單詞轉換為其長度”就是一種算法,雖然很low,這樣“蘋果”就是2,“梨”就是1。
不同的哈希算法有不同的轉換結果,但是必然會有一個東西——哈希沖突,比如“桃子”也是2,此時,需要使用鏈表,也稱沖突表,將編號相同的單詞鏈在一起。
當我們搜索“蘋果”的時候,經過哈希計算,得知其編號為2,然后發現2中有一個鏈表,里面可能保存著“蘋果”,”桃子”,“蘑菇”等,然后再遍歷鏈表找到蘋果即可。
這里和java8中的hashmap思想一致,不過鏈表也會過長,所以可以使用別的數據結構代替,比如紅黑樹,b樹等。
解決了第一個問題,我們就可以通過關鍵詞獲得他的Id,然后得到所建立的倒排列表了,比如“谷歌”。
第二個問題:由于文檔的數量龐大,我們獲取的文檔往往編號位數都很多,而不像上圖那樣1,2,3,4,5,導致倒排列表無謂的擴大,所以我們這里進行作差。
就是后面的文檔編號減去前面的,在取文檔(從磁盤中讀取)的時候加回來即可。
第三個問題:如何從磁盤中讀取文檔?
現在我們已經有了倒排列表:
可以有兩種方法從磁盤中讀取文檔:
兩次遍歷法
第一遍,掃描文檔集合,找到文檔數量N, 文檔集合內所包含的不同單詞數M,和每個單詞出現的頻率DF(如下圖),以及一些別的必要信息,這些東西所占內存加起來,得到需要開辟的內存空間,
同時這個空間是以單詞為單位劃分,比如“谷歌”一詞有5篇文檔:
1.第一遍主要就是確定要開辟多大的內存空間來顯示文檔。
2.第二遍掃描,就是邊掃描,匹配對應的文檔編號(三元組中的第一個數),載入內存。
但是這個方法有一個問題,那就是文檔集合有多大,內存就有多大,所以,很可能內存會溢出,不過都放在內存中速度也很快,這是一種空間換時間的方法。
相信你發現了,但凡設計到讀取,一定有兩種以上的方法,空間優先或是時間優先,第二種就是時間換空間——排序法。
排序法
現在我們只用固定大小的內存,如何從上圖中的倒排列表得知每個單詞對應的文章集合所需要的內存空間有多少呢?
我們需要解析文檔,構造(單詞ID,文檔ID,單詞頻率)三元組,然后進行排序,按單詞ID,文檔ID,單詞頻率先后排,最后如果規定的內存滿了,就將這些三元組通通寫入一個臨時文件A中。
為什么要這樣呢?想想看,如果我們最后拿到了一個(單詞A,文檔A,單詞頻率),我們就可以很輕松的知道一個單詞對應哪個文檔,和對應的頻率,
也就是一個三元組告訴我們單詞A對應的文檔A,另一個三元組告訴我們單詞A對應文檔B……
這些三元組加起來我們就知道了單詞A對應的文檔集合,就可以知道它需要多少內存空間來填補這些文檔了。
可能解析50個文檔后規定的內存就滿了,然后把這些三元組們寫入磁盤臨時文件A,就可以再讀下一篇50個文檔了。
注意,詞典是不斷增加的,比如前50個文檔只有上面7個單詞,后50個文檔可能出現了別的單詞,此時要插入詞典中,詞典一直在內存。
這樣,只用固定大小的內存就可以50一批的解析完所有文檔,寫入了一個個的臨時文件A,B,C,D,再將這些臨時文件合并,就是把他們分別讀入內存中的緩沖區,合成最終索引后再寫入磁盤。
這樣通過最終索引就知道有哪些單詞對應多少文檔,還有頻率,然后根據這些開辟內存空間讀取進入內存返回給你即可。
排序法敘述起來比較復雜,但是其實理解起來很簡單,耐心讀一定能懂哦~
————————————————
版權聲明:本文為CSDN博主「小松與蘑菇」的原創文章。
原文鏈接: