引入
我们要处理一个大文件,对其中的数值排序,一般我们想到的方法就是用排序算法,像快速排序、归并排序、选择排序、堆排序、冒泡排序等。但是这些排序算法使用的前提是需要把数据读入到内存,现在大文件太大,内存装不下,如何处理?
这时我们就要用外排序(External sorting)。
介绍
外排序是指能够处理极大量数据的排序算法。归并(merge)排序算法中用到了分治思想,一个大问题我们可以采取分而治之,各个突破,当子问题解决了,大问题也就搞定了。外排序也采用的是一种“排序-归并”的策略。在排序阶段,先读入能放在内存中的数据量,将其排序输出到一个临时文件,依此进行,将待排序数据组织为多个有序的临时文件。然后在归并段阶将这些临时文件组合为一个大的有序文件,也即完成排序结果。过程如下图所示。
图中这里有个batch容器,这个容器我是基于性能考虑的,当batch=n时,我们定时刷新到文件中,保证内存有足够的空间。
外排序的一个例子是外归并排序(External merge sort),它读入一些能放在内存内的数据量,在内存中排序后输出为一个顺串(即是内部数据有序的临时文件),处理完所有的数据后再进行归并,这样就得到了完整文件的排序结果。维基百科(外排序)有这个简单实例,可以参考。
当然可以用其他常规排序方式(如快速排序、堆排序、归并排序等方法)在内存中完成小文件的排序。
简而言之:外排序实现是使用堆来生成若干个顺串,然后使用多路归并算法来生成最终排序好的内容。
败者树,赢者树
胜者树与败者树是完全二叉树。就像是参加比赛一样,每个选手有不同的实力,两个选手PK,实力决定胜负,晋级下一轮,经过几轮之后,就能得到冠军。
胜者树和败者树可以在log(n)的时间内找到最值,但是如果只是找最值,有点大材小用了,中间节点记录的标号就没有意义了。其意义在于,任何一个叶子节点的值改变后,利用中间节点的信息,还是能够快速的找到最值。以后细讲他们的实现。
外排序编码
// recursive method to merge the lists until we are left with a
// single merged list
private File process(ArrayList<File> list) throws IOException {
if (list.size() == 1) {
return list.get(0);
}
ArrayList<File> inter = new ArrayList<File>();
for (Iterator<File> itr = list.iterator(); itr.hasNext(); ) {
File one = itr.next();
if (itr.hasNext()) {
File two = itr.next();
inter.add(merge(one, two));
} else {
return one;
}
}
return process(inter);
}
/**
* Splits the original file into a number of sub files.
*/
private ArrayList<File> split(File file) throws IOException {
ArrayList<File> files = new ArrayList<File>();
int[] buffer = new int[BUFFER_SIZE];
FileInputStream fr = new FileInputStream(file);
boolean fileComplete = false;
while (!fileComplete) {
int index = buffer.length;
for (int i = 0; i < buffer.length && !fileComplete; i++) {
buffer[i] = readInt(fr);
if (buffer[i] == -1) {
fileComplete = true;
index = i;
}
}
if (buffer[0] > -1) {
Arrays.sort(buffer, 0, index);
File f = new File("set" + new Random().nextInt());
FileOutputStream writer = new FileOutputStream(f);
for (int j = 0; j < index; j++) {
writeInt(buffer[j], writer);
}
writer.close();
files.add(f);
}
}
fr.close();
return files;
}
/**
* Merges two sorted files into a single file.
*
* @param one
* @param two
* @return
* @throws IOException
*/
private File merge(File one, File two) throws IOException {
FileInputStream fis1 = new FileInputStream(one);
FileInputStream fis2 = new FileInputStream(two);
File output = new File("data/merged" + new Random().nextInt());
FileOutputStream os = new FileOutputStream(output);
int a = readInt(fis1);
int b = readInt(fis2);
boolean finished = false;
while (!finished) {
if (a != -1 && b != -1) {
if (a < b) {
writeInt(a, os);
a = readInt(fis1);
} else {
writeInt(b, os);
b = readInt(fis2);
}
} else {
finished = true;
}
if (a == -1 && b != -1) {
writeInt(b, os);
b = readInt(fis2);
} else if (b == -1 && a != -1) {
writeInt(a, os);
a = readInt(fis1);
}
}
os.close();
return output;
}
具体完整的代码见我的github。
https://github.com/shibing624/BlogCode/blob/master/src/main/java/xm/math/sort/extsort/ExternalSorter.java
结果
Redis实现
现在有了Redis做key-value处理,可以直接把大文件的数据加载到redis,使用其自带的排序,也可以实现排序功能,而且速度超快,下面的代码是用jedis实现了该功能。
package xm.nosql;
import redis.clients.jedis.Jedis;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.List;
/**
* @author xuming
*/
public class SortDemo {
public static void main(String[] args) {
Jedis jedis;
jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
//jedis 排序
//注意,此处的rpush和lpush是List的操作。是一个双向链表(但从表现来看的)
jedis.del("b");//先清除数据,再加入数据进行测试
jedis.rpush("b", "1");
jedis.lpush("b", "6");
jedis.lpush("b", "3");
jedis.lpush("b", "9");
try {
File f = new File("data/test_sort.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), "utf-8"));
String readline;
while ((readline = br.readLine()) != null) {
jedis.lpush("b", readline.trim());
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(jedis.sort("b")); //[1, 3, 6, 9] //输入排序后结果
List<String> set = jedis.sort("b");
set.forEach(System.out::println);
}
}
具体完整的代码见我的github。https://github.com/shibing624/BlogCode/blob/master/src/main/java/xm/nosql/SortDemo.java