假设我有下面的应用程序的CSV文件操作日志。这个csv可能包含3-4百万行。
Company, ActionsType, Action
ABC, Downloaded, Tutorial 1
ABC, Watched, Tutorial 2
PQR, Subscribed, Tutorial 1
ABC, Watched, Tutorial 2
PQR, Subscribed, Tutorial 3
XYZ, Subscribed, Tutorial 1
XYZ, Watched, Tutorial 3
PQR, Downloaded, Tutorial 1
是否有方法通过按公司名称分组并将actionType计数器显示为列来聚合这些数据,如下面使用Java所示?
Company, Downloaded, Watched, Subscribed
ABC, 1, 2, 0
PQR, 1, 0, 2
XYZ, 0, 1, 1
我曾想过使用OpenCSV将CSV文件加载到列表中,但对于包含数百万数据的csv文件是否有效?
发布于 2014-11-27 10:46:33
如果你试图聚合数据,那肯定是没有效率的。您应该查看聚合大型数据的MapReduce。
下面是w/o MapReduce的解决方案:
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.HashMap;
public class CSVMapper {
public String transformCsv (String csvFile) {
return csvMapToString(getCsvMap(csvFile));
}
private HashMap<String, Integer[]> getCsvMap (String csvFile) {
// <K,V> := <Company, [Downloaded, Watched, Subscribed]>
HashMap<String, Integer[]> csvMap = new HashMap<String, Integer[]>();
BufferedReader reader = new BufferedReader(new StringReader(csvFile));
String csvLine;
// Create map
try {
while ((csvLine = reader.readLine()) != null) {
String[] csvColumns = csvLine.split(",");
if (csvColumns.length > 0) {
try {
String company = csvColumns[0].trim();
String actionsType = csvColumns[1].trim();
Integer[] columnValues = csvMap.get(company);
if (columnValues == null) {
columnValues = new Integer[3];
columnValues[0] = columnValues[1] = columnValues[2] = 0;
}
columnValues[0] = columnValues[0] + (actionsType.equals("Downloaded") ? 1 : 0);
columnValues[1] = columnValues[1] + (actionsType.equals("Watched") ? 1 : 0);
columnValues[2] = columnValues[2] + (actionsType.equals("Subscribed") ? 1 : 0);
if (!company.equals("Company"))
csvMap.put(company, columnValues);
}
catch (Exception nfe) {
//TODO: handle NumberFormatException
}
}
}
}
catch (Exception e) {
//TODO: handle IOException
}
return csvMap;
}
private String csvMapToString (HashMap<String, Integer[]> csvMap) {
StringBuilder newCsvFile = new StringBuilder();
newCsvFile.append("Company, Downloaded, Watched, Subscribed\n");
for (String company : csvMap.keySet()) {
Integer[] columnValues = csvMap.get(company);
newCsvFile.append(company +
", " + Integer.toString(columnValues[0]) +
", " + Integer.toString(columnValues[1]) +
", " + Integer.toString(columnValues[2]) + "\n");
}
return newCsvFile.toString();
}
public static void main (String[] args) {
String csvFile = "Company, ActionsType, Action\n" +
"ABC, Downloaded, Tutorial 1\n" +
"ABC, Watched, Tutorial 2\n" +
"PQR, Subscribed, Tutorial 1\n" +
"ABC, Watched, Tutorial 2\n" +
"PQR, Subscribed, Tutorial 3\n" +
"XYZ, Subscribed, Tutorial 1\n" +
"XYZ, Watched, Tutorial 3\n" +
"PQR, Downloaded, Tutorial 1";
System.out.println( (new CSVMapper()).transformCsv(csvFile) );
}
}
发布于 2014-11-27 10:05:32
由于您正在处理CSV中的数百万个条目,我不认为使用Java
解析上述文件是这里最好的操作方式。
相反,我将使用Java
或.NET
创建另一个应用程序,该应用程序将执行以下操作:
如果您有Oracle
数据库:
a)使用sqlldr
将CSV中的所有数据加载到数据库中。它将通过调用有关sqlldr:http://www.thegeekstuff.com/2012/06/oracle-sqlldr/的sqlldr
.More信息的外部进程调用来实现这一点。
( b)加载完成后,程序将运行一个查询来提取您需要的数据,如下所示:
WITH T AS (SELECT COMPANY, ACTIONSTYPE FROM tmp_csv)
SELECT *
FROM T PIVOT (COUNT (1)
FOR ACTIONSTYPE
IN ('Downloaded', 'Watched', 'Subscribed'))
( c)使用查询的ResultSet
执行您想做的事情,将其保存到另一个csv
或使用它创建一个新的表,以便您可以从那里查询结果(通过在查询之前用create table as
语句对查询进行修改,可以很容易地实现后者)
如果您有SqlServer
数据库:
进程与Oracle
相同,但使用bcp
实用程序从初始csv
加载数据。更多信息在这里:http://msdn.microsoft.com/en-us/library/ms162802.aspx
现在,您可以在每次重新生成日志csv时运行此应用程序,并将持久数据保存在数据库中,您可以随意查询这些数据,生成报告等等,而且由于您将处理推送到数据库,所以它比纯java
解决方案更有效。
如果坚持使用java
解决方案,我建议使用并行性读取和处理来自csv的每条记录,添加结果并将输出写入一个新的csv
文件中。
发布于 2014-11-27 10:19:31
例如,如果可以将CSV条目存储到MongoDB (首先将行转换为JSon ),则可以使用map/JSon数据处理。
https://stackoverflow.com/questions/27167026
复制相似问题