5 億整數(shù)的大文件雀监,怎么排

來源:foreach_break

cnblogs.com/foreach-break/p/external_sort.html

問題

給你1個文件bigdata,大小4663M在塔,5億個數(shù)幻件,文件中的數(shù)據(jù)隨機,如下一行一個整數(shù):

6196302

3557681

6121580

2039345

2095006

1746773

7934312

2016371

7123302

8790171

2966901

...

7005375

現(xiàn)在要對這個文件進(jìn)行排序,怎么搞蛔溃?

內(nèi)部排序

先嘗試內(nèi)排绰沥,選2種排序方式:

3路快排:

private final int cutoff = 8;


public void perform(Comparable[] a) {

? ? ? ? perform(a,0,a.length - 1);

? ? }


? ? private int median3(Comparable[] a,int x,int y,int z) {

? ? ? ? if(lessThan(a[x],a[y])) {

? ? ? ? ? ? if(lessThan(a[y],a[z])) {

? ? ? ? ? ? ? ? return y;

? ? ? ? ? ? }

? ? ? ? ? ? else if(lessThan(a[x],a[z])) {

? ? ? ? ? ? ? ? return z;

? ? ? ? ? ? }else {

? ? ? ? ? ? ? ? return x;

? ? ? ? ? ? }

? ? ? ? }else {

? ? ? ? ? ? if(lessThan(a[z],a[y])){

? ? ? ? ? ? ? ? return y;

? ? ? ? ? ? }else if(lessThan(a[z],a[x])) {

? ? ? ? ? ? ? ? return z;

? ? ? ? ? ? }else {

? ? ? ? ? ? ? ? return x;

? ? ? ? ? ? }

? ? ? ? }

? ? }


? ? private void perform(Comparable[] a,int low,int high) {

? ? ? ? int n = high - low + 1;

? ? ? ? //當(dāng)序列非常小,用插入排序

? ? ? ? if(n <= cutoff) {

? ? ? ? ? ? InsertionSort insertionSort = SortFactory.createInsertionSort();

? ? ? ? ? ? insertionSort.perform(a,low,high);

? ? ? ? ? ? //當(dāng)序列中小時贺待,使用median3

? ? ? ? }else if(n <= 100) {

? ? ? ? ? ? int m = median3(a,low,low + (n >>> 1),high);

? ? ? ? ? ? exchange(a,m,low);

? ? ? ? ? ? //當(dāng)序列比較大時徽曲,使用ninther

? ? ? ? }else {

? ? ? ? ? ? int gap = n >>> 3;

? ? ? ? ? ? int m = low + (n >>> 1);

? ? ? ? ? ? int m1 = median3(a,low,low + gap,low + (gap << 1));

? ? ? ? ? ? int m2 = median3(a,m - gap,m,m + gap);

? ? ? ? ? ? int m3 = median3(a,high - (gap << 1),high - gap,high);

? ? ? ? ? ? int ninther = median3(a,m1,m2,m3);

? ? ? ? ? ? exchange(a,ninther,low);

? ? ? ? }


? ? ? ? if(high <= low)

? ? ? ? ? ? return;

? ? ? ? //lessThan

? ? ? ? int lt = low;

? ? ? ? //greaterThan

? ? ? ? int gt = high;

? ? ? ? //中心點

? ? ? ? Comparable pivot =? a[low];

? ? ? ? int i = low + 1;


? ? ? ? /*

? ? ? ? * 不變式:

? ? ? ? *? ?a[low..lt-1] 小于pivot -> 前部(first)

? ? ? ? *? ?a[lt..i-1] 等于 pivot -> 中部(middle)

? ? ? ? *? ?a[gt+1..n-1] 大于 pivot -> 后部(final)

? ? ? ? *

? ? ? ? *? ?a[i..gt] 待考察區(qū)域

? ? ? ? */


? ? ? ? while (i <= gt) {

? ? ? ? ? ? if(lessThan(a[i],pivot)) {

? ? ? ? ? ? ? ? //i-> ,lt ->

? ? ? ? ? ? ? ? exchange(a,lt++,i++);

? ? ? ? ? ? }else if(lessThan(pivot,a[i])) {

? ? ? ? ? ? ? ? exchange(a,i,gt--);

? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? i++;

? ? ? ? ? ? }

? ? ? ? }


? ? ? ? // a[low..lt-1] < v = a[lt..gt] < a[gt+1..high].

? ? ? ? perform(a,low,lt - 1);

? ? ? ? perform(a,gt + 1,high);

? ? }

歸并排序:

/**

?* 小于等于這個值的時候,交給插入排序

?*/

private final int cutoff = 8;


/**

?* 對給定的元素序列進(jìn)行排序

?*

?* @param a 給定元素序列

?*/

@Override

public void perform(Comparable[] a) {

? ? Comparable[] b = a.clone();

? ? perform(b, a, 0, a.length - 1);

}


