[toc]

新的算法以及新的思路

关于上一个学生信息大数据分析操作指南 ,在数据清洗的脚本上,由于采用了本地磁盘存取文件再从文件中获取关键字对应内容的操作.其awk中调用了内置函数system,导致awk的性能降低了近100倍(相比于不使用awk内置函数进行操作的时候).对于海量数据的操作会导致时间上出现百倍的增长(全部数据计算出来初略估计需要100个小时以及以上,具体情况视电脑性能而定).从而选择弃用这种方式来进行操作.

但是为了简化hadoop的操作以及提升hadoop的操作性能,使用单文件计算的思想还是不变的.

hadoop本身采用了<key,value>的方式进行数据的处理计算和输出,所以在构造数据的时候也依然采用这样的思想进行数据构造.

最开始的思路是采用

学号    姓名    类型:数据1,数据2,...

的方式进行数据构造,但是在mapper的时候会导致其他潜在的性能问题.比如说要使用”\t”先对输入的value进行分割再用”:”进行分割判断类型,再使用”,”对数据进行分割,从而输出到reduce中,要进行三次字符串的分割操作.而且在对与图书数据的操作类型字段中的数据进行分析发现,该字段下存在”还书,保留本/保留本”的数据,如果贸然采用”,”对输入的value数据进行分割操作,要么导致数组的字段和判断不匹配,要么导致数组溢出出现空指针的操作(可能).所以不论是从数据清洗上还是从mapreduce上,算法以及数据清洗的思路有一个重新规划

如何去计算?

在数据清洗工作开始以前,我思考的事情是要如何去计算我所拥有的数据,我需要我的数据产生什么样的结果.

现在在我手里一共有3个sql文件一个tsv文件和四个excel文件,(具体的文件内容和文件结构请参考学生信息大数据分析操作指).而我需要按学号统计每个人不同年份里的数据,所以我的key至少应该应该有学号和年份两项,这样,在reduce的时候,hadoop才能对我相同key的数据进行合并.其次,由于要求统计结果数据的完整性,而且在图书馆记录的文件中发现了学生姓名的存在,而学生的姓名又不是属于我们需要计算的东西,所以key的字段里面,又需要多一个姓名.

从而我们的key完全确定下来了,为”学号 姓名 年份”.这样,所有学号,姓名,年份相同的数据就被集中的到了一起,在reducer的时候,就会形成一个以key为”学号 姓名 年份”,value为所有数据的一个list.
那么我们应该如何确定数据(value)?

由于进入reducer的数据是一定整理好的,形成list的,所以,如果将所有表的数据全部传送进去,reducer也是可以接收的,但是唯一需要确定的问题就在于,reduce应该如何对这些数据进行分类?

那么就加一个类型说明,告诉程序我这个数据是干嘛的,所以在value的时候,格式就很简单”类型 数据1 数据2 …”,通过让程序去获取类型字段明白数据到底是什么,让程序如何去计算.由于数据格式都是相同的”姓名 学号 年份 数据…”,所以我们可以将所有数据整合到一个文件里面进行计算了.最后reducer将所有的数据统计结果输出,就完成了一次性的计算工作.如下面代码所示

String data = Integer.toString(countNine) + "\t" +  // 九点门禁数据
    Integer.toString(countEleven) + "\t" +      // 十一点门禁数据
    String.format("%.2f", sumPay) + "\t" +      // 饭卡消费总额
    String.format("%.2f", payMax) + "\t" +      // 饭卡最大消费额
    String.format("%.2f", topUp) + "\t" +       // 饭卡最大充值额
    Integer.toString(cardLose) + "\t" +         // 饭卡遗失次数
    Integer.toString(countObject) + "\t" +      // 计算课程数
    String.format("%.2f", average) + "\t" +     // 平均分
    Integer.toString(countMakeUp) + "\t" +      // 补考次数
    Integer.toString(countRebuild) + "\t" +     // 重修次数
    Integer.toString(countBook) + "\t" +        // 借阅次数
    bookTypes.toString().replaceAll(" ","") + "\t" +    // 图书类型
    String.format("%.2f", bookPrice) + "\t" +   // 借阅图书中最贵多少钱
    Integer.toString(countRollOver) + "\t" +    // 续借次数
    Integer.toString(countOverdue) + "\t" +     // 逾期次数
    Integer.toString(countLose);                // 丢失次数
System.out.println(key.toString() + "\t" + data);
context.write(key, new Text(data));

如何清洗数据?

