MR多文件统计

该文章描述了一个使用HadoopMapReduce框架进行文件数据统计的实验,包括统计单个文件中数字的位数分布,如个位、十位、百位等,并计算百分比。此外,还介绍了如何修改程序以统计所有文件的汇总分布和找出Top10的数据分布。实验使用Java编写Mapper和Reducer类,并在Ubuntu环境下运行。
摘要由CSDN通过智能技术生成

实验要求:

本实验要做在实验5的多个文件数据上执行

  1. 读取单个文件中的key(可以是从1到100000的随机整数),统计“单个”文件中数据按位分布:

    个位数(1-9):?%

    十位数(10-99):?%

    百位数(100-999):?%

    千位数(1000-9999):?%

    万位数(10000-99999):?%

    十万位数(10000-99999):?%

  2. 修改上述程序1,输出对所有文件执行程序1汇总后,找到分布中Top10的数据分布以及所在文件信息;

  3. 修改上述程序1,不区分文件的情况下进行统计所有文件中以上的数据按位分布情况。

实验环境

  • Ubuntu16.04 - VMware Workstation

  • eclipse 3.8.1

  • Java 1.8

  • Hadoop 2.7.1

在这里插入图片描述
在这里插入图片描述

基础文件

之前生成的随机文件,以key,value的格式存储,key是一个随机整数,value记录了这是第几个文件中的第几条记录,以下是生成代码。

package cn.edu.ncu.young;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Random;

public class RandomFileGenerator {
    private static final int NUM_FILES = 100;//最大文件数
    private static final int MAX_LINES = 1000;//文件最大行数
    private static final int MAX_KEY = 1000000;
    private static final int MAX_STRING_LENGTH = 20;
    private static final String FILE_PREFIX = "file_";//文件前缀
    private static final String FILE_SUFFIX = "";//文件名后缀
    private static final String DIRECTORY = "/CloudComputing/code_5/generated_files/";//文件生成路径

    public static void main(String[] args) throws IOException {
        Random random = new Random();

        for (int i = 0; i < NUM_FILES; i++) {
            String fileName = DIRECTORY + FILE_PREFIX + i + FILE_SUFFIX;
            BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));

            for (int j = 0; j < MAX_LINES; j++) {
                int key = random.nextInt(MAX_KEY);
                String value = "the recorder #" + j + " in " + FILE_PREFIX + i;
                writer.write(key + "," + value);
                writer.newLine();
            }

            writer.close();
            System.out.println(fileName+" generate success");
        }
    }

    private static String generateRandomString(Random random, int maxLength) {
        int length = random.nextInt(maxLength) + 1;
        StringBuilder builder = new StringBuilder(length);

        for (int i = 0; i < length; i++) {
            char c = (char) (random.nextInt(26) + 'a');
            builder.append(c);
        }

        return builder.toString();
    }
}

查看部分文件内容

在这里插入图片描述

1. 统计单个文件分布

Mapper类(DigitMapper):继承自Hadoop的Mapper类,负责将输入的文本数据进行处理。在map()方法中,首先将输入的文本数据进行拆分,得到键和记录值。然后,将键转换为整数,并计算其位数(通过对数运算)。最后,将位数作为键,将固定的值1作为值进行输出。

Reducer类(DigitReducer):继承自Hadoop的Reducer类,负责对Mapper的输出进行汇总和处理。在reduce()方法中,对于每个位数键,遍历对应的值集合,并将它们进行累加得到总数。然后,将位数和总数存储在一个HashMap中。

cleanup()方法:在Reducer类的cleanup()方法中,对HashMap中的数据进行处理,计算每个位数的百分比。首先,计算所有值的总和。然后,遍历HashMap,对每个位数计算其对应值的百分比,并按照位数的不同,将百分比和对应的位数信息输出。

主函数(main):在主函数中,首先配置Hadoop作业的相关参数,包括输入路径、输出路径和作业名称。然后,设置Mapper和Reducer类,指定输出键值对的类型。最后,运行Hadoop作业并等待完成。

