HDFS-MapReduce

HDFS和MapReduce的一些案例

HDFS、HdfsTest、DfsApp 类

HDFS、HdfsTest 和 DfsApp 都是与 Hadoop 分布式文件系统(HDFS)交互的应用程序

HDFS 类

HDFS 类封装了对 Hadoop 文件系统的操作,提供了一系列的方法来执行文件和目录的验证、创建、删除、上传和下载等操作。

PATH_TYPE 枚举:定义了三种类型的路径,分别是 NOT_EXIST(资源不存在)、FILE(资源为文件)和 DIRECTORY(资源为目录)。

config 和 fileSystem:分别用于存储 Hadoop 配置信息和与 HDFS 进行交互的 FileSystem 实例。

构造函数 HDFS(String url, Map<String,String> configItems):初始化 HDFS 类的实例,设置 Hadoop 配置并连接到 HDFS。

exists(String path):检查给定路径的文件或目录是否存在于 HDFS 中。

type(String path):判断给定路径的资源是文件还是目录。

deleteIfExist(String path):如果给定路径的资源存在,就删除它。

mkdirIfNotExist(String path):如果给定路径的目录不存在,就创建它。

uploadFile(String localPath, String dfsPath, boolean recursive, int…maxDepth):将本地文件或目录上传到 HDFS,支持递归上传。

downloadFile(String dfsPath, String localDir):将 HDFS 中的文件下载到本地目录。
close():关闭与 HDFS 的连接。

package com.ybg.dfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.Objects;

/**
 * 封装对于 hadoop 文件系统操作
 * Configuration: 为job(driver) 做准备
 * FileSystem: 类内共享
 * 方法设计
 *      目录:验证,创建(递归),删除(递归)
 *      文件:上传,下载
 */
public class HDFS {

    public enum PATH_TYPE{
        NOT_EXIST,FILE,DIRECTORY
    }

    private Configuration config;
    private FileSystem fileSystem;


    public HDFS(String url, Map<String,String> configItems) throws IOException, InterruptedException {
        config = new Configuration();
        if (Objects.nonNull(configItems) && !configItems.isEmpty()) {
            configItems.entrySet().forEach(
                    e->config.set(e.getKey(),e.getValue())
            );
        }
        fileSystem = FileSystem.get(URI.create(url),config,"root");
    }

    public boolean exists(String path) throws IOException {
        return fileSystem.exists(new Path(path));
    }

    /**
     * 获取 hadoop 配置对象
     * @return
     */
    public Configuration getConfig(){
        return config;
    }

    /**
     * 判断参数资源的类型
     * @param path
     * @return inner enum PATH_TYPE
     * NOT_EXIST : 资源不存在
     * FILE : 资源为文件
     * DIRECTORY : 资源为目录
     * @throws IOException
     */
    public PATH_TYPE type(String path) throws IOException {
        if (!exists(path)) {
            return PATH_TYPE.NOT_EXIST;
        }
        Path res = new Path(path);
        if (fileSystem.getFileStatus(res).isFile()) {
            return PATH_TYPE.FILE;
        }
        return PATH_TYPE.DIRECTORY;
    }

    /**
     * 针对文件或目录,存在就删除
     * @param path
     * @throws IOException
     */
    public void deleteIfExist(String path) throws IOException {
        Path res = new Path(path);
        switch(type(path)){
            case FILE:
                fileSystem.delete(res,false);
                break;
            case DIRECTORY:
                fileSystem.delete(res,true);
                break;
        }
    }

    /**
     * 针对目录,不存在则递归创建
     * @param path
     * @throws IOException
     */
    public void mkdirIfNotExist(String path) throws IOException {
        if (!exists(path)) {
            fileSystem.mkdirs(new Path(path));
        }
    }

    /**
     * 递归上传
     * @param currDir 当前递归遍历的本地目录
     * @param destDir 上传到的目标目录路径
     * @param currDepth 当前递归深度
     * @param maxDepth 最大递归深度,用于控制递归的深度
     * @throws IOException
     */
    private void uploadRecursive(File currDir, Path destDir, int currDepth, int maxDepth) throws IOException {
        //递归结束的条件
        if( maxDepth>0 && currDepth>maxDepth){
            return;
        }
        // 使用listFile()获取当前目录下的文件和子目录
        for (File res : currDir.listFiles()) {
            if (res.isDirectory()) {
                uploadRecursive(res,destDir,currDepth+1,maxDepth);
            }else {
                fileSystem.copyFromLocalFile(new Path(res.getPath()),destDir);
            }
        }
    }

