查询计算大文件的桌面程序工具

什么是大文件?大文件是指大到计算机内存不足以一次性读入的文件。这种情况,直接使用桌面数据工具(比如 Excel)都无能为力了,常常需要编写程序来处理。而即使是写程序,也必须是分批读入进行计算处理,最后再按照不同的计算类型对分批处理结果进行恰当的汇总处理,比小文件数据的处理要很杂很多。大文件的种类也有多种,例如文本文件、Excel文件、XML文件、JSON文件、HTTP文件……等等,其中以文本 (txt 或 csv) 最为常见。

可以用于处理大文件的程序语言一般有以下几种:
1、 常规高级编程语言,比如 Java、C/C++、C#、Basic 等
2、  将文件数据导入到数据库后用SQL来处理
3、  Python
4、  esProc SPL

本文以文本文件举例,依次介绍以上几种程序方法进行大文件计算的特点。其它类型文件数据,除了读入数据的方式不同,读入后的处理思路就都与文本文件相似。

文中要用到的订单文件orders.txt中有5个列:orderkey、orderdate、state、quantity、amount,列数据间以TAB分隔,文件中第一行是列名,总共有1000万行数据。如下:

..

 

一、  高级语言(以Java为例)

用高级语言编程来计算,计算过程怎么编写,与具体的计算类型有关,不同类型的计算需要不同的计算过程。我们先来看看最简单的合计运算,例如求订单表orders.txt中订单金额amount的总和,用Java写出来是这样的:

BufferedReader br = new BufferedReader(new InputStreamReader( new FileInputStream("orders.txt") ) );
String[] fieldNames = br.readLine().split("\t");
double sumAmount = 0;
String line = null;
while(( line = br.readLine() )!= null) {
    String[] values = line.split("\t");
    float amount = Float.parseFloat(values[4] );  //假定已知第 5 列是 amount
    sumAmount += amount;
}

这段程序是一次读一行数据,访问文件时间太多,运行速度慢。想要运行快一些,需要一次读入大块数据(比如1万行),然后再用代码把数据拆成行来处理,过程会麻烦很多。

这是最简单的计算了,没有数据过滤和分组、排序等要求。其它求平均值、最大值与最小值的计算,跟这差不多。

如果要做分组汇总,代码就会麻烦得多。比如按州state分组后求各州的订单金额总和,计算思路是这样的:把各个分组保存起来,然后一行行地读各行中的state值,与保存的组比较,找到了则将本行订单金额加到组上,没找到则新加入一个组。最后直到所有行都处理完。如果按多个字段分组求多个统计值,比如按日期和州分组求订单金额总和、最大订单金额,程序的复杂度就会增加很多。

排序就更麻烦,还需要中间缓存文件。例如要按订单金额从大到小排序,因内存不足,不能读入所有数据来排序,计算思路是这样的:先读入5000行(读多少行合适要根据内存而定)数据,排序后存到一个临时文件,再读入5000行排序后存到另一个临时文件……直到所有数据处理完,最后对这些临时文件进行有序归并——读出每个临时文件的第一行,找出应该排在最前面的那一行,写入到结果文件,然后从那个临时文件中再读出一行,继续比较找出最前面的一行写入结果文件。按此方法不断进行,直到所有数据行都写入结果文件。

用高级语言完成大文件的处理确实是相当繁琐的,对于非专业的程序人员,这几乎是不可行的。

 

二、  利用数据库

数据库内置了许多计算算法,对数据的查询计算功能比较完善,性能也比较好,因此可以考虑将文件型数据导入到数据库,生成数据库表,再使用SQL来进行数据查询计算。

这个办法麻烦的地方就是要将文件数据导入数据库,在导入数据之前,先要创建数据表结构,指定每个列的数据类型,例如创建订单表的SQL如下:

CREATE TABLE orders ( orderkey INTEGER NOT NULL,
       orderdate DATE NOT NULL,
       state  CHAR(20) NOT NULL, 
       quantity  INTEGER NOT NULL, 
       amount  DECIMAL(15,2) NOT NULL,
       PRIMARY KEY(orderkey)
);

如果换个其它结构的数据文件,那么需要另写一条建表的SQL。这里特别需要指定数据类型,否则数据库无法接受这些数据,而这是许多非专业程序员很不熟悉的地方。