package mypackage;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class DigitDistribution {
    /**
     * DigitMapper类
     * @author siliyoung
     *
     */
    public static class DigitMapper extends Mapper<LongWritable, Text, IntWritable, IntWritable> {
        
        private static final IntWritable ONE = new IntWritable(1);
        private IntWritable digitCount = new IntWritable();
        
        /**
         * map函数 首先将输入的文本数据进行拆分,得到键和记录值。然后,将键转换为整数,并计算其位数(通过对数运算)。最后,将位数作为键,将固定的值1作为值进行输出。
         */
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String line = value.toString().trim();//读取每一行
            String[] parts = line.split(",");//将数据按‘,’这个分隔符划分成两部分,前面一部分是key值(随机的整数),后面一部分是value值
            
            if (parts.length == 2) {
                String keyValue = parts[0].trim();//这一步只需要关注key值
                //String record = parts[1].trim();
                
                int num = Integer.parseInt(keyValue);
                digitCount.set((int) Math.log10(num));//获取到数字的位数,0-10->0,11-99->1....
                
                context.write(digitCount, ONE);//写入(位数,1)  传给reducer
            }
        }
    }
    /**
     * DigitReducer 类
     * @author siliyoung
     *
     */
    public static class DigitReducer extends Reducer<IntWritable, IntWritable, Text, Text> {
        //创建一个辅助容器countMap来记录每一个位数对应有多少个记录
        private Map<Integer, Integer> countMap;
        
        @Override
        protected void setup(Context context) throws IOException, InterruptedException {
        	//初始化countMap
            countMap = new HashMap<>();
        }
        /**
         * reducer函数 对于每个位数键,遍历对应的值集合,并将它们进行累加得到总数。然后,将位数和总数存储在一个HashMap中
         */
        @Override
        protected void reduce(IntWritable key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        	//统计每一个key(位数)的value(出现次数1)总和
            int sum = 0;
            for (IntWritable value : values) {//遍历每一个key值的value
                sum += value.get();//前面map函数中传递的value值都是1,这一句也可以写成sum+=1,但是写value.get()更加严谨一些
            }
            
            countMap.put(key.get(), sum);//将统计得到的数据存入countMap中
        }
        /**
         * cleanup 函数 对HashMap中的数据进行处理,计算每个位数的百分比
         */
        @Override
        protected void cleanup(Context context) throws IOException, InterruptedException {
        	//计算所有值的总和,即一共有多少条记录
            int total = 0;
            for (int count : countMap.values()) {
                total += count;
            }
            
            //设置百分数的格式
            DecimalFormat df = new DecimalFormat("0.00%");
            //定义每一个位数对应key值,即输出结果的头部
            Text zero= new Text("个位数(1-9):");//个位
    	    Text one= new Text("十位数(10-99):");//十位
    	    Text two = new Text("百位数(100-999):");//百位
    	    Text three = new Text("千位数(1000-9999):");//千位
    	    Text four = new Text("万位数(10000-99999):");//万位
    	    Text five = new Text("十万位数(100000-999999):");//十万位
            
    	    //遍历countMap,得到每一个位数和位数对应的value
            for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
                int digit = entry.getKey();
                int count = entry.getValue();
                //计算百分比
                double percentage = (double) count / total;
                //匹配位数并写入文件
                if(digit == 0){
                	context.write(zero, new Text(df.format(percentage)));
                }else if(digit == 1){
                	context.write(one, new Text(df.format(percentage)));
                }else if(digit == 2){
                	context.write(two, new Text(df.format(percentage)));
                }else if(digit == 3){
                	context.write(three, new Text(df.format(percentage)));
                }else if(digit == 4){
                	context.write(four, new Text(df.format(percentage)));
                }else if(digit == 5){
                	context.write(five, new Text(df.format(percentage)));
                }
            }
        }
    }
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf, "Digit Distribution");
        job.setJarByClass(DigitDistribution.class);
        
        job.setMapperClass(DigitMapper.class);
        job.setReducerClass(DigitReducer.class);
        
        job.setOutputKeyClass(IntWritable.class);
        job.setOutputValueClass(IntWritable.class);
        //输入路径,处理单个文件就填一个文件名,处理多个文件就可以填一个文件夹名,结果都没有问题
        FileInputFormat.addInputPath(job, new Path("/CloudComputing/code_5/generated_files/file_0"));
        //输出路径
        FileOutputFormat.setOutputPath(job, new Path("/CloudComputing/code_8/output/output1(0)"));
        
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