    /**
     * 将本地资源(文件or目录)上传到 hadoop 文件系统
     * @param localPath 本地要上传的目标路径
     * @param dfsPath 文件上传到分布式系统的目标路径
     * @param recursive 判断是否可递归
     * @param maxDepth :
     * 设计为可变参数
     * 目录递归上传,最大深度才有用(只有 localPath=directory && recursive=true 时, maxDepth 才有用)
     * maxDepth[0]==0 表示全量递归
     * @throws IOException
     */
    public void uploadFile(String localPath, String dfsPath, boolean recursive, int...maxDepth) throws IOException {
        File res = new File(localPath);
        if (!res.exists()) {
            throw new IOException("you can't upload the localPath that is not exist");
        }
        if (type(dfsPath)!= PATH_TYPE.DIRECTORY) {
            throw new IOException("you can't upload any resource to a file");
        }
        Path local = new Path(localPath);   // local  <-->本地
        Path remote = new Path(dfsPath);    // remote <-->远程
        if (res.isFile()) {
            fileSystem.copyFromLocalFile(local,remote);
        }else if (recursive){
            int max = maxDepth[0];
            max = max<0 ? 0 : max;
            uploadRecursive(res,remote,1,max);
        }else{
            throw new IOException("you can't upload a directory when parameter recursive was set false");
        }
    }

    /**
     * 将 hadoop 文件系统中的资源下载到本地
     * @param dfsPath 要下载的文件在分布式系统中的路径
     * @param localDir 下载到本地的目标目录路径
     * @throws IOException
     */
    public void downloadFile(String dfsPath, String localDir) throws IOException {
        if (exists(dfsPath)) {
            File local;
            if ((local=new File(localDir)).exists() && local.isDirectory()) {
                Path remote = new Path(dfsPath);
                Path localPath = new Path(localDir);
                fileSystem.copyToLocalFile(remote, localPath);
            }else{
                throw new IOException("you can't download a resource to a local file");
            }
        }else{
            throw new IOException("you can't download a remote resource that is not exist");
        }
    }