private void perform(Comparable[] src,Comparable[] dest,int low,int high) {

? ? if(low >= high)

? ? ? ? return;


? ? //小于等于cutoff的時候,交給插入排序

? ? if(high - low <= cutoff) {

? ? ? ? SortFactory.createInsertionSort().perform(dest,low,high);

? ? ? ? return;

? ? }


? ? int mid = low + ((high - low) >>> 1);

? ? perform(dest,src,low,mid);

? ? perform(dest,src,mid + 1,high);


? ? //考慮局部有序 src[mid] <= src[mid+1]

? ? if(lessThanOrEqual(src[mid],src[mid+1])) {

? ? ? ? System.arraycopy(src,low,dest,low,high - low + 1);

? ? }


? ? //src[low .. mid] + src[mid+1 .. high] -> dest[low .. high]

? ? merge(src,dest,low,mid,high);

}


private void merge(Comparable[] src,Comparable[] dest,int low,int mid,int high) {


? ? for(int i = low,v = low,w = mid + 1; i <= high; i++) {

? ? ? ? if(w > high || v <= mid && lessThanOrEqual(src[v],src[w])) {

? ? ? ? ? ? dest[i] = src[v++];

? ? ? ? }else {

? ? ? ? ? ? dest[i] = src[w++];

? ? ? ? }

? ? }

}

數(shù)據(jù)太多麸塞,遞歸太深 ->棧溢出秃臣?加大Xss?

數(shù)據(jù)太多哪工,數(shù)組太長 -> OOM奥此?加大Xmx?

耐心不足,沒跑出來.而且要將這么大的文件讀入內(nèi)存雁比,在堆中維護(hù)這么大個數(shù)據(jù)量稚虎,還有內(nèi)排中不斷的拷貝,對棧和堆都是很大的壓力偎捎,不具備通用性蠢终。

sort命令來跑

sort -n bigdata -o bigdata.sorted

跑了多久呢?24分鐘.

為什么這么慢茴她?

粗略的看下我們的資源:

內(nèi)存

jvm-heap/stack寻拂,native-heap/stack,page-cache,block-buffer

外存

swap + 磁盤

數(shù)據(jù)量很大败京,函數(shù)調(diào)用很多兜喻,系統(tǒng)調(diào)用很多,內(nèi)核/用戶緩沖區(qū)拷貝很多赡麦,臟頁回寫很多朴皆,io-wait很高帕识,io很繁忙,堆棧數(shù)據(jù)不斷交換至swap遂铡,線程切換很多肮疗,每個環(huán)節(jié)的鎖也很多.

總之,內(nèi)存吃緊扒接,問磁盤要空間伪货,臟數(shù)據(jù)持久化過多導(dǎo)致cache頻繁失效,引發(fā)大量回寫钾怔,回寫線程高碱呼,導(dǎo)致cpu大量時間用于上下文切換,一切宗侦,都很糟糕愚臀,所以24分鐘不細(xì)看了,無法忍受.

很多問題其實答案很簡單矾利,但是背后的思考和邏輯不簡單姑裂,要做到知其然還要知其所以然。如果想學(xué)習(xí)Java工程化男旗、高性能及分布式舶斧、深入淺出。性能調(diào)優(yōu)察皇、Spring茴厉,MyBatis,Netty源碼分析的朋友點擊我的資料加群 561614305??让网,群里有阿里大牛直播講解技術(shù)呀忧,以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費分享給大家。

位圖法

private BitSet bits;