根据计算需求,进行数据清洗,但是我们始终离不开的一个问题就是需要学生的姓名,在图书馆的借阅记录中还需要图书馆的书籍信息,在(学生信息大数据分析操作指南)[http://www.myworldbooks.cn/archives/195#more-195]使用了硬盘构建文件数据系统的思路,但是因为性能问题而放弃.那么,该如何采用新的办法来解决这个问题呢?

使用awk进行两表关联

如果有充分了解到awk的内置变量和内置函数,使用awk进行两表关联就甚至三表关联都不会再是什么难事.

我们需要使用到awk中内置了NR和FNR两个变量,其中NR为awk处理文件的行号综合,FNR为awk当前处理文件的行号.通过使awk先判断NR是否等于FNR来判断awk是否在读取不同的文件,当读取文件的时候,将变量值先存入到数组当中,然后在NR!=FNR的时候,通过关联的相同字段key来读取数组的数据就获得了最先读取的文件内容.

通过两表关联,可以将图书馆数据文件中的姓名信息取出来放到再通过awk的print方法重定向到文件从而完成数据学号姓名数据的合并.同时,图书的信息和借阅信息也是同理的.

进行两表关联的时候,要求文件格式一致,而图书信息文件的分割是依靠”\t”,图书借阅信息是”,”.所以需要对所有数据格式不一致的格式进行数据预处理.

值得注意的事情是,使用awk进行数据预处理,统一格式或者删除无用信息的时候,awk会在每行数据的后面添加”\n”字符,如果很不幸,你需要使用到awk分割后的最后一个字段($n)并且打印输出,那么,”\n”会被记录,为了防止这种情况发生,你可以在数据预处理的时候使用BEGIN{ORS=”\t\n”}重新定义记录分隔符(每一行为一条记录).但是如果你获得的最后这一个字段的数据仅仅只是拿来在awk中做条件判断,awk会自动帮你处理掉”\n”,而你无需再操心这个事情./

开始数据清洗工作

由于我们的三个数据文件是sql格式的,而且很不幸的是,在数据文件中的记录中存在带”,”的数据,为了防止后续出现错误,我们选择”\t”做分割符.所以,就需要将三个sql文件全部转化成为以”\t”为分割的记录文件.

文件转化完成以后,还有四个图书信息文件是exlce格式的,我们的命令是无法直接去处理excel格式,可以通过excel自带的另存为文本文档工具将这些文件另存为txt文件,幸运的是,另存为的文件的记录本身就是以”\t”做分割.

另外还需要注意在windows环境下,所有的文本文档都会带一个bom头,如果你不知道这个bom头有什么意义,那么你只需要记住这个bom头也是类似于\n\t的字符,你是无法看到的,但是在文件操作中,这个bom头可能导致程序判断出错甚至发生错误.一般一个文件只有一个bom头且位于文件的开头,所有如果正好开头的记录的第一个字段是你要使用的字段,那么请规避bom头以便发生发生不必要的错误.

所有文件预处理完成以后,那么再来看看我们还需要什么数据.

一个是学生的姓名,姓名位于图书馆的借阅记录中,但是借阅数据有6000w行数据,里面含了大量的重复数据,所以我们需要将借阅数据中的姓名和学号单独取出来然后去重,以保证程序运行时的简洁高效.

此外我们还需要使用到根据图书馆借阅记录的索引号去查询图书的信息,所以,我们还需要使用到图书信息,幸运的是,所有图书信息并没有冗余信息,只是需要注意在”如何清洗数据”中的斜体字部分.

在获得所有信息以后,就可以开始清洗数据了.注意我们需要的格式,为了方便操作,将所有记录全部放置到一个文件中,需要在每一条记录的开始添加一个记录说明,根据程序需求.我们可以获得以下的输出结果:

类型 姓名 学号 数据1 数据2 数据3 数据n ...

根据需要的分类:图书借阅,学生成绩,门禁信息,饭卡.取这四个信息英文翻译的首字母作为类型标志,形成以下格式:

BB name id data1 data2 data3 ...
SS name id data1 data2 data3 ...
EG name id data1 data2 data3 ...
MC name id data1 data2 data3 ...

为什么不将年份单独取出来?考虑到为了使awk的执行更高效,将年份的获取放到hadoop中,这样也可以保证数据的完整度,同时防止数据冗余

在整理数据的时候,如果有表头或者存在大量的NULL数据,可以通过awk进行剔除,以提高数据的质量.

开始hadoop工作

代码编写参考”如何去计算部分”.

示例代码