程序开发中经常会碰到处理文本文件中数据的情况,这里通过一个例子来看用java实现文本文件分组汇总的方法:从文本文件employee.txt中读取员工信息,按照DEPT分组,求出每组的员工个数COUNT和薪酬SALARY总额。

文本文件empolyee.txt的格式如下:

EID   NAME       SURNAME        GENDER  STATE        BIRTHDAY        HIREDATE         DEPT         SALARY
1       Rebecca   Moore      F       California 1974-11-20       2005-03-11       R&D          7000
2       Ashley      Wilson      F       New York 1980-07-19       2008-03-16       Finance    11000
3       Rachel      Johnson   F       New Mexico     1970-12-17       2010-12-01       Sales         9000
4       Emily         Smith        F       Texas        1985-03-07       2006-08-15       HR    7000
5       Ashley      Smith        F       Texas        1975-05-13       2004-07-30       R&D          16000
6       Matthew Johnson   M     California 1984-07-07       2005-07-07       Sales         11000
7       Alexis        Smith        F       Illinois       1972-08-16       2002-08-16       Sales         9000
8       Megan     Wilson      F       California 1979-04-19       1984-04-19       Marketing        11000
9       Victoria    Davis        F       Texas        1983-12-07       2009-12-07       HR    3000
10     Ryan         Johnson   M     Pennsylvania    1976-03-12       2006-03-12       R&D          13000
11     Jacob        Moore      M     Texas        1974-12-16       2004-12-16       Sales         12000
12     Jessica     Davis        F       New York 1980-09-11       2008-09-11       Sales         7000
13     Daniel       Davis        M     Florida      1982-05-14       2010-05-14       Finance    10000
…

Java程序的编写思路是:

1、从文件逐行读入数据保存到List对象sourceList的多个Map对象emp中。

2、遍历sourceList对象,按照DEPT分组保存到Map对象group的不同List对象list中。

3、再遍历group,针对每一个DEPT,遍历其list对象,累加SALARY。

4、遍历group的同时,将DEPT,COUNT,SALARY的值存入Map对象result。不同部门的result存入List对象resultList中。

5、打印出resultList的数据。

具体代码如下:

public static void myGroup() throws Exception{
File file = newFile("D:\\esProc\\employee.txt");
FileInputStream fis = null;
fis = new FileInputStream(file);
InputStreamReader input = newInputStreamReader(fis);
BufferedReader br = newBufferedReader(input);
String line = null;
String info[] = null;
List>sourceList= new ArrayList>();
List> resultList=new ArrayList>();
if ((line = br.readLine())== null)return;//第一行舍弃
while((line = br.readLine())!= null){
info =line.split("\t");
Mapemp=new HashMap();
emp.put("EID",info[0]);
emp.put("NAME",info[1]);
emp.put("SURNAME",info[2]);
emp.put("GENDER",info[3]);
emp.put("STATE",info[4]);
emp.put("BIRTHDAY",info[5]);
emp.put("HIREDATE",info[6]);
emp.put("DEPT",info[7]);
emp.put("SALARY",info[8]);
sourceList.add(emp);
}
Map>>group = new HashMap>>();
//分组对象
for (int i = 0, len =sourceList.size(); i < len; i++) {//将明细数据分组
Map emp=(Map) sourceList.get(i);
if(group.containsKey(emp.get("DEPT"))){
group.get(emp.get("DEPT")).add(emp) ;
} else {
List> list = newArrayList>() ;
list.add(emp) ;
group.put(emp.get("DEPT"),list) ;
}
}
Set key =group.keySet();
for (Iterator it = key.iterator();it.hasNext();) {//将分组数据汇总
String dept = (String)it.next();
List>list = group.get(dept);
double salary =0;
for (int i = 0, len =list.size(); i < len; i++) {
salary +=Float.parseFloat(list.get(i).get("SALARY"));
}
Mapresult=new HashMap();
result.put("DEPT",dept);
result.put("SALARY",salary);
result.put("COUNT",list.size());
resultList.add(result);
}
for (int i = 0, len =resultList.size(); i < len; i++) {//打印结果数据
System.out.println("dept="+resultList.get(i).get("DEPT")+
"||salary="+resultList.get(i).get("SALARY")+
"||count="+resultList.get(i).get("COUNT"));
}
}