public void perform(

? ? ? ? String largeFileName,

? ? ? ? int total,

? ? ? ? String destLargeFileName,

? ? ? ? Castor castor,

? ? ? ? int readerBufferSize,

? ? ? ? int writerBufferSize,

? ? ? ? boolean asc) throws IOException {


? ? System.out.println("BitmapSort Started.");

? ? long start = System.currentTimeMillis();

? ? bits = new BitSet(total);

? ? InputPart largeIn = PartFactory.createCharBufferedInputPart(largeFileName, readerBufferSize);

? ? OutputPart largeOut = PartFactory.createCharBufferedOutputPart(destLargeFileName, writerBufferSize);

? ? largeOut.delete();


? ? Integer data;

? ? int off = 0;

? ? try {

? ? ? ? while (true) {

? ? ? ? ? ? data = largeIn.read();

? ? ? ? ? ? if (data == null)

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? int v = data;

? ? ? ? ? ? set(v);

? ? ? ? ? ? off++;

? ? ? ? }

? ? ? ? largeIn.close();

? ? ? ? int size = bits.size();

? ? ? ? System.out.println(String.format("lines : %d ,bits : %d", off, size));


? ? ? ? if(asc) {

? ? ? ? ? ? for (int i = 0; i < size; i++) {

? ? ? ? ? ? ? ? if (get(i)) {

? ? ? ? ? ? ? ? ? ? largeOut.write(i);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }else {

? ? ? ? ? ? for (int i = size - 1; i >= 0; i--) {

? ? ? ? ? ? ? ? if (get(i)) {

? ? ? ? ? ? ? ? ? ? largeOut.write(i);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }


? ? ? ? largeOut.close();

? ? ? ? long stop = System.currentTimeMillis();

? ? ? ? long elapsed = stop - start;

? ? ? ? System.out.println(String.format("BitmapSort Completed.elapsed : %dms",elapsed));

? ? }finally {

? ? ? ? largeIn.close();

? ? ? ? largeOut.close();

? ? }

}


private void set(int i) {

? ? bits.set(i);

}


private boolean get(int v) {

? ? return bits.get(v);

}

nice!跑了190秒溃睹,3分來鐘.

以核心內(nèi)存4663M/32大小的空間跑出這么個結(jié)果而账,而且大量時間在用于I/O,不錯.

問題是因篇,如果這個時候突然內(nèi)存條壞了1泞辐、2根,或者只有極少的內(nèi)存空間怎么搞竞滓?

外部排序

該外部排序上場了.

外部排序干嘛的咐吼?

內(nèi)存極少的情況下,利用分治策略商佑,利用外存保存中間結(jié)果锯茄,再用多路歸并來排序;

map-reduce的嫡系.

1.分

內(nèi)存中維護(hù)一個極小的核心緩沖區(qū)memBuffer,將大文件bigdata按行讀入,搜集到memBuffer滿或者大文件讀完時肌幽,對memBuffer中的數(shù)據(jù)調(diào)用內(nèi)排進(jìn)行排序晚碾,排序后將有序結(jié)果寫入磁盤文件bigdata.xxx.part.sorted.

循環(huán)利用memBuffer直到大文件處理完畢,得到n個有序的磁盤文件:

2.合

現(xiàn)在有了n個有序的小文件喂急,怎么合并成1個有序的大文件格嘁?

把所有小文件讀入內(nèi)存,然后內(nèi)排廊移?

(⊙o⊙)…

no!

利用如下原理進(jìn)行歸并排序:

我們舉個簡單的例子:

文件1:3,6,9

文件2:2,4,8

文件3:1,5,7

第一回合:

文件1的最小值:3 , 排在文件1的第1行

文件2的最小值:2糕簿,排在文件2的第1行

文件3的最小值:1,排在文件3的第1行

那么狡孔,這3個文件中的最小值是:min(1,2,3) = 1

也就是說懂诗,最終大文件的當(dāng)前最小值,是文件1苗膝、2响禽、3的當(dāng)前最小值的最小值,繞么荚醒?

上面拿出了最小值1,寫入大文件.

第二回合:

文件1的最小值:3 , 排在文件1的第1行

文件2的最小值:2隆嗅,排在文件2的第1行

文件3的最小值:5界阁,排在文件3的第2行

那么,這3個文件中的最小值是:min(5,2,3) = 2

將2寫入大文件.

也就是說胖喳,最小值屬于哪個文件泡躯,那么就從哪個文件當(dāng)中取下一行數(shù)據(jù).(因為小文件內(nèi)部有序,下一行數(shù)據(jù)代表了它當(dāng)前的最小值)

最終的時間丽焊,跑了771秒较剃,13分鐘左右.

less bigdata.sorted.text

...

9999966

9999967

9999968

9999969

9999970

9999971

9999972

9999973

9999974

9999975

9999976

9999977

9999978

...

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市技健,隨后出現(xiàn)的幾起案子写穴,更是在濱河造成了極大的恐慌,老刑警劉巖雌贱,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啊送,死亡現(xiàn)場離奇詭異,居然都是意外死亡欣孤,警方通過查閱死者的電腦和手機馋没,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來降传,“玉大人篷朵,你說我怎么就攤上這事。” “怎么了声旺?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵笔链,是天一觀的道長。 經(jīng)常有香客問我艾少,道長卡乾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任缚够,我火速辦了婚禮幔妨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谍椅。我一直安慰自己误堡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布雏吭。 她就那樣靜靜地躺著锁施,像睡著了一般。 火紅的嫁衣襯著肌膚如雪杖们。 梳的紋絲不亂的頭發(fā)上悉抵,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音摘完,去河邊找鬼姥饰。 笑死,一個胖子當(dāng)著我的面吹牛孝治,可吹牛的內(nèi)容都是我干的列粪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼谈飒,長吁一口氣:“原來是場噩夢啊……” “哼岂座!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起杭措,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤费什,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瓤介,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吕喘,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年刑桑,在試婚紗的時候發(fā)現(xiàn)自己被綠了氯质。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡祠斧,死狀恐怖闻察,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤辕漂,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布呢灶,位于F島的核電站,受9級特大地震影響钉嘹,放射性物質(zhì)發(fā)生泄漏鸯乃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一跋涣、第九天 我趴在偏房一處隱蔽的房頂上張望缨睡。 院中可真熱鬧,春花似錦陈辱、人聲如沸奖年。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陋守。三九已至,卻和暖如春利赋,著一層夾襖步出監(jiān)牢的瞬間水评,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工媚送, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留之碗,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓季希,卻偏偏與公主長得像,于是被迫代替她去往敵國和親幽纷。 傳聞我的和親對象是個殘疾皇子式塌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容