Skip to content

hardPass/50G_15_200MBlines

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 

Repository files navigation

题目来源:http://www.iteye.com/topic/1131438

下面是摘录楼主的相关描述

我有一个文件,一共100列,每个列以 tab 分开,第二列是一个 15位 的整数(此列是乱序的) 文件行数在2亿行之内,文件很大,大约 50G 左右。现在要求我找出 满足这种条件的行:第二列的整数,在此文件中,出现过2次或2次以上

有啥好办法嘛? 我现在这么搞的:将文件尽量分成小文件(保证同样的数字分到同一个小文件中),使得此文件可以整个load到内存中。然后对内存中的数据使用set看是否曾经重复出现过 根据最后一位的值(0, 1, ..., 9),分成10个child文件。如果某个child文件还大于512M(我JVM内存比512大一点点,可以load整个文件),在根据第二位再分割此child文件,得到 grandchild文件;如果grandchild文件还是大于512M,则根据第三位分割......

缺点:太耗时,以至于不现实,要1个多小时

PS:我将 JVM 最大heap设为 1024M,然后,测试将Long加入到set中,发现,可以创建 1700W个Long对象(Integer对象也是这么多)。到production环境中,我估计heap可以设置到8G,但是,即使这样,也只有1.6亿的Long(或者Integer),所以,肯定还是不能够读入所有的 整数,然后使用set判断哪个曾经出现过2次或2次以上

各位,有好办法嘛?我只要知道哪些整数曾经出现过2次或2次以上即可(分不分文件、出现的确切次数 我都不在乎)

另外:不要建议分布式啥的,这个也用不起来,我这就是一个 standalone 的应用 国际漫游,数据量非常大。这是运营商提供的数据,我们不能控制

15位数字的组成

当时lz也说了,“这不是面试题,这是现实遇到的”。于是上网挖掘了下潜在的需求,猜测15位的手机号码是这么一个组成:

4位国家代码 + 11位手机号码。

4位国家代码

然后第一直觉就是,所谓的4位国家号码,表面看有9999种可能性,实际地球上的国家或者地区是有限的,实际可能性很少,上网一查,果真如此,大概有不到200的可能性:

country_code = []string{
	"0001", "0007", "0020", "0027", "0030", "0031", "0032", "0033", "0034", "0036", "0039", "0040", "0041", "0043", "0044", "0045", "0046", "0047", "0048", "0049",
	"0051", "0052", "0053", "0054", "0055", "0056", "0057", "0058", "0060", "0061", "0062", "0063", "0064", "0065", "0066", "0081", "0082", "0084", "0086", "0090",
	"0091", "0092", "0093", "0094", "0095", "0098", "0212", "0213", "0216", "0218", "0220", "0221", "0223", "0224", "0225", "0226", "0228", "0229", "0230", "0231",
	"0232", "0233", "0234", "0235", "0236", "0237", "0239", "0241", "0242", "0243", "0244", "0247", "0248", "0249", "0251", "0252", "0253", "0254", "0255", "0256",
	"0257", "0258", "0260", "0261", "0262", "0263", "0264", "0265", "0266", "0267", "0268", "0327", "0331", "0350", "0351", "0352", "0353", "0354", "0355", "0356",
	"0357", "0358", "0359", "0370", "0371", "0372", "0373", "0374", "0375", "0376", "0377", "0378", "0380", "0381", "0386", "0420", "0421", "0423", "0501", "0502",
	"0503", "0504", "0505", "0506", "0507", "0509", "0591", "0592", "0593", "0594", "0595", "0596", "0597", "0598", "0599", "0673", "0674", "0675", "0676", "0677",
	"0679", "0682", "0684", "0685", "0689", "0850", "0852", "0853", "0855", "0856", "0880", "0886", "0960", "0961", "0962", "0963", "0964", "0965", "0966", "0967",
	"0968", "0970", "0971", "0972", "0973", "0974", "0976", "0977", "0992", "0993", "0994", "0995", "1242", "1246", "1264", "1268", "1345", "1441", "1664", "1670",
	"1671", "1758", "1784", "1787", "1809", "1876", "1890",
}	

11位手机号码

手机号码大家都比较熟悉,前3位是网段识别代码,只有这31种可能:

