一、自定義數據類型
在設計的系統開發過程之中,有可能要參與分析的文件會有很多,並且文件的組成結構也可能會非常的複雜,所以來講在整個的Hadoop裏面可以針對於用戶的需求實現自定義類型。 現在假如說有以下一個信息文件,文件的組成格式(購物統計):
用戶名[0]、省份[1]、城市[2]、購買日期時間[3]、商品名稱[4]、商品分類[5]、商品子分類[6]、商品價格[7]、商品購買價格[8]
希望可以通過一種數據類型能夠描述出以下幾種關係:
1、保留有省份的花銷(商品原始價格、成交價格、折扣的價格);
2、保留有用戶的花銷(商品原始價格、成交價格、折扣的價格);
3、保留有商品分類的花銷(商品原始價格、成交價格、折扣的價格);
在整個的MapReduce之中並不會提供有任何的一種類型來描述這種花銷的結構,那麼這樣的話就需要定義一個自己的數據類型,而所有的數據類型一定要實現一個接口:org.apache.hadoop.io.Writable;
/**
* 實現了自定義的記錄數據價格的結構
* @author mldn
*/
public class RecordWritable implements Writable {
private double price ; // 商品的原始價格
private double deal ; // 商品的成交價格
private double discount ; // 商品的折扣價格
public RecordWritable() {
// RecordWritable類需要在讀取的時候執行數據反序列化操作;
}
// RecordWritable類要在數據創建的時候接收內容
public RecordWritable(double price, double deal, double discount) {
super();
this.price = price;
this.deal = deal;
this.discount = discount;
}
@Override
public void write(DataOutput output) throws IOException {
output.writeDouble(this.price);
output.writeDouble(this.deal);
output.writeDouble(this.discount);
}
@Override
public void readFields(DataInput input) throws IOException {
this.price = input.readDouble() ;
this.deal = input.readDouble() ;
this.discount = input.readDouble() ;
}
@Override
public String toString() {
return "RecordWritable [price=" + price + ", deal=" + deal + ", discount=" + discount + "]";
}
public double getPrice() {
return price;
}
public double getDeal() {
return deal;
}
public double getDiscount() {
return discount;
}
}
最終的結果一定要對數據進行MapReduce 操作統計,所以在整個的統計裏面,現在希望可以實現這樣的操作統計:
· 可以實現根據省份數據得到的統計結果;
定義Map 數據處理
/**
* 針對於每一行發送的數據進行拆分處理,將每一行的數據拆分爲key與value的形式<br>
* 輸入類型:關注的是value的內容;<br>
* 輸出類型:單詞名稱=數量(1)<br>
* @author mldn
*/
private static class RecordMapper extends Mapper<Object, Text, Text, RecordWritable> {
@Override
protected void map(Object key, Text value, Mapper<Object, Text, Text, RecordWritable>.Context context) throws IOException, InterruptedException {
// 讀取每一行的數據,Map是根據換行作爲讀取的分割符;
String str = value.toString(); // 取的每一行的內容
// 所有的單詞要求按照空格進行拆分
String result[] = str.split(","); // 按照“,”拆分
// 以後如果要根據不同的方式來進行統計,則此處可以取得不同的內容
String province = result[1] ; // 取得省份的信息內容
double price = Double.parseDouble(result[7]) ; // 取得原始價格
double deal = Double.parseDouble(result[8]) ; // 成交價格
RecordWritable rw = new RecordWritable(price, deal, price-deal) ;
context.write(new Text(province), rw); // 將數據取出
}
}
定義Reduce 處理類
private static class RecordReducer extends Reducer<Text, RecordWritable, Text, RecordWritable> {
@Override
protected void reduce(Text key, Iterable<RecordWritable> values,Reducer<Text, RecordWritable, Text, RecordWritable>.Context context)
// 計算出消費的總價格數據
double priceSum = 0.0 ;
double dealSum = 0.0 ;
double discountSum = 0.0 ;
for (RecordWritable rw : values) {
priceSum += rw.getPrice() ;
dealSum += rw.getDeal() ;
discountSum += rw.getDiscount() ;
}
RecordWritable rw = new RecordWritable(priceSum, dealSum, discountSum) ;
context.write(key, rw);
}
}
定義相關的作業進行處理
public class Record {
private static final String OUTPUT_PATH = "hdfs://192.168.122.132:9000/output-" ;
// 具體的輸入的路徑由用戶自己來輸入,而我們來定義一個屬於自己的輸出路徑,格式“output-20201010”
public static void main(String[] args) throws Exception {
if (args.length != 1) { // 現在輸入的參數個數不足,那麼則應該退出程序
System.out.println("本程序的執行需要兩個參數:HDFS輸入路徑");
System.exit(1); // 程序退出
}
Configuration conf = new Configuration() ; // 此時需要進行HDFS操作
String paths [] = new GenericOptionsParser(conf,args).getRemainingArgs() ; //將輸入的兩個路徑解析爲HDFS的路徑
// 需要定義一個描述作業的操作類
Job job = Job.getInstance(conf, "hadoop") ;
job.setJarByClass(Record.class); // 定義本次作業執行的類的名稱
job.setMapperClass(RecordMapper.class); // 定義本次作業完成所需要的Mapper程序類
job.setMapOutputKeyClass(Text.class); // 定義Map輸出數據的Key的類型
job.setMapOutputValueClass(RecordWritable.class); // 定義Map輸出數據的Value的類型
job.setReducerClass(RecordReducer.class); // 定義要使用的Reducer程序處理類
job.setOutputKeyClass(Text.class); // 最終輸出統計結果的key的類型
job.setOutputValueClass(RecordWritable.class); // 最終輸出統計結果的value的類型
// 所有的數據都在HDFS上進行操作,那麼就必須使用HDFS提供的程序進行數據的輸入配置
FileInputFormat.addInputPath(job, new Path(paths[0])); // 定義輸入的HDFS路徑
// 輸出路徑可以自己進行定義
FileOutputFormat.setOutputPath(job,
new Path(OUTPUT_PATH + new SimpleDateFormat("yyyyMMdd").format(new
Date())));// 定義統計結果的輸出路徑
System.exit(job.waitForCompletion(true) ? 0 : 1); // 程序執行完畢後要進行退出
}
}