在进行文本处理时,经常会遇到对大文件进行字符串匹配情况。用命令行的grep\cat命令处理此类问题时,写法很简单,但效率太低,用高级语言处理此类问题虽然可以获得较高的运行效率,但代码编写复杂度却相当高。
集算器支持大文件字符串匹配和多线程并行计算,代码简洁性能优异,下面通过例子来看一下具体作法。
文件file1.txt存储着大量的字符串,现在需要找出以”.txt”结尾的行数据,并输出到result.txt中。部分源数据如下:
集算器代码:
A1:以游标的形式打开文件。函数cursor并不会将数据全部读入内存,而是以游标(流)的方式打开文件,因此不会占据内存空间。函数cursor使用了默认参数,即:以tab为列分割符读入全部的字段,自动命名为_1、_2、_3…_n。对于本案例来说,只有一个字段_1。
A2=A1.select(like@c(_1,"*.txt这句代码用来查询出游标A1里以“.txt”结尾的行数据。函数select执行查询,函数like进行字符串匹配,其中_1表示第一个字段。函数like还使用了选项@c,这表示匹配时不区分大小写。
值得注意的是,A2的运算结果是游标,仍然不会占据内存空间。只有遇到export/fetch/groups等函数时,集算器引擎才会分配合适的内存缓冲区,并将前面的游标计算自动转化为内存计算。。
A3=file("e:\\result.txt").export(A2),这句代码将最终计算结果写入文件。部分数据如下:
上面的匹配规则比较简单,如果遇到复杂的情况,那就需要使用正则表达式。比如:找出以“c:\windows”开头,且结尾名不是“.txt”的行数据。
函数regex可以进行正则表达式匹配,只需将A2改为:
A1.regex@c("^c:\\\\windows.*(?<!\\\\(.txt)$)")
上面代码中,选项@c表示不区分大小写。
正则表达式虽然可以实现更复杂的匹配规则,但性能偏低,比如从2.13G的文件中找出以“.txt”结尾的行数据,在同样环境下测试,用正则表达式需要206秒,用普通表达式(select)只需要119秒。
事实上,普通表达式也可以实现很多逻辑较复杂的匹配规则,而且语法更加直观,学习成本更低。比如emp.txt存储着大量的用户信息,每条用户信息分为多个字段,字段之间用tab分割,第一行是字段名。现在要找出符合下列条件的数据:EId字段在100以内,Name字段的首字母是a,Birthday字段大于1984-01-01。集算器代码如下:
函数cursor的选项@t表示将第一行读为列名,之后就可以使用列名来访问数据。
查询条件是三个,可以分别用EId>100、like@c(Name,"a*")、Birthday>=date("1984-01-01")来表示,条件之间是“与”的逻辑关系,可以用&&来表示。
前面的算法是串行,改成并行可以进一步提高性能,具体做法是用多个线程并行读取文件,每个线程都用游标访问文件的一部分,并同时进行集合计算,最后再将每个游标的结果合并。
在相同的硬件环境下对2.13G的大文进行测试,串行时平均耗时119秒,并行时平均耗时 56秒,性能提高一倍左右。例子中的算法复杂度较低,瓶颈会产生在硬盘读取上,如果进一步加大运算的复杂度,性能提升的幅度将会更大。
集算器并行计算的代码如下:
A1=4,A1是分段数量,即将文件分成4段。分段数量,也就是实际执行时的并行数,一般不要超过CPU的核数,否则会形成排队等待,并不能提高性能。实际使用最大并行数量可以在选项中配置。
A2=A1.(file("e:\\file1.txt").cursor@z(;, ~:A1))
上面的代码按照分段数量生成4个游标。其中A1.(express)表示按照括号内的表达式依次计算A1的成员,括号内可用“~”来表示当前成员。A1一般是集合,比如["file1", " file2"]或[2,3],A1如果是从1开始的连续数字,比如[1,2,3,4],则可以简写成4.( express),案例中的代码就是这种情况。
括号内的表达式是file("e:\\file1.txt").cursor@z(;, ~:A1),其中函数cursor使用了选项@z,这表示将文件分段,用游标取其中的某一段。~:A1表示文件会被大致分为4段(A1=4),当前取第~段。“~”是A1的当前成员,因此每个游标依次对应第1、第2、第3、第4段文件。
另外,之所以是“大致分”,是因为精确分会出现半行数据的情况,而集算器会去头补位,自动取出整行数据。
A3=A2.(~.select(like@c(_1,"*.txt"))),这句代码针对A2中的每个游标(即~)进行计算,求出游标中符合条件的行数据。这里的计算结果仍然是四个游标。
A4=A3.conj@xm(),这句代码将A3中的多个游标进行并行合并。
A5=file("e:\\result.txt”).export(A4),将最终计算结果输出到文件中。
集算器脚本不仅能在IDE中独立运行,也可以通过JDBC接口被JAVA程序调用,用法和普通数据库没有区别。单步的计算脚本还可以直接嵌入JAVA代码中,而无需脚本文件。比如前面的A1-A5是分步计算,其实可以合为一步:file("e:\\result.txt").export(4.(file("e:\\file1.txt").cursor@z(;, ~:4)).(~.select(like@c(_1, "*.txt"))).conj@xm())
在操作系统命令行也可以直接运行这种单步脚本,具体内容请参考相关文档。