日期:2014-05-20  浏览次数:20690 次

100分问 读取2G的大文件并进行比较的问题
有两个文件,A文件和B文件,每个文件在2G左右大小,文件存储的内容是用户信息格式如下
1|张三|23|南京市
2|...........以下略
以每一行一条记录的格式,每个字段用 | 进行分隔,记录数在100万至500万之间,不会超过500万条.
A和B的文件的格式都是一样,现在要比较两个文件之间不同的记录数,要求把结果输出到新的文件,以A文件为基础,如果A文件里有的记录而B文件里没有,则把此记录记到新文件中,并在最后增加标志位设为3(表示删除);如果A文件里有的记录而B文件里也有,但记录的内容不一样,则也把B文件中的此记录内容记录到新文件中,并在最后增加标志位设为2(表示修改);如果A文件里没有的记录而B文件里有,则把B文件中的此记录内容记录到新文件中,并在最后增加标志位设为1(表示新增)

(注:同样的记录在两个文件中可能位置不一样,并且记录的顺序也可能不一样.不过两个文件的差异性不会超过10%)
最后生成的新文件则是A和B两个文件的差异.
问,用什么样的思路解决此问题?方法不限,要求速度越快越好,内存占用越少越好.

------解决方案--------------------
java.nio.*包里面的MappedByteBuffer
[code=Java][/code]public class ReadBigFile {

static int length = 0x8FFFFFF; // 128 Mb

public static void main(String[] args) throws Exception {
MappedByteBuffer out = new RandomAccessFile("test.txt", "rw")
.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);
for (int i = 0; i < length; i++) {
out.put((byte) 'a');
}
System.out.println("Finished writing");
for (int i = length / 2; i < length / 2 + 6; i++)
System.out.print((char) out.get(i)); // read file
}

}
------解决方案--------------------
分三步走吧,新增和删除,还是比较好做一点:

删除:
A文件打开,一行一行的解析,B文件打开,把第一列(主健)全部映射到HashSet中,
记录最多500万,那长度也就在七八个字节,全部读进来的话占用的内存应该不到50M,
读文件A时,提取第一列,与HashSet的contains方法比较(hash算法速度很快的),
当为false时,将这一行写入记录文件,并标记为“3”,直到A文件读完。

新增的话与删除相反,把A文件的主键全部读入到HashSet中,B文件一行一行地读,并
将主键与HashSet比较,为false时,写入记录文件,并标记为“2”,直到B文件读完。

但是修改的话就讨厌了,我目前的想法是:读取刚才生成的才个记录文件,里面有标记
为“2”和“3”的记录,将这个记录文件的主键全部读入到HashSet中,关闭该文件。

打开A文件,一行一行地读,分隔所有列,用主键匹配那个HashSet,只要不在其中的,就分隔
键和值,存到HashMap中,key存键,值的话估计很长(2G,500万行,一行平均425个字节)这样
的话肯定是不能直接存进去的,可以采用SHA-1生成该行的散列值,散列字符串的话是40个字节,
这样可以以十分之一的大小存到HashMap的值当中,这样500万行,除掉一些增加删除的,内存
占用估计在200~300MB左右,读完好关掉A文件;
打开B文件,一行一行地读,跳过HashSet中有的记录,分隔键和值,计算SHA-1散列,通过键在
HashMap中查找,若比较相同的,则读取下一行,若不同的,将这一行记录到文件中,并标记为
“3”,直至B文件全中读完。

以上的方法仅仅是理论上的,由于需要采用SHA-1进行散列计算,速度可能会比较慢一些,但是
这样比较节省内存,SHA-1产生碰撞的概率是1/2^50以下。对于500万行数据不同的SHA-1散列相
同的概率是微忽其微的,况且HashMap的键是主键,值是包括主键和所有列的散列值,这样产生
碰撞的概率,可以视为0。

以上仅是个人的建议,可以参考一下。

------解决方案--------------------
大文件处理通常都是采用分割的。
其实我上面虽然提到了排序,实际上也不一定需要排序的,比如说你按10万条分一个文件,那么你把它按id为1-99999,100000-199999,200000-299999等分别分割,也算是一种排序了。比如有500万条数据,那么就被分割为50个小文件,比如A文件被分割在A文件夹里,分别为1.txt, 2.txt...50.txt,B文件被分割在B文件夹,分别为1.txt, 2.txt...50.txt,分割文件时还可以用两个线程同时对A,B分割。
分割完以后,如果你最后的输出文件对id的顺序没有特别的要求,那你就可以采用多线程分别从A和B文件夹中取文件进行比较,如果对id顺序有要求,采用单线程也没关系,因为是按id分好的文件,所以只需要A的1.txt和B的1.txt比,A的2.txt和B的2.txt比...A的50.txt和B的50.txt比就可以了。这里面需要注意的是A文件夹有的文件B不一定有,这说明B已经把A中该记录删除了,反过来B文件夹有的A也不一定有,这说明B中追加了新的记录。两个文件夹都有的,则把文件读入到map中,然后通过集合的交并差运算去比较。
这个问题说难也不难,关键是性能。哪天有空再写个sample试试看吧。

------解决方案--------------------
针对你的最终方案提3个意见:
1、做索引时不要用行号。看看我的代码,直接用记录的起止位置做索引,这样找起文件记录更快。毫无疑问,随机访问比顺序访问更快。
2、没必要用特征码!MD5,SHA-1算法都比较费时间,每个字符串都算一遍耗费不少时间,直接比较字符串虽然多读了次硬盘,但也慢不了多少。
3、这种程序用多线程纯属画蛇添足!