广告位联系
返回顶部
分享到

Java 5亿整数大文件排序的介绍

java 来源:互联网搜集 作者:秩名 发布时间:2020-03-24 18:33:14 人浏览
摘要

问题 给你1个文件 bigdata ,大小4663M,5亿个数,文件中的数据随机,如下一行一个整数: 6196302 3557681 6121580 2039345 2095006 1746773 7934312 2016371 7123302 8790171 2966901 ... 7005375 现在要对这个文件进行排序,怎么搞? 内部排序 先尝试内排,

问题

给你1个文件bigdata,大小4663M,5亿个数,文件中的数据随机,如下一行一个整数:

6196302
3557681
6121580
2039345
2095006
1746773
7934312
2016371
7123302
8790171
2966901
...
7005375

现在要对这个文件进行排序,怎么搞?

内部排序

先尝试内排,选2种排序方式:

3路快排:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
private final int cutoff = 8;
 
public <T> void perform(Comparable<T>[] a) {
    perform(a,0,a.length - 1);
  }
 
  private <T> int median3(Comparable<T>[] 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 <T> void perform(Comparable<T>[] a,int low,int high) {
    int n = high - low + 1;
    //当序列非常小,用插入排序
    if(n <= cutoff) {
      InsertionSort insertionSort = SortFactory.createInsertionSort();
      insertionSort.perform(a,low,high);
      //当序列中小时,使用median3
    }else if(n <= 100) {
      int m = median3(a,low,low + (n >>> 1),high);
      exchange(a,m,low);
      //当序列比较大时,使用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<T> 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] 待考察区域
    */
 
    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);
  }

归并排序:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
  * 小于等于这个值的时候,交给插入排序
  */
 private final int cutoff = 8;
 
 /**
  * 对给定的元素序列进行排序
  *
  * @param a 给定元素序列
  */
 @Override
 public <T> void perform(Comparable<T>[] a) {
   Comparable<T>[] b = a.clone();
   perform(b, a, 0, a.length - 1);
 }
 
 private <T> void perform(Comparable<T>[] src,Comparable<T>[] 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 <T> void merge(Comparable<T>[] src,Comparable<T>[] 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++];
     }
   }
 }

数据太多,递归太深 ->栈溢出?加大Xss?
数据太多,数组太长 -> OOM?加大Xmx?

耐心不足,没跑出来.而且要将这么大的文件读入内存,在堆中维护这么大个数据量,还有内排中不断的拷贝,对栈和堆都是很大的压力,不具备通用性。

sort命令来跑

?
1
sort -n bigdata -o bigdata.sorted

跑了多久呢?24分钟.

为什么这么慢?

粗略的看下我们的资源:
1. 内存
jvm-heap/stack,native-heap/stack,page-cache,block-buffer
2. 外存
swap + 磁盘

数据量很大,函数调用很多,系统调用很多,内核/用户缓冲区拷贝很多,脏页回写很多,io-wait很高,io很繁忙,堆栈数据不断交换至swap,线程切换很多,每个环节的锁也很多.

总之,内存吃紧,问磁盘要空间,脏数据持久化过多导致cache频繁失效,引发大量回写,回写线程高,导致cpu大量时间用于上下文切换,一切,都很糟糕,所以24分钟不细看了,无法忍受.

