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]++ 起来吧~ 会给你节省时间的哈哈

发布于:2019-10-25 23:55:02