执行结果如下:

dept=Sales||salary=1362500.0||count=187
dept=Finance||salary=177500.0||count=24
dept=Administration||salary=40000.0||count=4
dept=Production||salary=663000.0||count=91
dept=Marketing||salary=733500.0||count=99

这个myGroup函数的分组字段只有一个,如果有多个的话,需要嵌套多重的集合类,代码就会更复杂了。而且myGroup函数的分组字段和汇总字段都是固定的,如果字段发生了变化,只能修改程序,无法处理灵活的动态分组汇总。如果希望myGroup像SQL语句那样适用于动态分组汇总,就需要编写动态表达式解析和求值的程序,编写难度相当大。

采用集算器esProc辅助是个更轻松的方案。集算器是专门为结构化(半结构化)数据处理设计的开发语言,实现灵活分组汇总任务很轻松,并和Java程序能无缝结合,从而使Java程序可以象SQL那样灵活访问和处理文本文件中的数据。

例如,我们需要按照DEPT分组,求出每组的员工个数COUNT和薪酬SALARY总额,esProc程序可以从外部传入一个输入参数“groupBy”作为动态的分组和汇总条件,如下图:

java stream 按照多个字段拼接分组 java实现分组_java 配置文件分组

“groupBy”的值是:DEPT:dept;count(~):count,sum(SALARY):salary。具体的esProc代码只需要三行,如下:

java stream 按照多个字段拼接分组 java实现分组_List_02

A1:定义一个file对象,导入数据,第一行是标题,字段分隔符默认是tab。esProc的集成开发环境可以直观的显示出导入的数据,如上图右边部分。

A2:按照指定字段分组汇总。这里使用宏来实现动态解析表达式,其中的groupBy就是传入参数。集算器将先计算${…}里的表达式,将计算结果作为宏字符串值替换${…}之后解释执行。这个例子中最终执行的是:=A1.groups(DEPT:dept;count(~):count,sum(SALARY):salary)。

A3:向外部程序返回符合条件的结果集。

分组字段发生变化时不用改变程序,只需改变groupBy参数即可。例如,分组变为:按照DEPT和GENDER两个字段分组,求出每组的员工个数COUNT和薪酬SALARY总额。groupBy的参数值可以写为:DEPT:dept,GENDER:gender;count(~):count,sum(SALARY):salary。执行之后,A2中的结果集如下图:

java stream 按照多个字段拼接分组 java实现分组_java 配置文件分组_03

最后,还需要在Java程序中调用这段esProc程序获得分组汇总结果,使用esProc提供的jdbc即可完成。将上述esProc程序保存为test.dfx文件的话,Java调用的代码如下:

//建立esProc jdbc连接
Class.forName("com.esproc.jdbc.InternalDriver");
con= DriverManager.getConnection("jdbc:esproc:local://");
//调用esProc 程序(存储过程),其中test是dfx的文件名
com.esproc.jdbc.InternalCStatementst;
st =(com.esproc.jdbc.InternalCStatement)con.prepareCall("calltest(?)");
//设置参数
st.setObject(1,"DEPT:dept,GENDER:gender;count(~):count,sum(SALARY):salary");//参数就是动态的分组汇总字段
//执行esProc存储过程
st.execute();
//获取结果集
ResultSet set = st.getResultSet();

这里的集算器代码比较简单,可以在Java程序中直接调用,不必再写集算器的脚本文件(比如上边的test.dfx)。具体代码如下:

st=(com.esproc.jdbc.InternalCStatement)con.createStatement();
ResultSetset=st.executeQuery("=file(\"D:\\\\esProc\\\\employee.txt\").import@t().groups(DEPT:dept,GENDER:gender;count(~):count,sum(SALARY):salary)");

上面的Java代码直接调用了一句集算器语句:从文本文件中读取数据,按照指定字段分组和汇总之后,将结果返回给Java程序的ResultSet对象set。