位图法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
private BitSet bits;
 
 public void perform(
     String largeFileName,
     int total,
     String destLargeFileName,
     Castor<Integer> castor,
     int readerBufferSize,
     int writerBufferSize,
     boolean asc) throws IOException {
 
   System.out.println("BitmapSort Started.");
   long start = System.currentTimeMillis();
   bits = new BitSet(total);
   InputPart<Integer> largeIn = PartFactory.createCharBufferedInputPart(largeFileName, readerBufferSize);
   OutputPart<Integer> 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分来钟.
以核心内存4663M/32大小的空间跑出这么个结果,而且大量时间在用于I/O,不错.

问题是,如果这个时候突然内存条坏了1、2根,或者只有极少的内存空间怎么搞?

外部排序

该外部排序上场了.
外部排序干嘛的?

内存极少的情况下,利用分治策略,利用外存保存中间结果,再用多路归并来排序; map-reduce的嫡系.

1.分

内存中维护一个极小的核心缓冲区memBuffer,将大文件bigdata按行读入,搜集到memBuffer满或者大文件读完时,对memBuffer中的数据调用内排进行排序,排序后将有序结果写入磁盘文件bigdata.xxx.part.sorted.
循环利用memBuffer直到大文件处理完毕,得到n个有序的磁盘文件:

2.合

现在有了n个有序的小文件,怎么合并成1个有序的大文件?
把所有小文件读入内存,然后内排?
(⊙o⊙)…
no!

利用如下原理进行归并排序:

我们举个简单的例子:

文件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
也就是说,最终大文件的当前最小值,是文件1、2、3的当前最小值的最小值,绕么?
上面拿出了最小值1,写入大文件.

第二回合:
文件1的最小值:3 , 排在文件1的第1行
文件2的最小值:2,排在文件2的第1行
文件3的最小值:5,排在文件3的第2行
那么,这3个文件中的最小值是:min(5,2,3) = 2
将2写入大文件.

也就是说,最小值属于哪个文件,那么就从哪个文件当中取下一行数据.(因为小文件内部有序,下一行数据代表了它当前的最小值)

最终的时间,跑了771秒,13分钟左右.

less bigdata.sorted.text
...
9999966
9999967
9999968
9999969
9999970
9999971
9999972
9999973
9999974
9999975
9999976
9999977
9999978
...


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/gsky1986/article/details/46499529
相关文章
  • SpringBoot自定义错误处理逻辑介绍

    SpringBoot自定义错误处理逻辑介绍
    1. 自定义错误页面 将自定义错误页面放在 templates 的 error 文件夹下,SpringBoot 精确匹配错误信息,使用 4xx.html 或者 5xx.html 页面可以打印错误
  • Java实现手写一个线程池的代码

    Java实现手写一个线程池的代码
    线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和
  • Java实现断点续传功能的代码

    Java实现断点续传功能的代码
    题目实现:网络资源的断点续传功能。 二、解题思路 获取要下载的资源网址 显示网络资源的大小 上次读取到的字节位置以及未读取的字节
  • 你可知HashMap为什么是线程不安全的
    HashMap 的线程不安全 HashMap 的线程不安全主要体现在下面两个方面 在 jdk 1.7 中,当并发执行扩容操作时会造成环形链和数据丢失的情况 在
  • ArrayList的动态扩容机制的介绍

    ArrayList的动态扩容机制的介绍
    对于 ArrayList 的动态扩容机制想必大家都听说过,之前的文章中也谈到过,不过由于时间久远,早已忘却。 所以利用这篇文章做做笔记,加
  • JVM基础之字节码的增强技术介绍

    JVM基础之字节码的增强技术介绍
    字节码增强技术 在上文中,着重介绍了字节码的结构,这为我们了解字节码增强技术的实现打下了基础。字节码增强技术就是一类对现有字
  • Java中的字节码增强技术

    Java中的字节码增强技术
    1.字节码增强技术 字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。 参考地址 2.常见技术 技术分类 类
  • Redis BloomFilter布隆过滤器原理与实现

    Redis BloomFilter布隆过滤器原理与实现
    Bloom Filter 概念 布隆过滤器(英语:Bloom Filter)是1970年由一个叫布隆的小伙子提出的。它实际上是一个很长的二进制向量和一系列随机映射
  • Java C++算法题解leetcode801使序列递增的最小交换次

    Java C++算法题解leetcode801使序列递增的最小交换次
    题目要求 思路:状态机DP 实现一:状态机 Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minSwap(int[] nums1, int[] nums2) { int n
  • Mybatis结果集映射与生命周期介绍

    Mybatis结果集映射与生命周期介绍
    一、ResultMap结果集映射 1、设计思想 对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了 2、resultMap的应用场
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计