awk 非排序和 sort uniq 排序处理文件的交叉并集合
日常工作中,经常会有文件处理。要玩的转命令,记得熟,速度快。
如题,指定列去重就是一个频繁遇到的问题。脑子里一直深深烙着 sort | uniq 这种用法,直到前两天处理一个2700万+行的大文件,才意识到老朋友玩不转了。。
对千万行的大文件执行 sort | uniq,等的花儿谢了也白扯… 显然,咱手头的文件列也不是带索引的(又不是数据库),那执行大数据排序 sort 操作当然非常慢了。 更快的方法,当然要考虑怎么不用排序去重。
熟悉编程的同学,肯定立马想到用对象数组啊,key=>value这种。因为key是唯一的(文件指定的去重列做key),这样不就是去重了么~
嗯啊,下边介绍的这种机智的命令就是应用的这个想法。 大杀器: !arr[key]++
简洁的表达,反而容易迷糊住一般人。简单说下就明白了。
- arr即是定义一个数组
- key即是文件的指定列
- 运算符优先级:a++ 优先于 !
再啰嗦一句就捅破了.. 按照文件行处理的话:第一次的key,arr[key]++ 表达式返回0,加个!,表达式结果为true。此时arr[key]==1了,后续行再遇到这个相同key, !arr[key]++ 这个表达式自然就是false了。
明白这个表达式了,那命令也出来了,利用下awk。比如指定列是第一列
awk '!arr[$1]++' filename
再说下awk是’条件{do sth}'
, !arr[$1]++
是条件,命令默认打印全部{print $0}
这种去重方式,显然不需要预先排序,速度自然快的飞起哈哈。不过速度是快了,牺牲了什么呢?他疯狂的吃内存,因为它要存下来整个文件列的数组。很大的文件情况下就要考虑内存承受了。
我的开发机,在搞这2700万+的文件就吃不消了,进程过一会被杀死了。执行过程中,top一下会发现这个命令内存占比蹭蹭往上涨。
但是 sort | uniq
这种方式,利用内存上会更好,不会挂掉。
因为开发机内存不够的原因,为了展示下这两个方法的数据对比。我按每900万行拆了下文件。(实际文件是两列,指定第一列去重)
split -l 9000000 userimei.log
利用分割后的文件 xaa
对比:
#!/bin/bash time awk '{print $1}' xaa | sort | uniq > zaa.log time awk '!arr[$1]++{print $1}' xaa > zbb.log
time 是查看命令执行时间。
1、sort | uniq 方法耗时 92秒
real 1m32.192s user 1m27.186s sys 0m1.529s
2、 !arr[key]++ 方法耗时 24秒
real 0m23.902s user 0m20.660s sys 0m1.602s
查看去重后文件行数是否一致:
wc -l zaa.log wc -l zbb.log 都是 8942497 行,符合预期。
awk !arr[key]++
这种用法,手痒简单写个php处理。
<?php ini_set('memory_limit', '1024M'); $filename = 'xaa'; $file = fopen($filename, "r"); // 按照awk一行一行读取。大文件处理方式 while(!feof($file)){ $lineArr = explode("t", fgets($file));// 本文件两列 $key = $lineArr[0];// 指定第一列 if(!isset($arr[$key])){ $arr[$key] = null; } } fclose($file); file_put_contents('zcc.log', implode("n", array_keys($arr)));
是不是真正掌握了大杀器呢?
举一反三: 比如交集、并集、差集这些集合运算呢?
a.log 和 b.log 是各自去重了的A集合和B集合(集合性质之一互异性)。
交集
1. sort | uniq 方法 -d 参数只输出重复行 sort a.log b.log | uniq -d 2. arr[$1]++ 方法该怎么办呢?真正掌握了的话,就知道表达式等于1输出就ok了 awk 'a[$1]++==1' a.log b.log 仍然用了前面900万行的文件a,和从中选取两行做b;实际对比: # 方法1 real 0m51.684s # 方法2 real 0m18.216s
并集
1. sort | uniq 方法,直接就是去重 sort a.log b.log | uniq 2. arr[$1]++ 这个就是去重而已。表达式为0 或者 非!就ok了 awk '!a[$1]++' a.log b.log 仍然用了前面900万行的文件a,和从中选取两行做b;实际对比: # 方法1 real 0m52.436s # 方法2 real 0m19.080s
差集
差集需要注意顺序,是A-B or B-A
A-B 举例: # sort | uniq 方法,-u 参数只输出出现一次的记录。一个技巧,使用两次B集合完成 A-B 差集运算 1. sort a.log b.log b.log | uniq -u # arr[$1] 杀器,设计这种方法花费了我好长时间。。也写出来几个,但每写一个总感觉有更快的,最后选择了下面这个方法。即便这种写法的速度也大大优越于排序的方法。 2. awk 'NR<=FNR{a[$1]}NR>FNR{delete a[$1]}END{for(k in a) print k}' a.log b.log A a.log 是3条数据;B b.log是300万条数据。 开始测试: A-B: (小文件-大文件) # 方法1 real 0m52.710s # 方法2 real 0m1.891s 这效果杠杠滴把哈哈~ ---------------------- B-A: (大文件-小文件) # 方法1 real 0m22.575s # 方法2 real 0m7.930s ---------------------- 当小文件-大文件的差集时,第二种方法远远优于第一种排序方法。 同时也发现,数组这种方法,在处理上,时间更多是耗费在对数组赋值上(赋值操作是前一个文件)。
再简单介绍下差集中的 awk 命令:
NR 表示处理当前的行数,不管awk一个文件还是两个文件,NR始终都是 +1 递增的。
FNR 表示当前文件处理的行数,类似NR的是,当处理各自文件时是 +1 递增的;而区别与NR,FNR处理新文件时,是从 1 开始了。
这样语句就好解释了,当NR在第一个文件a.log时,始终赋值列到数组a里。当NR在第二个文件b.log时,始终删除a数组列。哈哈,其实这不就是差集的定义么?在 A 集合,但又不在 B 集合。
==============================================================================================================
Okay了~ 写篇博客真耗时啊… 写完是真开心!
当遇到大文件不需要排序的去重 或者 交差并集合运算时候,利用 !arr[$1]++
起来吧~ 会给你节省时间的哈哈
原文出处:hansongda -> http://www.hansongda.club/post/filesetuniqfield