转自:http://netsmell.com/post/how-sort-10-billion-data.html?ref=myread
海量数据处理/外部归并排序 - 分治.cppp
今天要给100亿个数字排序,100亿个 int 型数字放在文件里面大概有 37.2GB,非常大,内存一次装不下了。那么肯定是要拆分成小的文件一个一个来处理,最终在合并成一个排好序的大文件。
实现思路
1.把这个37GB的大文件,用哈希分成1000个小文件,每个小文件平均38MB左右(理想情况),把100亿个数字对1000取模,模出来的结果在0到999之间,每个结果对应一个文件,所以我这里取的哈希函数是 h = x % 1000,哈希函数取得”好”,能使冲突减小,结果分布均匀。
2.拆分完了之后,得到一些几十MB的小文件,那么就可以放进内存里排序了,可以用快速排序,归并排序,堆排序等等。
3.1000个小文件内部排好序之后,就要把这些内部有序的小文件,合并成一个大的文件,可以用二叉堆来做1000路合并的操作,每个小文件是一路,合并后的大文件仍然有序。
首先遍历1000个文件,每个文件里面取第一个数字,组成 (数字, 文件号) 这样的组合加入到堆里(假设是从小到大排序,用小顶堆),遍历完后堆里有1000个 (数字,文件号) 这样的元素
然后不断从堆顶拿元素出来,每拿出一个元素,把它的文件号读取出来,然后去对应的文件里,加一个元素进入堆,直到那个文件被读取完。拿出来的元素当然追加到最终结果的文件里。
按照上面的操作,直到堆被取空了,此时最终结果文件里的全部数字就是有序的了。
最后我用c++写了个实验程序,具体代码在这里可以看到。
如何拆分大文件?
一个32G的大文件,用fopen()打开不会全部加载到内存的,然后for循环遍历啊,把每个数字对1000取模,会得到0到999种结果,然后每种结果在写入到新的文件中,就拆分了
| // 对 2 亿个数字进行排序, 约 10 G 的文件, 每个数字 int 能表示 | ||
| 3 | // 算法流程 | |
| 4 | // 将 10 G 的文件散列到 300 个文件中, 每个文件大约 35 MB | |
| 5 | // 对 35 MB 的小文件内部排序, 或者分发到多台计算机中, 并行处理 MapReduce | |
| 6 | // 最后使用最小堆, 进行 300 路归并排序, 合成大文件 | |
| 7 | // 再写一个算法判断 2 亿个数字是否有序 | |
| 8 | ||
| 9 | > | |
| 10 | > | |
| 11 | > | |
| 12 | > | |
| 13 | > | |
| 14 | ||
| 15 | // 哈希文件数 | |
| 16 | a) (a % FILE_NUM) | |
| 17 | ||
| 18 | // 2 亿个数字, 手动改 | |
| 19 | // 待排文件 | |
| 20 | // 排序后文件 | |
| 21 | // 临时目录 | |
| 22 | ||
| 23 | // 随机生成 2 亿个数字 | |
| 24 | void) | |
| 25 | { | |
| 26 | NULL; | |
| 27 | int i; | |
| 28 | ||
| 29 | ||
| 30 | ||
| 31 | 0; | |
| 32 | ||
| 33 | int s, e; | |
| 34 | clock(); | |
| 35 | 0; i<num; i++) | |
| 36 | { | |
| 37 | clock(); | |
| 38 | // 计算进度 | |
| 39 | { | |
| 40 | 100.0) / num); | |
| 41 | s = e; | |
| 42 | } | |
| 43 | ||
| 44 | 31623)); | |
| 45 | } | |
| 46 | fclose(out); | |
| 47 | 1; | |
| 48 | } | |
| 49 | ||
| 50 | // 对 2 亿个数字进行哈希, 分散到子文件中 | |
| 51 | // 入口参数: path, tmpdir | |
| 52 | void) | |
| 53 | { | |
| 54 | NULL; | |
| 55 | 5]; | |
| 56 | // 哈希文件地址 | |
| 57 | int data, add; | |
| 58 | int i; | |
| 59 | ||
| 60 | ||
| 61 | ||
| 62 | 0; | |
| 63 | NULL; | |
| 64 | ||
| 65 | // 开始哈希, 核心代码要尽可能的加速 | |
| 66 | int s, e; | |
| 67 | clock(); | |
| 68 | 0; | |
| 69 | ||
| 70 | { | |
| 71 | HASH(data); | |
| 72 | NULL) | |
| 73 | { | |
| 74 | ||
| 75 | ||
| 76 | } | |
| 77 | ||
| 78 | ||
| 79 | i++; | |
| 80 | // 计算进度 | |
| 81 | 1000) | |
| 82 | { | |
| 83 | 100.0) / num); | |
| 84 | s = e; | |
| 85 | } | |
| 86 | } | |
| 87 | 0; i<FILE_NUM; i++) | |
| 88 | fclose(tmp[i]); | |
| 89 | fclose(in); | |
| 90 | ||
| 91 | 1; | |
| 92 | } | |
| 93 | ||
| 94 | // 对 300 个文件逐个排序, 采用堆排序 STL 的优先队列 | |
| 95 | void) | |
| 96 | { | |
| 97 | // 判断文件存在 | |
| 98 | // 堆排序 | |
| 99 | 512]; | |
| 100 | NULL; | |
| 101 | int i, data; | |
| 102 | ||
| 103 | // 逐个处理 300 个文件, 或者将这些文件发送到其它计算机中并行处理 | |
| 104 | 0; i<FILE_NUM; i++) | |
| 105 | { | |
| 106 | ||
| 107 | fileexist(hashfile)) | |
| 108 | { | |
| 109 | ||
| 110 | ||
| 111 | // 小文件从磁盘加入内存中 | |
| 112 | ||
| 113 | ||
| 114 | { | |
| 115 | push(data); | |
| 116 | // 优先队列默认是大顶堆, 即降序排序 | |
| 117 | // 要升序需要重载 () 运算符 | |
| 118 | } | |
| 119 | fclose(fp); | |
| 120 | ||
| 121 | // 排序后再从内存写回磁盘 | |
| 122 | // 覆盖模式写 | |
| 123 | empty()) | |
| 124 | { | |
| 125 | top()); | |
| 126 | pop(); | |
| 127 | } | |
| 128 | fclose(fp); | |
| 129 | } | |
| 130 | } | |
| 131 | } | |
| 132 | ||
| 133 | // 队列结点 | |
| 134 | { | |
| 135 | int data; | |
| 136 | // 哈希文件的编号 | |
| 137 | const | |
| 138 | data; } | |
| 139 | }node; | |
| 140 | ||
| 141 | // 将 300 个有序文件合并成一个文件, K 路归并排序 | |
| 142 | void) | |
| 143 | { | |
| 144 | char *path); | |
| 145 | // 堆排序 | |
| 146 | 5]; | |
| 147 | NULL; | |
| 148 | 512]; | |
| 149 | node tmp, p; | |
| 150 | 0; | |
| 151 | ||
| 152 | ||
| 153 | ||
| 154 | 0; | |
| 155 | NULL; | |
| 156 | // 打开全部哈希文件 | |
| 157 | { | |
| 158 | ||
| 159 | fileexist(hashfile)) | |
| 160 | { | |
| 161 | ||
| 162 | data); | |
| 163 | id = i; | |
| 164 | // 初始化队列 | |
| 165 | // 计数器 | |
| 166 | 100.0) / FILE_NUM); | |
| 167 | } | |
| 168 | } | |
| 169 | int s, e; | |
| 170 | clock(); | |
| 171 | // 开始 K 路归并 | |
| 172 | { | |
| 173 | top(); | |
| 174 | pop(); | |
| 175 | // 将堆顶的元素写回磁盘, 再从磁盘中拿一个到内存 | |
| 176 | data); | |
| 177 | data) != EOF) | |
| 178 | { | |
| 179 | id; | |
| 180 | push(p); | |
| 181 | count++; | |
| 182 | } | |
| 183 | ||
| 184 | // 计算进度 | |
| 185 | 1000) | |
| 186 | { | |
| 187 | 100.0) / num); | |
| 188 | s = e; | |
| 189 | } | |
| 190 | } | |
| 191 | 0; i<FILE_NUM; i++) | |
| 192 | fclose(file[i]); | |
| 193 | fclose(out); | |
| 194 | ||
| 195 | 1; | |
| 196 | } | |
| 197 | ||
| 198 | // 检查是否降序排序 | |
| 199 | { | |
| 200 | NULL; | |
| 201 | int max = 0x7FFFFFFF; | |
| 202 | int data; | |
| 203 | 0; | |
| 204 | ||
| 205 | ||
| 206 | ||
| 207 | 0; | |
| 208 | ||
| 209 | int s, e; | |
| 210 | clock(); | |
| 211 | ||
| 212 | { | |
| 213 | if (data <= max) max = data; | |
| 214 | else | |
| 215 | { | |
| 216 | fclose(in); | |
| 217 | 0; | |
| 218 | } | |
| 219 | count++; | |
| 220 | // 计算进度 | |
| 221 | 1000) | |
| 222 | { | |
| 223 | 100.0) / num); | |
| 224 | s = e; | |
| 225 | } | |
| 226 | } | |
| 227 | fclose(in); | |
| 228 | 1; | |
| 229 | } | |
| 230 | ||
| 231 | // 判断文件存在 | |
| 232 | char *path) | |
| 233 | { | |
| 234 | NULL; | |
| 235 | ||
| 236 | ||
| 237 | if (fp) | |
| 238 | { | |
| 239 | fclose(fp); | |
| 240 | 1; | |
| 241 | } | |
| 242 | 0; | |
| 243 | } | |
| 244 | ||
| 245 | void) | |
| 246 | { | |
| 247 | // 删除目录 | |
| 248 | // 设置隐藏 | |
| 249 | // 建立目录 | |
| 250 | ||
| 251 | // 初始化 cmd 命令, 建立工作目录 | |
| 252 | ||
| 253 | ||
| 254 | ||
| 255 | system(cmd_del); | |
| 256 | // 建立工作目录 | |
| 257 | // 隐藏目录 | |
| 258 | ||
| 259 | // 随机生成 2 亿个数字 | |
| 260 | 0; | |
| 261 | ||
| 262 | // 对 2 亿个数字进行哈希, 即 Map | |
| 263 | // 对 300 个文件逐个排序 | |
| 264 | // 最后将 300 个有序文件合并成一个文件, 即 reduce | |
| 265 | ||
| 266 | ||
| 267 | ||
| 268 | // 删除哈希文件 | |
| 269 | // 删除 2 亿数字文件 | |
| 270 | // 删除排序后的文件 | |
| 271 | ||
| 272 | 0; | |
| 273 | } |