对于导入过程,数据库一般都提供了直接导入文本文件的工具,而其它文件则不能直接导入,需要先转换为文本文件。Excel文件还可以直接另存为文本,而对于XML文件、JSON文件、HTTP文件等则又需要编写程序将它们转化为文本文件,或者编写程序先读入文件数据,生成一条SQL语句将数据写入数据库表中,无论如何都是非常繁琐的事情。

 

数据存入到数据库表以后,查询计算确实就非常简单了,分组、排序都挺容易,示例如下:

1、  求订单金额总和

select sum(amount) from orders;

2、  按州分组求各州订单金额总和

select state, sum(amount) from orders group by state;

3、  按订单金额排序

select * from orders order by amount;

利用数据库能很方便地查询计算较大数据量,但把大文件导入数据库却很繁琐而且有一定的专业技能要求。相对于高级语言的可行性大幅提高,但仍不够好。

三、  Python

Python也没有提供直接针对大文件的处理语法,其实现思路和高级语言类似,如前面的计算订单金额总和写出来大概是:

sumAmount=0
with open("orders.txt",'r') as f:
    while True:
        line = f.readline()
        if not line:
            break
        sumAmount += float(line.split("\t")[4])
print(sumAmount)

对于分组排序这类复杂一些的运算,如果实现前面说过的思路,用Python也非常麻烦,并不比java简单多少。但Python有个pandas包,封装了不少结构化数据的处理函数。如果是可读入内存的小文件,它可以很简单地处理。可惜的是,pandas没有针对大文件提供直接分批处理的方法,还是要自己写。使用pandas的复杂度比直接硬写要小很多,但仍然要实现前面讨论过的思路。

分组运算写起来太麻烦,我们还是把上面的求和运算基于pandas写出来感受一下。

import pandas as pd
chunk_data = pd.read_csv("orders.txt",sep="\t",header=None,chunksize=100000)
sumAmount=0
for chunk in chunk_data:
    sumAmount+=chunk[4].sum()
print(sumAmount)

使用pandas后可以把文本看成一行行有结构的数据,不再需要自己拆分。

 

Python处理小文件没有大问题,但对于大文件未提供有效支持。和高级语言相比,减轻的工作量很有限,可用程度不高,还不如数据库。

另外,Python是个解释执行语言,执行速度远远低于高级语言,处理大文件时感受会更明显。

 

四、  esProc  SPL

esProc是专业的数据处理工具,与数据库一样内置了各种查询计算算法,可以直接使用文本、Excel、Xml、Json等文件数据计算,不需要导入数据的过程。

esProc提供了游标,可以分批读入数据再计算,这样就可以很方便地处理大文件了。象前面的例子,只要写一行代码就可以:

1、  求订单金额总和

=file("orders.txt").cursor@t().total(sum(amount))

如果想再加个过滤也很容易,比如只统计2009年开始的订单金额总和:

=file("orders.txt").cursor@t().select(orderdate>=date("2009-01-01")).total(sum(amount))

做分组、排序也简单:

2、  按州分组求各州订单金额总和

=file("orders.txt").cursor@t().groups(state;sum(amount))

3、  按订单金额排序

=file("orders.txt").cursor@t().sortx(amount)

 

esProc甚至还允许直接对文件使用SQL查询,如前面3例写出来如下:

$select sum(amount) from "orders.txt"

$select state, sum(amount) from "orders.txt" group by state

$select * from "orders.txt" order by amount

 

esProc还内置了并行计算,能充分利用多核CPU提高性能,这对于大文件是特别有用的。比如分组汇总计算写成:

=file("orders.txt").cursor@tm(;4).groups(state;sum(amount))

将会按 4 路并行方式计算,在普通多核笔记本上速度能提高 2-3 倍。相对来讲,高级语言能实现并行,但不仅麻烦,而且只有专业程序员才会做。Python基本上不能并行。SQL得看数据库,Oracle这种专业数据库没问题,而简易的MySQL就不行。

esProc SPL拥有了SQL的优点,又避免了数据导入的麻烦,对于桌面数据分析人员来讲,是用于处理大文件的最合适工具。


请使用浏览器的分享功能分享到微信等