关于海量数据查找排序问题
問題:假設(shè)一個文件中有9億條不重復(fù)的9位整數(shù),現(xiàn)在要求對這個文件進行排序。?
一般解題思路:?
1、將數(shù)據(jù)導(dǎo)入到內(nèi)存中?
2、將數(shù)據(jù)進行排序 (比如插入排序、快速排序)?
3、將排序好的數(shù)據(jù)存入文件?
難題:?
一個整數(shù)為4個字節(jié)?
即使使用數(shù)組也需要900,000,000 * 4byte = 3.4G內(nèi)存?
對于32位系統(tǒng),訪問2G以上的內(nèi)存非常困難,而且一般設(shè)備也沒有這么多的物理內(nèi)存?
將數(shù)據(jù)完全導(dǎo)入到內(nèi)存中的做法不現(xiàn)實?
其他解決辦法:?
1、導(dǎo)入數(shù)據(jù)庫運算?
2、分段排序運算?
3、使用bit位運算?
解決方案一:數(shù)據(jù)庫排序?
將文本文件導(dǎo)入到數(shù)據(jù)庫,讓數(shù)據(jù)庫進行索引排序操作后提取數(shù)據(jù)到文件?
優(yōu)點:操作簡單?
缺點:運算速度慢,而且需要數(shù)據(jù)庫設(shè)備。?
解決方案二:分段排序?
操作方式:?
規(guī)定一個內(nèi)存大小,比如200M,200M可以記錄(200*1024*1024/4) = 52428800條記錄,我們可以每次提取5000萬條記錄到文件進行排序,要裝滿9位整數(shù)需要20次,所以一共要進行20次排序,需要對文件進行20次讀操作?
缺點:?
編碼復(fù)雜,速度也慢(至少20次搜索)?
關(guān)鍵步驟:?
先將整個9位整數(shù)進行分段,億條數(shù)據(jù)進行分成20段,每段5000萬條?
在文件中依次搜索0~5000萬,50000001~1億……?
將排序的結(jié)果存入文件?
解決方案三:bit位操作?
思考下面的問題:?
一個最大的9位整數(shù)為999999999?
這9億條數(shù)據(jù)是不重復(fù)的?
可不可以把這些數(shù)據(jù)組成一個隊列或數(shù)組,讓它有0~999999999(10億個)元素?
數(shù)組下標(biāo)表示數(shù)值,節(jié)點中用0表示這個數(shù)沒有,1表示有這個數(shù)?
判斷0或1只用一個bit存儲就夠了?
聲明一個可以包含9位整數(shù)的bit數(shù)組(10億),一共需要10億/8=120M內(nèi)存?
把內(nèi)存中的數(shù)據(jù)全部初始化為0, 讀取文件中的數(shù)據(jù),并將數(shù)據(jù)放入內(nèi)存。比如讀到一個數(shù)據(jù)為341245909這個數(shù)據(jù),那就先在內(nèi)存中找到341245909這個bit,并將bit值置為1遍歷整個bit數(shù)組,將bit為1的數(shù)組下標(biāo)存入文件?
關(guān)鍵代碼?
檢查是某一個char里面(first)的第second位中存儲的數(shù)據(jù)是否為1?
bool CompareBit (unsigned char first, int second)?
{?
const static int mark_buf[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};?
if (second > .8)?
return false;?
return (first & mark_buf[second]) == mark_buf[second];?
}?
將某一個char(Desc)中的第source位置為1?
bool WriteToBit (unsigned char *Desc, int source)?
{?
const static int mark_buf[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};?
if (source >? .8)?
return false;?
Desc[0] |= mark_buf[source];?
return true;?
}?
案例?
在某個項目中,我們需要對2億條手機號碼刪除重復(fù)記錄(過濾號碼黑名單同樣有效)?
工作難點就在于如何處理這2億條電話號碼,直接用哈希表存放手機號碼不大現(xiàn)實,即使經(jīng)過優(yōu)化,用一個unsigned int存放一條記錄,那也得需要2億*4=8億byte,遠超過32位系統(tǒng)的尋址能力?
解決方案:?
將電話號碼由12位單個數(shù)字組成的字符串轉(zhuǎn)換為一個unsigned int型數(shù)據(jù)(這個完全可能,手機號碼由前三位數(shù)字和后面八位數(shù)字組成,后面八位需要占到1~1000萬的空間,而前面用0~100的數(shù)字存儲已經(jīng)足夠)為簡單起見,默認為0~4G的數(shù)字都有可能分布號碼,為此我們分配4G/32=512M的內(nèi)存將這2億個號碼整理成unsigned int類型后按上述辦法存放在這塊內(nèi)存中(比如13512345678我們整理后為112345678,我們找到內(nèi)存中112345678bit的下標(biāo),并將此bit值設(shè)為1)遍歷整個bit數(shù)組,記錄下所有的號碼,這些號碼即是不重復(fù)的手機號碼?
總結(jié)?
建立一個足夠大的bit數(shù)組當(dāng)作hash表?
以bit數(shù)組的下標(biāo)來表示一個整數(shù)?
以bit位中的0或1來表示這個整數(shù)是否在這個數(shù)組中存在?
適用于無重復(fù)原始數(shù)據(jù)的搜索?
原來每個整數(shù)需要4byte空間變?yōu)?bit,空間壓縮率為32倍?
擴展后可實現(xiàn)其他類型(包括重復(fù)數(shù)據(jù))的搜索 < xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />
?
?
主題:3000w數(shù)據(jù)的表,取某項字段前50項數(shù)據(jù),內(nèi)存2G
偶然看到這個題,就想了一下怎么做,大體實現(xiàn)思路是這樣子的,3000w的數(shù)據(jù)劃分為1000段,也就是1-3w為一段,30001-6w項為第二段,依次類推,從每3w的數(shù)據(jù)中提取出前50條數(shù)據(jù)(這個根據(jù)sql排序就能取出來,2個g的內(nèi)存夠了),最后1000個50就會產(chǎn)生5w個數(shù)據(jù),最后提取出來的5w的數(shù)據(jù)放置到ArrayList中去,最后5w的數(shù)據(jù)統(tǒng)一排序,取出前50條。5w*5w的對比與交換是可以搞定的。具體實現(xiàn),等最近的項目完了用多線程試試!
?
主題:【探討】給你1G內(nèi)存,如何從3000萬個手機號碼中檢索出你要的號碼,要求每秒檢索>1000次
?
?
主題:3億數(shù)據(jù)快速檢索實現(xiàn)
上周有個需求,就是要做一個檢索庫:?
1 3億個手機號碼,并且每個號碼20個左右的屬性例:地區(qū),訂閱等信息。?
2 在最短的時候內(nèi)select出來(5分鐘,10分鐘)[最重要]?
3 允許更新。對這些號碼進行發(fā)送信息后,狀態(tài)改變。[可以讓他慢慢更新]?
和幾個同事討論了一下,具體要注意以下幾點:?
1 如果發(fā)送下去狀態(tài)改變,但是只發(fā)送一半,但狀態(tài)改變了如何辦??
2 如果多個產(chǎn)品線一起下發(fā),狀態(tài)會不會混亂。?
解決以上第二個問題,決定采用,隊列等待的方式。第一個問題沒想到好的解決辦法,回滾也想過了,但感覺不是很現(xiàn)實!?
解決方案:?
經(jīng)過實驗500w條的數(shù)據(jù)在用plsql直接select,只需要0.2秒,所以總體采用分表的方式,每500w條分一個表,然后同時查詢!?
-----------------------------------------重新描述一下需求-------------------------------?
很多人說需求不是很的清楚,這里重新整理了一下!?
不過要注意的是數(shù)據(jù)庫里已經(jīng)有3億個手機基數(shù)了!?
一.號碼入庫。?
不定期會有新的號碼需要入庫,入庫需要記錄號碼的常規(guī)屬性,如:手機號,省份,城市,入庫時間,手機卡類型,是否支持彩信,號碼來源情況等。?
二.入庫手機號源文件管理?
入庫手機號源文件要以文件形式保存在服務(wù)器上。?
三.按需要提取號碼(最關(guān)鍵部分)?
要按照需求提取所需的號碼。?
例如:?
提號要求:?
1.此號碼非黑名單用戶。?
2.此號碼為的訂購和退訂用戶。?
3.此號碼2個月內(nèi)沒有活動。?
4.省份要求:遼寧,云南,廣東?
5.號段要求:137和138和139號段?
6.數(shù)量要求:每個省10w?
7.是否支持彩信:是(是,否,忽略三種情況)?
……?
最后,符合條件的號碼,按照固定格式(每個手機號占一行),形成文本文件,將此文件測試號碼,是否需要狀態(tài)報告等信息形成最終可發(fā)送文件并提供下載功能,同時記錄本次提取信息(發(fā)送時間,發(fā)送標(biāo)識等)?
注:文件格式如下:?
139***85185#09#0?
139***71283?
139***33190?
第1列:手機號?
第2列:產(chǎn)品類型(#09)?
第3列:是否需要狀態(tài)報告(#0)?
四.統(tǒng)計功能?
一.號碼情況統(tǒng)計?
1.統(tǒng)計當(dāng)前號碼總量。?
2.按照2個基本要求,統(tǒng)計現(xiàn)在庫中可以使用的號碼數(shù)量。?
注:統(tǒng)計需要顯示,全國總量,各省總量,各省省會總量,各省去除省會總量,各省7天未下發(fā)總量(省會與其他城市分開顯示),各省可以發(fā)送總量(省會與其他城市分開顯示,所以單獨列出來)。?
二.發(fā)送產(chǎn)品統(tǒng)計?
1.按時間段、業(yè)務(wù)線等統(tǒng)計發(fā)送產(chǎn)品的情況,如:發(fā)送時間,最終發(fā)送文件等?
五.黑名單及特殊號碼管理?
1.?添加黑名單?
2.?去除黑名單?
3.?過濾黑名單?
4.?查詢黑名單?
以上除黑名單外都是迫切需要的,黑名單功能可以以后完善。
?
?
?
現(xiàn)在有一萬(1-10000)的個數(shù),從中拿掉一個數(shù),還剩9999個數(shù),現(xiàn)在用一個數(shù)組來存儲這9999個數(shù),問怎么才能找出拿掉的數(shù)?
用10000個數(shù)的數(shù)組循環(huán)匹配9999個數(shù),匹配成功,從9999數(shù)組中去除,不成功就是該數(shù)。?
大家還有什么好的思路沒有?
?
問題:假設(shè)一個文件中有9億條不重復(fù)的9位整數(shù),現(xiàn)在要求對這個文件進行排序。
一般解題思路:?1、將數(shù)據(jù)導(dǎo)入到內(nèi)存中 2、將數(shù)據(jù)進行排序 (比如插入排序、快速排序) 3、將排序好的數(shù)據(jù)存入文件
難題:?一個整數(shù)為4個字節(jié)即使使用數(shù)組也需要900,000,000 * 4byte = 3.4G內(nèi)存對于32位系統(tǒng),訪問2G以上的內(nèi)存非常困難,而且一般設(shè)備也沒有這么多的物理內(nèi)存將數(shù)據(jù)完全導(dǎo)入到內(nèi)存中的做法不現(xiàn)實。
其他解決辦法:?1、導(dǎo)入數(shù)據(jù)庫運算 2、分段排序運算 3、使用bit位運算
解決方案一:數(shù)據(jù)庫排序?將文本文件導(dǎo)入到數(shù)據(jù)庫,讓數(shù)據(jù)庫進行索引排序操作后提取數(shù)據(jù)到文件
優(yōu)點:操作簡單缺點:運算速度慢,而且需要數(shù)據(jù)庫設(shè)備。
解決方案二:分段排序?操作方式:規(guī)定一個內(nèi)存大小,比如200M,200M可以記錄52428800條記錄,我們可以每次提取5000萬條記錄到文件進行排序,要裝滿9位整數(shù)需要20次,所以一共要進行20次排序,需要對文件進行20次讀操作
缺點:?編碼復(fù)雜,速度也慢(至少20次搜索)
關(guān)鍵步驟:先將整個9位整數(shù)進行分段,億條數(shù)據(jù)進行分成20段,每段5000萬條,在文件中依次搜索0~5000萬,50000001~1億…… 將排序的結(jié)果存入文件
解決方案三:bit位操作?思考下面的問題: 一個最大的9位整數(shù)為999999999 這9億條數(shù)據(jù)是不重復(fù)的,可不可以把這些數(shù)據(jù)組成一個隊列或數(shù)組,讓它有0~999999999(10億個)元素數(shù)組下標(biāo)表示數(shù)值,節(jié)點中用0表示這個數(shù)沒有,1表示有這個數(shù),判斷0或1只用一個bit存儲就夠了
聲明一個可以包含9位整數(shù)的bit數(shù)組(10億),一共需要10億/8=120M內(nèi)存,把內(nèi)存中的數(shù)據(jù)全部初始化為0 ,讀取文件中的數(shù)據(jù),并將數(shù)據(jù)放入內(nèi)存。比如讀到一個數(shù)據(jù)為341245909這個數(shù)據(jù),那就先在內(nèi)存中找到341245909這個bit,并將bit值置為1 ,遍歷整個bit數(shù)組,將bit為1的數(shù)組下標(biāo)存入文件
關(guān)鍵代碼?檢查是某一個char里面(first)的第second位中存儲的數(shù)據(jù)是否為1
bool CompareBit (unsigned char first, int second)
?{
const static int mark_buf[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
if (second > 8) return false;
return (first & mark_buf[second]) == mark_buf[second];
?}
將某一個char(Desc)中的第source位置為1
bool WriteToBit (unsigned char *Desc, int source)
{
const static int mark_buf[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
if (source > 8) return false;
Desc[0] |= mark_buf[source];
return true;
}
案例?在某個項目中,我們需要對2億條手機號碼刪除重復(fù)記錄(過濾號碼黑名單同樣有效)
工作難點就在于如何處理這2億條電話號碼,直接用哈希表存放手機號碼不大現(xiàn)實,即使經(jīng)過優(yōu)化,用一個unsigned int存放一條記錄,那也得需要2億*4=8億byte,遠超過32位系統(tǒng)的尋址能力
解決方案:?將電話號碼由12位單個數(shù)字組成的字符串轉(zhuǎn)換為一個unsigned int型數(shù)據(jù)(這個完全可能,手機號碼由前三位數(shù)字和后面八位數(shù)字組成,后面八位需要占到1~1000萬的空間,而前面用0~100的數(shù)字存儲已經(jīng)足夠) ,為簡單起見,默認為0~4G的數(shù)字都有可能分布號碼,為此我們分配4G/32=512M的內(nèi)存,將這2億個號碼整理成unsigned int類型后按上述辦法存放在這塊內(nèi)存中(比如13512345678我們整理后為112345678,我們找到內(nèi)存中112345678bit的下標(biāo),并將此bit值設(shè)為1) ,遍歷整個bit數(shù)組,記錄下所有的號碼,這些號碼即是不重復(fù)的手機號碼
總結(jié)?建立一個足夠大的bit數(shù)組當(dāng)作hash表,以bit數(shù)組的下標(biāo)來表示一個整數(shù),以bit位中的0或1來表示這個整數(shù)是否在這個數(shù)組中存在,適用于無重復(fù)原始數(shù)據(jù)的搜索,原來每個整數(shù)需要4byte空間變?yōu)?bit,空間壓縮率為32倍,擴展后可實現(xiàn)其他類型(包括重復(fù)數(shù)據(jù))的搜索
注意?由于操作系統(tǒng)和編程語言本身的限制,有可能內(nèi)存足夠,但無法分配一塊連續(xù)大內(nèi)存的情況,這樣的話可以申請多塊稍微小一點的內(nèi)存,然后用鏈表或其他的方式連接起來使用
?
?
關(guān)于海量數(shù)據(jù)處理
常用的數(shù)據(jù)結(jié)構(gòu):
1.Bloom Filter
?? 大致思想是這樣,把一個數(shù)據(jù)通過N個哈希函數(shù)映射到一個長度為M的數(shù)組的一位上,將hash函數(shù)對應(yīng)的值的位數(shù)組置1,查找時如果發(fā)現(xiàn)所有hash函數(shù)對應(yīng)位都是1說明該數(shù)據(jù)的存在。但不能保證完全正確性,但是此方法無比高效。
【實例】給你A,B兩個文件,各存放50億條URL,每條URL占用64字節(jié),內(nèi)存限制是4G,讓你找出A,B文件共同的URL。如果是三個乃至n個文件呢??
2.哈希法
?? 這個簡單,無非是通過一些哈希函數(shù)把元素搞到一個指定的位置,簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數(shù)。這個很一般啊感覺。無非就是分類查找么,完全不如1猛。
3.最大或最小堆
?? ? 就是一個完全的最大或最小二叉樹,用途,比如:1)100w個數(shù)中找最大的前100個數(shù)。?用一個100個元素大小的最小堆即可。感覺還是不錯的。
4.Bit-map
所謂的Bit-map就是用一個bit位來標(biāo)記某個元素對應(yīng)的Value, 而Key即是該元素。由于采用了Bit為單位來存儲數(shù)據(jù),因此在存儲空間方面,可以大大節(jié)省。
【問題實例】
1)已知某個文件內(nèi)包含一些電話號碼,每個號碼為8位數(shù)字,統(tǒng)計不同號碼的個數(shù)。
8位最多99 999 999,大概需要99M個bit,大概10幾m字節(jié)的內(nèi)存即可。 (可以理解為從0-99 999 999的數(shù)字,每個數(shù)字對應(yīng)一個Bit位,所以只需要99M個Bit==1.2MBytes,這樣,就用了小小的1.2M左右的內(nèi)存表示了所有的8位數(shù)的電話)
2)2.5億個整數(shù)中找出不重復(fù)的整數(shù)的個數(shù),內(nèi)存空間不足以容納這2.5億個整數(shù)。
將bit-map擴展一下,用2bit表示一個數(shù)即可,0表示未出現(xiàn),1表示出現(xiàn)一次,2表示出現(xiàn)2次及以上,在遍歷這些數(shù)的時候,如果對應(yīng)位置的值是0,則將其置為1;如果是1,將其置為2;如果是2,則保持不變。或者我們不用2bit來進行表示,我們用兩個bit-map即可模擬實現(xiàn)這個2bit-map,都是一樣的道理。
公司的一道考試題算法分析——大數(shù)據(jù)量整數(shù)排序
????題目大意:移動公司需要對已經(jīng)發(fā)放的所有139段的號碼進行統(tǒng)計排序,已經(jīng)發(fā)放的139號碼段的文件都存放在一個文本文件中(原題是放在兩個文件中),一個號碼一行,現(xiàn)在需要將文件里的所有號碼進行排序,并寫入到一個新的文件中;號碼可能會有很多,最多可能有一億個不同的號碼(所有的139段號碼),存入文本文件中大概要占1.2G的空間;jvm最大的內(nèi)存在300以內(nèi),程序要考慮程序的可執(zhí)行性及效率;只能使用Java標(biāo)準(zhǔn)庫,不得使用第三方工具。?
????這是個典型的大數(shù)據(jù)量的排序算法問題,首先要考慮空間問題,一下把1.2G的數(shù)據(jù)讀入內(nèi)存是不太可能的,就算把1一億條數(shù)據(jù),轉(zhuǎn)都轉(zhuǎn)換成int類型存儲也要占接近400M的空間。當(dāng)時做個題目我并沒有想太多的執(zhí)行效率問題,主要就考慮了空間,而且習(xí)慣性的想到合并排序,基本思想是原文件分割成若干個小文件并排序,再將排序好的小文件合并得到最后結(jié)果,算法大概如下:?
??? 1.順序讀取存放號碼文件的中所有號碼,并取139之后的八位轉(zhuǎn)換為int類型;每讀取號碼數(shù)滿一百萬個,(這個數(shù)據(jù)可配置)將已經(jīng)讀取的號碼排序并存入新建的臨時文件。
??? 2.將所有生成的號碼有序的臨時文件合并存入結(jié)果文件。?
????這個算法雖然解決了空間問題,但是運行效率極低,由于IO讀寫操作太多,加上步驟1中的排序的算法(快速排序)本來效率就不高(對于電話排序這種特殊情況來說),導(dǎo)致1億條數(shù)據(jù)排序運行3個小時才有結(jié)果。?
????如果和能夠減少排序的時間呢?首當(dāng)其沖的減少IO操作,另外如果能夠有更加好排序算法也行。前天無聊再看這個題目時突然想到大三時看《編程珠璣》時上面也有個問題的需求這個這個題目差不多,記得好像使用是位向量(實際上就是一個bit數(shù)組),用電話作為index,心中大喜,找到了解決此問題的最完美方案啦:用位向量存儲電話號碼,一個號碼占一個bit,一億個電話號碼也只需要大概12M的空間;算法大概如下:
????? 1.初始化bits[capacity];
????? 2.順序所有讀入電話號碼,并轉(zhuǎn)換為int類型,修改位向量值:bits[phoneNum]=1;
????? 3.遍歷bits數(shù)組,如果bits[index]=1,轉(zhuǎn)換index為電話號碼輸出。?
??? Java中沒有bit類型,一個boolean值占空間為1byte(感興趣的可以自己寫程序驗證),我自己寫個用int模擬bit數(shù)組的類,代碼如下:?
???
Java代碼< xmlnamespace prefix ="v" ns ="urn:schemas-microsoft-com:vml" />
????
??? bit數(shù)組有了,剩下就是算法代碼,核心代碼如下:?
???
Java代碼
來源:http://blog.csdn.net/lixam/article/details/8845310
總結(jié)
以上是生活随笔為你收集整理的关于海量数据查找排序问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tcp为什么要三次握手,而不能二次握手?
- 下一篇: 全排列的生成算法:字典序法