问题
怎样给一个磁盘文件排序? 是否一定要使用归并排序算法?
一个只由纯数字组成的文件, 例如手机号码, 且文件保证了手机号码不会重复出现, 有哪些排序思路呢?
- 输入: 数据规模n=10^7的整数列表
- 输出: 按升序排列的整数列表
- 约束:
1MB内存、充足的磁盘、尽可能短的运行时间(秒级)
思路
- 多次归并 (文件排序)磁盘排序 运行时间仍然需要几天
- 多趟归并 内存排序
如果每个号码都使用32位整数来表示的话,在1MB存储空间里就可以存储250 000个号码。
因此,可以使用遍历输入文件40趟的程序来完成排序。
在第一趟遍历中,将0至249 999之间的任何整数都读入内存,并对这(最多)250 000 个整数进行排序,然后写到输出文件中。
第二趟遍历排序250 000至499 999之间的整数,依此类推,到第40趟遍历的时候对9 750 000至9 999 999之间的整数进行排序
- 神奇排序 不借助外部文件、不需要多趟
神奇排序的实现 位图
可用一个 20 长的字符串来表示一个所有元素都小于 20 的简单的非负整数集合。
例如,可以用如下字符串来表示集合{1,2,3,5,8,13}:即把元素作为位图的索引, 用位图中的值1表示存在,0表示不存在
0 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0
排序实现伪代码如下
/* phase 1: initialize set to empty */
for i = [0,n)
bit[i] = 0
/* phase 2: insert present elements into the set */
for each i in the input file
bit[i] = 1
/* phase 3: write sorted output */
for i = [0,n)
if bit[i] == 1
write i on the output file
位图实现代码
public class Bitmap {
private final byte[] bits;
private final int size;
public Bitmap(int size) {
this.size = size;
/*
一个字节8位,所以需要size/8 个字节
如果size不能被8整除,需要额外一个字节
*/
bits = new byte[(size + 7) / 8];
}
public void set(int n) {
if (n < 0 || n >= size) {
throw new IllegalArgumentException("out of range");
}
/*
n % 8 得到字节中的第几位
1 << (n % 8) 得到一个只有第n%8位为1的字节
bits[n / 8] |= 1 << (n % 8) 将第n%8位置为1
*/
/*
* 例如n为10
* n % 8 = 2
* 1 << 2 = 0000 0100
* bits[10 / 8] = bits[1] = 0000 0000
* bits[1] |= 0000 0100 = 0000 0100
*/
/*
bits[0] 表示第0-7位; bits[1] 表示第8-15位; bits[2] 表示第16-23位; ...
bits[1] = 0000 0101 表示第8, 10位为1, 同一字节中倒序存放
*/
bits[n / 8] |= (byte) (1 << (n % 8));
}
public boolean test(int n) {
if (n < 0 || n >= size) {
throw new IllegalArgumentException("out of range");
}
return (bits[n / 8] & (1 << (n % 8))) != 0;
}
public static void main(String[] args) {
Bitmap bitmap = new Bitmap(100);
bitmap.set(10);
bitmap.set(20);
bitmap.set(30);
System.out.println(bitmap.test(10));
System.out.println(bitmap.test(20));
System.out.println(bitmap.test(30));
System.out.println(bitmap.test(40));
}
}
拓展 Redis中的位图
Redis中的位图(Bitmap)是一种特殊类型的字符串值,它可以用来处理特定类型的数据集,如一组元素(例如用户ID)的出现与否。位图本质上是一个由位组成的数组,每个位的值只能是0或1。
在Redis中,位图主要通过以下几个命令来操作:
- SETBIT key offset value:将键key中的位offset设置为value。value必须是0或1。如果key不存在,Redis会创建一个新的字符串值。如果offset超过了字符串的当前长度,字符串会被扩展,并用0填充。
- GETBIT key offset:获取键key中位offset的值。如果offset超过了字符串的当前长度,或者key不存在,返回0。
- BITCOUNT key [start end]:计算键key中设置为1的位的数量。可以通过start和end参数指定一个字节范围。
- BITOP operation destkey key [key ...]:对一个或多个位图进行位运算,并将结果存储在destkey中。
这些命令可以用来实现各种有趣的功能,如统计在线用户数量、实现简单的搜索引擎等。
注意:Redis中的位图是以字节为单位存储的,所以如果你有一个非常大的位图,但只设置了其中的一小部分位,Redis仍然会为整个位图分配内存。因此,位图最适合用于稠密的数据集。
小结
本文由文件排序问题引出位图,虽是两种不同的数据处理逻辑, 却能处理相同的问题, 关键是在明确问题、找出正确的问题、找出数据的规律,并找到合理的解决方案