net_code = []string{
	"133", "153", "180", "181", "189", // China Telecom
	"130", "131", "132", "145", "155", "156", "185", "186", // China Unicom
	"134", "135", "136", "137", "138", "139", "147", "150", "151", "152", "157", "158", "159", "182", "183", "184", "187", "188", // CMCC
}

15位数字拆分

这样,可以把15位数字拆成:

(4位国家代码 + 3位网段识别代码) + 8位数字

其中最后一段8位数字,已经满足Uint32(4个Byte)的要求。但是前7位的可能性还是太多:200 × 31, 如果拆分成差不多6000左右的文件数,太多的文件描述符,这不是不可以,这个对系统要求也比较高,尤其当我们只是把这个程序运行在自己的笔记本上。干脆这样分:

(4位国家代码 + 2位代码) + 9位数字

这样最后一段9位数字依然满足Uint32(4个Byte)的要求。同时前6位数字的可能性也不是太多:200 × 5,大概1000个左右的可能。其中第5、6两位数字的组成只有5种可能性。

生成50G的大文件

实际任务就是随机生成每行第2列的随机数字,并且加入重复的可能性。实验了下单线程(代码),和双线(代码)。实际双线程用时12分钟左右,单线程稍微慢些。双线程的逻辑,就是一个线程只管io操作写文件,另一个线程只管生成数据。(注:Go语言不是线程的概念,称为Goroutine)

concurrent
-----------------------
Done in 704770 ms!
allLines 200003584
total 53600960512 byte equals 51117 MB
repeated 66654904
torepeat 66679715


single thread
-----------------------
Done in 798330 ms!
allLines 200003584
total 53600960512 byte equals 51117 MB
repeated 66663090
torepeat 66668163

798330 / 704770 = 113.275%
704770 / 798330 = 88.28%

50G大文件的具体解析程序分成两个阶段:

代码:https://github.com/hardPass/50G_15_200MBlines/blob/master/do_50GB_200Mrow.go

第1阶段 拆分

即读大文件,根据前6位数字(称为n6),把后9位数字(称为n9)写入不同的文件里(文件名以n6区分)。

可以预计的是,在整个程序运行过程中,这个阶段所占用的时间应该最多,而且占用绝大多数的时间。

这个阶段的运行逻辑是,分成3个线程(同上, Go里面是Goroutine):

  • loopRead,即依次遍历读50G的大文件,每次读固定的字节数(readPiece = 1 << 20),即每次读一个块(这边称piece),并将这个piece放入到chan(toParse = make(chan []byte, 1<<8),参考java中的BlockQueue)中,传递给loopParse的routine。

  • loopParse,即将循环处理loopRead传递过来的每个piece,其中有拆分的逻辑,封装成方便写操作的东西,放入到chan(toDisk = make(chan *_w_objcet, 1<<8))中,传递给 loopWrite 的routine。

  • loopWrite,即写小文件操作

第2阶段 处理小文件

依次分析这些小文件,取出重复出现的,写入到结果文件中。其中用到了Bitmap。

因为可预计这个阶段所用的时间不会太多,故单线程处理了。

最终实验结果

在没有进一步优化、并且输出了很多到Console的日志的情况下,整个运行时间不到17分钟。其中第一阶段用了15分钟。 其中找到的重复数字列表在result.txt中,694MB。

loopRead done, all spend: 895922 ms
loopParse read lines: 200003584, total read: 51117MB, spends: 895950ms
part: 178713 start, all spend: 896060 ms
part: 178713 ltrip, all spend: 896230 ms
part: 178713 done , all spend: 896269 ms
part: 035118 start, all spend: 896269 ms
part: 035118 ltrip, all spend: 896910 ms
part: 035118 done , all spend: 896955 ms
...
part: 008414 start, all spend: 1016779 ms
part: 008414 ltrip, all spend: 1016835 ms
part: 008414 done , all spend: 1016842 ms
part: 004814 start, all spend: 1016843 ms
part: 004814 ltrip, all spend: 1016897 ms
part: 004814 done , all spend: 1016913 ms

-----------------------
Done in 1016914 ms!
read 200003584 lines

About

50G大文件分析_含2亿行_15位数字找重复_bitmap

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published