本案例需要处理的文件是nba.csv。这份档案记录了历年NBA总冠军的详细情况。文件的字段从左到右分别是比赛年份、具体日期、冠军、得分、亚军和当年的MVP(联盟MVP是Most的缩写,即最有价值球员),每个字段都是分开的半角逗号“,”,如图3-1所示。
图3-1NBA原始文件数据
本课程的设计需要对这个数据集进行如下处理:
(1)数据清洗;
(2)统计各队获得冠军的数量;并分别存储东西部球队的统计结果。
任务1:实施过程
(一)要求
NBA的历史比较悠久。 1947年至2019年期间,有些球队已不复存在(例如:芝加哥雄鹿队),有些球队的名称发生了变化(例如:明尼阿波利斯湖人队,现名为洛杉矶湖人队);因此,对于已经不存在的团队,继续保存他们的名字而不做任何修改;但已更改名称的队伍需要映射到当前队伍的名称;
另外,由于需要对球队的东西赛区进行统计,因此需要在球队中添加东西赛区标识。
(二)解题思路
添加团队的新旧名称之间的映射。读取每行数据时,如果遇到旧名称,则用新名称替换;
添加东西部球队的映射。读取数据时,分析冠军球队所在赛区,然后添加标识(东部球队用“E”标识,西部球队用“W”标识)
需要说明的是,美国NBA联盟从1970年开始划分东西部,因此需要判断年份。
(3)核心代码分析
在自定义类中,我们首先创建了两个java.util.Map对象来封装新老球队、东西赛区球队之间的映射。核心代码如图:
图3-2 封装了新老球队、东西赛区的映射对象
映射数据的初始化最好放在类的setup()方法中。该类有四个方法:
无效设置( )
无效映射(KEYIN 键,值,)
空白 ( )
无效运行()
setup()方法一般用于加载一些初始化工作,如关联数据的初始化、数据库链接的建立等; ()方法是收尾工作,比如执行map()后关闭文件或者键值分配等; map() ) 函数描述了每行数据的处理逻辑; run()方法定义了上述方法的执行流程,如图3-3所示。通过这个方法我们可以看到 setup() 和 () 在对象的生命周期中只调用一次,而 map() 方法只要有新的键和值就会被调用。
图3-3 类中的run()方法
setup()方法中初始化映射数据的核心代码如图3-4所示。
图3-4 映射数据初始化核心代码
最终将东、西分区列表添加到对象中,如图3-5所示。
图3-5 为对象添加分区
在map方法中nba东部球队,只需按照上述要求进行逻辑映射即可。 Map的最终输出结果就是类型。文本部分被替换为新名称,并附加标识东部和西部地区的数据。核心代码如图3-6所示。
图3-6 map方法核心代码
main方法需要定义作业启动的相关参数。数据清洗部分实际上只需要输出map阶段的结果,不需要部分汇总处理。因此nba东部球队,不需要配置,但即使不写类,框架也会添加一个默认的类是 org.... 可以使用 Job 对象的方法,并将其参数设置为 0,以避免不必要的性能浪费。核心代码如图3-7所示。
图3-7 main方法核心代码
任务2:统计每支球队获得的冠军数量。统计结果存储在三个文件中:东区、西区和未分区。
任务2:解决问题的思路
统计每支球队获得的冠军数量,基本思路和之前程序的逻辑是一致的。在map阶段,解析冠军球队的名字作为key,用一个值为1的对象作为value,然后传递给part,做加法操作即可。
另外,统计结果需要按照东、西地区分档存储,相对复杂。从给定和清理的数据集可以看出nba东部球队,应该分为三个文件存储:东区、西区和未分区。因此,需要根据每行数据需要进入哪个分区的分区标识进行定制和判断。
指定数量是通过job.(3)设定的3个。
任务2:核心部分代码分析
Map部分主要用于解析冠军球队名称,解析分区标识符,并根据分区标识符设置key值,而value部分则直接取值1。核心代码如图3- 9.
图3-9 地图核心代码
定制时,需要根据key的值来确定每行数据进入哪个分区。核心代码如图3-10所示。
图3-10 核心代码
将这些部分合并来计算每支球队赢得的冠军数量。核心代码如图3-11所示。
图3-核心代码
main方法中,除了基本作业提交所需的参数外,这里还指定了。由于计算数量的逻辑一致,所以直接使用该类。另外,还需要指定自定义分区类,并将数量设置为3。核心代码如图3-12所示。
图3-12 Main方法核心代码
最终计算结果共有三个文件(除files外),文件列表如图3-13所示。
图3-13 结果文件列表
三个文件的内容如下:
(1)part-r-00000的内容如图3-14所示。
图3--r-00000内容
(2)part-r-00001的内容如图3-15所示。
图3--r-00001内容
(3)part-r-00002的内容如图3-16所示。
图3--r-00002内容
任务3:统计每支球队获得的冠军数量。统计结果定制,实现多文件存储。
接下来我们对上面的案例进行修改,将东区、西区和无分区的数据以自定义的方式保存在三个文件中。
(一)解题思路
是框架用于数据输出的抽象父类。该类是继承的并用于定义文件的输出。在该阶段,继承了默认的输出类。我们可以通过继承来实现自己的输出逻辑。
(2)核心部分代码分析
定制非常简单,只需在其方法中返回一个对象即可。核心代码如图3-17所示。
图3-17 核心代码
我们需要继承来实现我们自己的输出逻辑。本例定义了一个类,在其构造方法中创建了三个对象,用于输出不同类型的数据。在其核心的write方法中,根据分区表示指定不同分区数据的输出,并在最终的close方法中用于关闭三者。核心代码如图3-18所示。
图3-18 核心代码
需要注意的是,main方法中,由于使用了定制,所以不再需要定制。此外,还需要指定类。核心代码如图3-19所示。
图3-19 main方法核心代码
最终的输出结果与前面的例子基本相同,只不过本例的代码中直接定义了文件名。最终生成的结果文件列表如图3-20所示。
图3-20 结果文件列表
任务4:仅处理东西向分区后的数据
(一)解题思路
在前面的两个例子中,已经实现了数据的多文件存储。不过,由于本案例的要求是只将东西分区的数据存储在文件中,所以单独保存1970年之前的数据有些多余。去除这些多余的数据很简单,只需在map方法中过滤即可示例2和示例3,但更好的方法是自定义类,在数据进入map方法之前将其移除。
是数据读取框架的抽象父类。它是继承的,主要用于文件读取。框架默认使用继承,我们可以继承来实现自己的输出逻辑。
(2)核心部分代码分析
创建类继承并实现该方法。该方法返回一个自定义对象。核心代码如图3-21所示。
图3-21 核心代码
为了实现自定义,需要重写以下方法:
无效(分割,)
()
()
文本()
漂浮()
无效关闭()
其中,该方法主要用于初始化数据读取所需的一些参数,如起始位置、结束位置、读取流对象等;该方法会在数据读入map方法之前被调用(见图3-3),其作用是为键和值赋值; and方法用于获取分配的键和值,并将其传递给map方法;用于计算已读取了多少数据; close 用于关闭读取流。
定义用于读操作的变量。核心代码如图3-22所示。
图3-22 定义读写操作变量
方法中初始化上述变量,核心代码如图3-23所示。
图3-23 方法核心代码
在该方法中,进行过滤。 1970年之前的数据不会传递到map方法中。核心代码如图3-24所示。
图3-24 方法核心代码
实现及方法,核心代码如图3-25所示。
图3-25及方法核心代码
将上述代码添加到示例2或示例3中,然后在main方法中设置类。核心代码如图3-26所示。
图3-26 Main方法核心代码
运行后的最终输出中,分区之前的数据将不再存在。