输出结果查看

控制台信息

六月 19, 2023 10:31:21 上午 org.apache.hadoop.mapreduce.Job monitorAndPrintJob
信息: Job job_local472126281_0001 completed successfully
六月 19, 2023 10:31:22 上午 org.apache.hadoop.mapreduce.Job monitorAndPrintJob
信息: Counters: 30
	File System Counters
		FILE: Number of bytes read=89958
		FILE: Number of bytes written=586440
		FILE: Number of read operations=0
		FILE: Number of large read operations=0
		FILE: Number of write operations=0
	Map-Reduce Framework
		Map input records=1000
		Map output records=1000
		Map output bytes=8000
		Map output materialized bytes=10006
		Input split bytes=115
		Combine input records=0
		Combine output records=0
		Reduce input groups=4
		Reduce shuffle bytes=10006
		Reduce input records=1000
		Reduce output records=4
		Spilled Records=2000
		Shuffled Maps =1
		Failed Shuffles=0
		Merged Map outputs=1
		GC time elapsed (ms)=63
		Total committed heap usage (bytes)=335683584
	Shuffle Errors
		BAD_ID=0
		CONNECTION=0
		IO_ERROR=0
		WRONG_LENGTH=0
		WRONG_MAP=0
		WRONG_REDUCE=0
	File Input Format Counters 
		Bytes Read=34786
	File Output Format Counters 
		Bytes Written=132

输出文件
在这里插入图片描述

2. 统计top10

Mapper类(DigitMapper):继承自Hadoop的Mapper类,负责将输入的文本数据进行处理。在map()方法中,首先获取当前输入行所属的文件名。然后,将输入行进行拆分,得到键和记录值。将键转换为整数,并计算其位数。最后,将文件名作为键,位数作为值进行输出。

Reducer类(DigitReducer):继承自Hadoop的Reducer类,负责对Mapper的输出进行汇总和处理。在reduce()方法中,对于每个文件名键,遍历对应的位数值集合,并统计每个位数值的出现次数。将位数值和对应的出现次数存储在countMap中。

汇总所有文件的数据分布:在reduce()方法中,对于每个位数值和其对应的出现次数,将其存储在top10Map中。top10Map是一个TreeMap,它会自动按照出现次数进行排序,并保持大小为10,即只保留Top10的数据分布。

cleanup()方法:在Reducer类的cleanup()方法中,对top10Map进行遍历,并按照出现次数的降序输出Top10的数据分布信息。同时,计算每个数据分布的百分比,通过countMap中的数据来计算总数,然后计算每个数据分布的百分比。

主函数(main):在主函数中,首先配置Hadoop作业的相关参数,包括输入路径、输出路径和作业名称。然后,设置Mapper和Reducer类,指定输出键值对的类型。最后,运行Hadoop作业并等待完成。

package mypackage;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.lang.Integer;

import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

/**
 * 统计top10
 * @author siliyoung
 *
 */
public class Distribute2 {
    
/**
 * DigitMapper 类 
 * @author siliyoung
 *
 */
 public static class DigitMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
	 
    private final IntWritable digitCount = new IntWritable();//记录位数
    private final Text fileKey = new Text();//记录文件名