    /**
     * 资源释放
     */
    public void close(){
        if (Objects.nonNull(fileSystem)) {
            try {
                fileSystem.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
HdfsTest 类

HdfsTest 类是 HDFS 类的一个测试用例,它演示了如何使用 HDFS 类的方法来执行一系列操作。

创建 Hadoop 配置信息,包括设置副本数和块大小。

创建 HDFS 实例并连接到 HDFS。

检查 /hadoop 目录是否存在,如果不存在则创建 /hadoop/data/del 目录。

上传本地目录 D:\courses\2_phrase_snd\1_hadoop\HomeworkData 到 HDFS 的 /hadoop/data/del 目录,递归深度为 2。

从 HDFS 下载 /hadoop/data/del 目录的内容到本地目录 C:\Users\HUAWEI\我的电脑\temp_dn。

删除 /hadoop/data/del 目录。

异常处理和资源释放。

package com.ybg.dfs;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class HdfsTest {
    public static void main(String[] args) {
        Map<String,String> config = new HashMap<>(4);
        config.put("dfs.replication","1");
        config.put("dfs.blocksize","128");
        final String URI = "hdfs://single01:9000";
        HDFS hdfs = null;
        try {
            hdfs = new HDFS(URI, config);
            System.out.println(hdfs.exists("/hadoop"));

            hdfs.mkdirIfNotExist("/hadoop/data/del");

            String remote_upload = "/hadoop/data/del";
            String local_upload = "D:\\courses\\2_phrase_snd\\1_hadoop\\HomeworkData";
            hdfs.uploadFile(local_upload,remote_upload,true,2);

            String local_download = "C:\\Users\\HUAWEI\\我的电脑\\temp_dn";
            String remote_download = "/hadoop/data/del";
            hdfs.downloadFile(remote_download,local_download);

            hdfs.deleteIfExist("/hadoop/data/del");

        }catch(IOException e){
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (Objects.nonNull(hdfs)) {
                hdfs.close();
            }
        }

    }
}
DfsApp 类

DfsApp 类是另一个与 HDFS 交互的应用程序,它演示了如何使用 Hadoop Java API 来列出 HDFS 中的文件和目录。

创建 Hadoop 配置信息,并连接到 HDFS。

检查 /hadoop 目录是否存在,如果存在则列出该目录下的所有文件和子目录,否则打印 “not exist”。

关闭与 HDFS 的连接。

package com.ybg.dfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.LocatedFileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.RemoteIterator;

import java.io.IOException;
import java.net.URI;

public class DfsApp {
    public static void main(String[] args) throws IOException, InterruptedException {
        final String URL = "hdfs://single01:9000";
        Configuration config = new Configuration();
        config.set("dfs.replication","1");
        config.set("dfs.blocksize","128m");
        config.set("hadoop","E:\\hadoop-3.1.3");
        FileSystem fs = FileSystem.get(URI.create(URL),config,"root");

        if(fs.exists(new Path("/hadoop"))){
            System.out.println(fs.mkdirs(new Path("/hadoop/test2/test")));
            final RemoteIterator<LocatedFileStatus> it = fs.listFiles(new Path("/hadoop"),true);
            while(it.hasNext()){
                System.out.println(it.next().getPath().getName());
            }
        }else{
            System.out.println("not exist");
        }
        fs.close();
    }
}

WordCountTest、StoryWordCount 类

WordCountTest 和 StoryWordCount 展示了如何实现一个简单的单词计数器,其中 WordCountTest 是一个单行文本的单词计数器,而 StoryWordCount 是一个使用 Hadoop MapReduce 框架的分布式单词计数器。

WordCountTest 类

WordCountTest 类包含一个 main 方法,用于测试单词计数功能。它使用 StringTokenizer 类来对字符串进行分词,并计算单词的数量。

定义一个包含多个单词的字符串 line。

使用 StringTokenizer 对字符串进行分词。

打印分词后的总数。

遍历分词结果,并打印每个单词。

package com.ybg.dfs;

import java.util.StringTokenizer;

public class WordCountTest {
    public static void main(String[] args) {
        String line = "I love you baby, are you ok now will you be my girl";
        StringTokenizer it = new StringTokenizer(line);
        System.out.println(it.countTokens());
        while(it.hasMoreElements()){
            System.out.println(it.nextToken());
        }
    }
}
补充:StringTokenizer 的用法

StringTokenizer 是 Java 中的一个类,用于将字符串分解为一系列的标记(Token)。

它允许指定用作分隔符的字符,从而可以灵活地根据需要对字符串进行分词。

StringTokenizer 类位于 java.util 包中。

使用方法

StringTokenizer 的构造函数有两个重载版本:

(1)一个接受字符串和分隔符模式,

(2)另一个只接受字符串作为参数。

(1)分隔符模式可以是单个字符,也可以是两个字符的序列,用来定义分隔符。例如,", " 表示逗号和空格都被视为分隔符。

(2)如果没有提供分隔符模式,StringTokenizer 默认使用空白字符(空格、制表符、换行符等)作为分隔符。

StoryWordCount 类

StoryWordCount 类是一个使用 Hadoop MapReduce 框架的分布式单词计数应用程序。它包含两个内部类 StoryMapper 和 StoryReducer,以及一个 main 方法来配置和运行 MapReduce 作业。

package com.ybg.dfs;

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;

import java.io.IOException;

import java.util.*;

/**
 * job ( driver )
 */
public class StoryWordCount {
    /**
     * Mapper : map(KI, VI){ send(KO,VO) }
     * 选择实现 Mapper 类的目的是为了【适配】
     * KI : LongWritable 文章中某行的行号
     * VI : Text 文章中某行的文本内容
     * KO : Text 文章中某行某个单词
     * VO : IntWritable 文章中某行某个英文单词出现的次数
     */
    static class StoryMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
        private Text word = new Text();
        private IntWritable count = new IntWritable();

        /**
         * @param key 输入的键,通常表示输入的数据的偏移位置
         * @param value 输入的值,表示一行文本数据
         * @param context 上下文对象,提供了与hadoop框架的交互能力,包括写出键值对和设置作业参数
         * @throws IOException
         * @throws InterruptedException
         */
        @Override // 对父类(Mapper类)的重写
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String line = value.toString()
                    .trim() //移除字符串两端的空白字符
                    .replaceAll("\\s+", " ") //替换多个连续的空白字符(包括空格、制表符等)为单个空格
                    .replaceAll("[^a-zA-Z -]", ""); //移除非字母、空格和破折号的字符
            // 使用 StringTokenizer 对处理后的文本进行分词
            StringTokenizer it = new StringTokenizer(line);
            if (it.countTokens()>0) {
                Map<String,Integer> wordCount = new HashMap<>(it.countTokens());
                while (it.hasMoreElements()) {
                    String word = it.nextToken();
                    if (!wordCount.containsKey(word)) {
                        wordCount.put(word,0);
                    }
                    wordCount.put(word,wordCount.get(word)+1);
                }
                // 输出键值对
                wordCount.entrySet().forEach(e->{
                    word.set(e.getKey());
                    count.set(e.getValue());
                    try {
                        context.write(word,count);// 使用 context.write 方法写出键值对到MapReduce框架,以便后续的处理
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                });
            }
        }
    }


    /**
     * Reducer : map(KI, VI){ send(KO,VO) }
     * 选择实现 Mapper 类的目的是为了【适配】
     * KI : Text 文章中某个单词
     * VI : Iterable<IntWritable> values 文章中某单词在【N个段落】中出现的次数
     * KO : Text 文章中某行某个单词
     * VO : IntWritable 文章中某个单词出现的总次数
     */
    static class StoryReducer extends Reducer<Text,IntWritable,Text,IntWritable>{
        private IntWritable count = new IntWritable();

        @Override
        protected void reduce(Text word, Iterable<IntWritable> values, Context context)
                throws IOException, InterruptedException {
            // 计算单词计数
            int num = 0;
            Iterator<IntWritable> it = values.iterator();
            while (it.hasNext()) {
                num += it.next().get();
            }
            // 设置输出键值对
            count.set(num);
            context.write(word,count);
        }
    }

    public static void main(String[] args) {
        String url = "hdfs://single01:9000";
        /**
         * 设置 HDFS 配置项
         * 这些配置项可以影响到在 HDFS 上存储文件时的数据分布和冗余策略。
         * 通过设置 dfs.replication,你可以调整数据的冗余级别,
         * 而设置 dfs.blocksize 可以影响到数据块的大小,从而影响到文件的切片大小。
         * 这些配置项的选择通常会根据具体的使用场景和需求来进行调整。在这里,items 存储了两个常见的配置项示例。
         */
        Map<String,String> items = new HashMap<>(2); //items 是一个用于存储HDFS配置项的HashMap
        items.put("dfs.replication","1");
        // dfs.replication 是 HDFS 中数据块的复制因子,指定了数据在集群中的复制次数
        // 在这里,设置为 "1" 表示不进行额外的数据复制,即只有一个副本
        items.put("dfs.blocksize","128M");
        // dfs.blocksize 是 HDFS 数据块的大小,这里设置为 "128M" 表示每个数据块的大小为128兆字节

        String src = "/hadoop/data/《小王子》英文版.txt";
        String dst = "/hadoop/wordcount";
        HDFS hdfs = null;
        try {
             hdfs = new HDFS(url,items);
             // 检查文件是否存在并删除目标目录
            if(!hdfs.exists(src)){
                return;
            }
            hdfs.deleteIfExist(dst);

            /**
             * 配置MapReduce任务
             * 创建一个新的MapReduce作业(Job对象)并命名为"story_word_count"。
             * 设置作业使用的Jar包(setJarByClass),这里指定了包含StoryWordCount类的Jar。
             * 设置Reduce任务的数量为1。
             * 配置Mapper和Reducer类,以及它们的输出键值对的类型。
             */
            Job job = Job.getInstance(hdfs.getConfig(),"story_word_count");
            job.setJarByClass(StoryWordCount.class);
            job.setNumReduceTasks(1);

            job.setMapperClass(StoryMapper.class);
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(IntWritable.class);

            job.setReducerClass(StoryReducer.class);
            job.setMapOutputKeyClass(Text.class);
            job.setOutputValueClass(IntWritable.class);

            /**
             * 设置输入输出路径
             * 指定输入路径为源文件的路径。
             * 指定输出路径为目标目录的路径。
             */
            Path srcPath = new Path(url+src);
            Path destPath = new Path(url+dst);
            FileInputFormat.setInputPaths(job, srcPath);
            FileOutputFormat.setOutputPath(job,destPath);

            /**
             * 提交作业并等待完成
             * 提交MapReduce作业,并等待其完成。
             * 如果作业成功完成,则退出程序并返回状态码0,否则返回状态码1。
             */
            if (job.waitForCompletion(true)) {
                System.exit(0);
            }else{
                System.exit(1);
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(Objects.nonNull(hdfs)){
                hdfs.close();
            }
        }
    }
}
StoryMapper

StoryMapper 类继承自 Mapper 类,并实现了 map 方法。它的任务是对输入的文本行进行分词,并为每个单词输出一个键值对,键是单词本身,值是单词出现的次数。

对输入的文本行进行预处理,移除空白字符和非字母字符。

使用 StringTokenizer 对处理后的文本进行分词。

创建一个 HashMap 来存储每个单词及其出现次数。

遍历分词结果,更新单词计数,并输出键值对。

StoryReducer

StoryReducer 类继承自 Reducer 类,并实现了 reduce 方法。它的任务是将所有映射输出的相同单词的计数合并成一个总计数。

对于每个输入单词,计算其在所有输入记录中出现的总次数。

输出单词和其总计数作为最终结果。

main 方法

main 方法负责配置和运行 MapReduce 作业:

设置 Hadoop 配置项,如 dfs.replication 和 dfs.blocksize。

指定输入和输出路径。

创建 Job 对象,设置作业的相关参数,包括 Mapper 和 Reducer 类。

提交作业并等待其完成。

根据作业完成情况退出程序。

释放 HDFS 资源。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值