    /**
     * map 函数 将输入的文本数据进行处理
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String fileName = ((FileSplit) context.getInputSplit()).getPath().getName(); // 获取文件名
        fileKey.set(fileName);

        String line = value.toString().trim();
        String[] parts = line.split(",");//将输入行进行拆分,得到键和记录值

        if (parts.length == 2) {
            int keyValue = Integer.parseInt(parts[0].trim());
            int numDigits = (int) Math.floor(Math.log10(keyValue));//将键转换为整数,并计算其位数
            digitCount.set(numDigits);
            //将文件名作为键,位数作为值进行输出
            context.write(fileKey, digitCount);
        }
    }
}
 
/**
 * DigitReducer 类 负责对Mapper的输出进行汇总和处理
 * @author siliyoung
 *
 */
public static class DigitReducer extends Reducer<Text, IntWritable, Text, Text> {
    private final Map<Integer, Integer> countMap = new HashMap<>();
    private final TreeMap<Integer, String> top10Map = new TreeMap<>();

    /**
     * reduce 函数
     */
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        countMap.clear();
        //对于每个文件名键,遍历对应的位数值集合
        for (IntWritable value : values) {
            int digit = value.get();
            countMap.put(digit, countMap.getOrDefault(digit, 0) + 1);//并统计每个位数值的出现次数,并更新
        }

        // 汇总所有文件的数据分布
        for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
            int digit = entry.getKey();
            int count = entry.getValue();
            
            String zero= new String("个位数(1-9)");//个位
            String one= new String("十位数(10-99)");//十位
            String two = new String("百位数(100-999)");//百位
            String three = new String("千位数(1000-9999)");//千位
            String four = new String("万位数(10000-99999)");//万位
            String five = new String("十万位数(100000-999999)");//十万位
            
            String distribute = five ;
    	    
            switch(digit){
            case 0:
            	distribute = zero;
            	break;
            case 1:
            	distribute = one;
            	break;
            case 2:
            	distribute = two;
            	break;
            case 3:
            	distribute = three;
            	break;
            case 4:
            	distribute = four;
            	break;
            case 5:
            	distribute = five;
            	break;
            default:
            		break;
            }
            top10Map.put(count, distribute + " : " + key.toString());

            // 保持Top10的大小为10
            if (top10Map.size() > 10) {
                top10Map.pollFirstEntry();
            }
        }
    }

    @Override
    protected void cleanup(Context context) throws IOException, InterruptedException {
        for (Map.Entry<Integer, String> entry : top10Map.descendingMap().entrySet()) {
            int count = entry.getKey();
            String info = entry.getValue();
            DecimalFormat df = new DecimalFormat("0.00%");
            
            //total 
            int total = 0;
            for (int c : countMap.values()) {
                total += c;
            }
            double percentage = (double) count / total;


            context.write(new Text(info), new Text(df.format(percentage)));
        }
    }
}
   
 


    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf, "Digit Distribution");
        job.setJarByClass(Distribute2.class);
        
        job.setMapperClass(DigitMapper.class);
        job.setReducerClass(DigitReducer.class);
        
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        
        FileInputFormat.addInputPath(job, new Path("/CloudComputing/code_5/generated_files/"));
        FileOutputFormat.setOutputPath(job, new Path("/CloudComputing/code_8/output/output_2"));
        
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

输出结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7nPvJjeS-1687145099713)(C:\Users\qq\AppData\Roaming\Typora\typora-user-images\image-20230619104500606.png)]

3. 统计所有文件分布

仍然使用实验1的思路和代码,然后将输入文件的目录修改为所有文件目录的文件夹即可。

 //输入路径,处理单个文件就填一个文件名,处理多个文件就可以填一个文件夹名,结果都没有问题
FileInputFormat.addInputPath(job, new Path("/CloudComputing/code_5/generated_files"));

输出结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pWNFfwNk-1687145099717)(C:\Users\qq\AppData\Roaming\Typora\typora-user-images\image-20230619104613272.png)]

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值