简介一份面向大数据初学者的Hadoop实验报告完整记录Hadoop虚拟机安装、环境配置以及Eclipse连接Hadoop的全过程并给出WordCount单词统计程序的Java实现与运行说明适合正在学习MapReduce编程框架的高校学生与自学者。资源为单个DOC文档大小758KB内含完整实验报告与可复制的源码代码。已有1726人学习浏览。报告按实验目的、环境、内容、步骤组织从启动Hadoop、检查端口状态到配置hadoop-eclipse-plugin插件再到编写MyMapper与MyReducer类逐步演示单词计数实现。代码部分包含Mapper、Reducer及Job主类注释解释了split分割、foreach遍历等关键语法能帮助读者快速理解MapReduce中“分而治之”的编程思路掌握从环境搭建到代码运行的关键流程减少实际操作中的踩坑。对于初次接触Hadoop分布式计算、需要完成课程实验报告或准备相关面试的读者这份资料兼具教学参考与代码模板价值。1. Hadoop 编程实现 wordcount 单词统计一份记录真实踩坑的实验报告Hadoop 编程实现 wordcount 单词统计听起来是入门 Demo但做大数据课程设计的人大多卡在两个地方要么环境起不来要么代码明明写对了Eclipse 里却连不上 HDFS。这份南华大学大数据实验报告的价值恰恰在于它记录了从 VMware 16 到 Hadoop 伪分布式、再到 Eclipse 插件连接和 MapReduce 源码的完整操作链并且把操作过程中的真实报错和解决方式都留在了注释里。它是一份可直接复现的上机记录不是泛泛讲原理的科普文。适合正在上大数据平台课的本科生也适合毕业设计里需要快速搭出一个可运行 Demo 的人。照着这份报告走一遍你要做的只是把路径换成自己的然后看着统计结果出现在 DFS 目录里。2. 环境搭建VMware 16 与 Hadoop 伪分布式启动的完整命令序列2.1 为什么选 VMware 16 伪分布式版本兼容与资源边界实验报告里专门点了一句VMware 版本要用 16如果用 15 及以下版本可能会出现不兼容的情况。这句提醒在上过手的人看来信息量不小。Hadoop 虚拟机镜像通常是在特定 VMware 版本下封装好的虚拟硬盘格式、VMX 配置、网卡型号和 I/O 控制器在不同大版本之间都有差异。最常见的表现是虚拟机开机黑屏、启动到一半卡住或者网络适配器一直显示未连接。我有一次帮同学调环境VMware 15 打开课程镜像系统能进终端里 ifconfig 却始终拿不到 IP换 VMware 16 之后直接恢复正常前后只花了几分钟。所以版本这块不要硬扛直接按报告说的用 16。再说伪分布式。这个实验跑的是单机伪分布式NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager 这五个进程全部挤在一台虚拟机里通过 localhost 互相通信。对学习场景来说这是资源上最划算的选型。真实集群至少三台机器光配置免密登录、同步时钟、调整副本数就要折腾大半天而伪分布式只需要一个虚拟机镜像加 2GB 内存。理解 MapReduce 的编程框架和运行流程伪分布式完全够用。它的局限在于无法体察数据分布、网络传输和节点故障带来的影响这部分只能等上了集群再补课。JDK 1.8 与 Hadoop 的搭配是另一个隐藏前提。实验报告写的 JDK 1.8不是随便选的。Hadoop 2.x 官方文档明确支持 Java 7 和 Java 8这个 2.6.0 插件版本对应的集群环境基本就是 2.6.0配 JDK 1.8 是官方支持的组合。如果你是在自己电脑上从零搭装 JDK 的时候一定盯住大版本不要顺手装了 JDK 11 或者 17后面启动 Hadoop 报IllegalAccessError的时候你会回头来找这句话。检查当前版本用java -version确认输出第一行是 1.8 开头。2.2 启动 Hadoop 的命令序列start-all.sh、jps 与 Web 端口验证启动步骤看起来就两条命令实际执行时要注意顺序和验证。打开虚拟机终端先进入 Hadoop 安装目录下的 sbin 目录然后执行cd /usr/local/hadoop/sbin ./start-all.sh./start-all.sh会一次性拉起 HDFS 和 YARN 两套服务。第一遍执行时终端会刷出一堆日志看到 “starting namenode” 和 “starting resourcemanager” 字样只能说明启动脚本执行了不代表所有进程都正常存活。Hadoop 的进程启动是异步的脚本打完日志进程可能还在初始化也可能已经崩了。所以紧接着必须执行下面这条命令确认进程真实状态jpsjps 是 JDK 自带的进程查看工具列出当前用户启动的 Java 进程 PID 和主类名。伪分布式环境下正常应该看到 5 到 6 个进程各进程的职责如下表。进程名职责NameNode管理 HDFS 元数据维护目录树和文件块的位置映射DataNode实际存储数据块响应读写请求定期向 NameNode 汇报SecondaryNameNode定期合并 NameNode 的编辑日志辅助故障恢复不是热备ResourceManagerYARN 的资源调度入口接受客户端提交的作业请求NodeManager单节点上的资源和任务执行管理器真正启动 Map/Reduce 任务的进程如果jps输出里缺了 DataNode 或者 NodeManager先别急着改代码。最常见的原因是格式化动作没做或者上一次虚拟机被强制关闭导致 NameNode 元数据状态不对。伪分布式第一次使用前需要执行一次hdfs namenode -format。课程镜像一般已经格式化过所以你只需要确认五个进程都在。如果缺失最简单的后悔药是清掉 Hadoop 配置里dfs.namenode.name.dir指向的目录重新格式化代价是 HDFS 里的旧数据全部丢失课程环境下这个代价通常可以接受。进程确认无误后打开虚拟机里的 Firefox 做 Web 验证。HDFS 的 NameNode 管理界面默认端口是 50070YARN 的 ResourceManager 调度页面默认是 8088输入下面的地址http://localhost:50070 http://localhost:808850070 页面能看到 DataNode 列表以及 HDFS 的总容量和使用情况还能直接浏览文件目录很多人在这一步会顺便确认/data/inputdata是否上传成功。8088 页面是 YARN 的资源调度后台后续提交 wordcount 作业时这个页面会实时展示作业的 Map 进度、Reduce 进度和运行日志入口是我排查作业卡住时第一个打开的页面。两个页面都能正常打开说明 Hadoop 服务真正起来了可以继续配置 Eclipse。这里补充一个常见误用有的教程会用./start-dfs.sh和./start-yarn.sh分开启动效果与start-all.sh等价。写成脚本时分开启动更灵活比如只重启 HDFS 而不影响 YARN。但排错时优先用start-all.sh一把梭因为两个脚本分开跑遗漏某一个时jps 缺进程的现象容易误导排查方向。2.3 数据准备把 inputdata 文本放进 HDFS 的正确姿势代码里写死了输入路径hdfs://localhost:8020/data/inputdata跑代码之前必须把输入文件放到 HDFS 的这个位置。最容易忽略的一点是HDFS 的目录与虚拟机本地文件系统完全隔离你在桌面新建一个 inputdata.txtHadoop 是看不见的需要显式上传。开第二个终端按这个顺序执行hdfs dfs -mkdir -p /data hdfs dfs -put /home/student/inputdata.txt /data/inputdata hdfs dfs -ls /data第一行-mkdir -p递归创建/data目录-p的意思是目录不存在就建已存在不报错第二行-put把本地文件上传到 HDFS 的/data/inputdata注意第二个参数是 HDFS 路径不是本地路径第三行-ls用于确认文件确实出现在/data下。习惯用-copyFromLocal也可以语义与-put一致。输入文本的组织方式直接影响统计结果。代码里 Mapper 用空格分词所以文本每行建议是“单词 空格 单词 空格 单词”这样的形式。如果你拿一段带标点、带 Tab 的英文文章直接塞进去统计结果里会出现带逗号、带句号的词条看起来像 Bug其实是因为文本和分隔符不匹配。课程实验里最稳的做法是准备一个纯单词的文本每行几个单词用空格隔开先跑通框架再逐步换成真实文本。上传完成后隔一会儿再去 Eclipse 的 DFS Locations 里看如果看不到刚上传的文件不要怀疑自己传错了先按 F5 刷新目录这是整个实验里出现频率最高的隐藏坑后面避坑章节会展开。到这里HDFS 侧的前置工作全部就绪。3. Eclipse 与 Hadoop 连接插件安装、连接配置与 MapReduce 项目创建3.1 hadoop-eclipse-plugin 安装版本匹配与重启的玄学Eclipse 本身不认识 Hadoop它靠一个 jar 插件获得访问 HDFS 的能力。实验报告用的是 hadoop-eclipse-plugin-2.6.0.jar版本号 2.6.0 必须与 Hadoop 内核版本对应。Hadoop 2.6.0 配 2.6.0 插件如果环境是 Hadoop 2.7 或 3.xDFS Locations 大概率直接报连接失败。这个版本对应关系没有升级迁就的余地插件会直接调用 Hadoop RPC 接口接口版本不一致时握手失败。不要试图从别处拷贝一个高版本插件配这个老集群课程环境用什么版本就用什么版本。安装动作本身很简单把下载好的 jar 拖进 Eclipse 安装目录的 plugins 文件夹然后重启 Eclipse。这里面有一个“重启的玄学”——不是关闭窗口再打开窗口而是确认 Eclipse 进程在系统里彻底退出了再重新启动。拖入 jar 时如果 Eclipse 还开着插件不会立即加载而插件 jar 有没有被加载可以从启动日志里看到也可以直接看菜单栏是否出现 DFS Locations。如果重启后还是没有黄色大象图标按两步排查第一确认 jar 放对了目录路径是 eclipse/plugins不是 workspace第二确认 Eclipse 是 64 位版本32 位 Eclipse 加载这个插件兼容性很差。插件加载成功后最直观的标志是打开 Window → Show View → Other输入 map 后能看到一个黄色大象图标的视图。如果这一步找不到基本可以认定插件没有加载成功不要继续往下配置先把重启这件事做干净。3.2 建立 Hadoop Location端口号与用户名决定成败视图打开后出现那个蓝色大象图标的导航栏这是 DFS Locations 的入口。第一次打开时它是空的需要右键选择 New Hadoop Location 填写连接参数。这些参数不是凭空填的全部对应 Hadoop 安装目录下 etc/hadoop 里的两个配置文件。core-site.xml 里的fs.defaultFS决定 HDFS 地址和端口yarn-site.xml 里的yarn.resourcemanager.address决定作业提交时连接的地址。对照配置文件填比背教程里的数字可靠得多。配置项填写内容对应配置文件Location name显示名如 hadoop-localhost无Map/Reduce Master Hostlocalhostyarn-site.xml 中 ResourceManager 地址Map/Reduce Master Port8032yarn-site.xml 中 yarn.resourcemanager.addressDFS Master Hostlocalhostcore-site.xml 中 fs.defaultFS 主机部分DFS Master Port8020core-site.xml 中 fs.defaultFS 端口部分User name虚拟机登录用户名决定 HDFS 操作权限端口是这里翻车率最高的配置项。网上大量教程写的 DFS Master Port 是 9000但那是别的课程环境的配置。你这台虚拟机的端口到底是多少直接执行cat /usr/local/hadoop/etc/hadoop/core-site.xml找到fs.defaultFS的值比如hdfs://localhost:8020那 DFS Master Port 就填 8020。Map/Reduce Master Port 同理看 yarn-site.xml 里yarn.resourcemanager.address的端口一般是 8032。填完后点击 Finish如果导航栏出现可展开的目录树能看到一个名为 hasoop 的根节点说明连接已经建立。连接建立后建议做一个快速验证在 Eclipse 里右键点击根节点选择刷新然后在/data目录下确认 inputdata 文件可见。如果 Location 填的端口不对这个视图会一直转圈最后报连接超时这种情况优先回头查配置文件而不是重装插件。还有一类报错是Permission denied原因通常是 User name 填的和虚拟机登录用户名不一致Hadoop 会把这个用户名当作 HDFS 上的身份与文件的所有者不匹配就拒绝访问。3.3 新建 MapReduce 项目路径配置与类名命名连接建立后开始建项目。File → New → Project在弹出的向导里找到 MapReduceProject如果列表里没有这一项说明插件加载有问题回到 3.1 重新检查重启流程。选中后点 Next项目名填 WordCount注意大小写。接下来最关键的一步Configure Hadoop install directory。Eclipse 需要知道 Hadoop 安装根目录才能把 hadoop-common 的 jar 和配置加入项目的 classpath否则项目里所有 import org.apache.hadoop 开头的代码都会标红。路径怎么填最靠谱在终端里执行echo $HADOOP_HOME把输出的绝对路径填进去常见的是/usr/local/hadoop。注意要填安装根目录不是 bin不是 etc填错位置编译器找不到包。如果$HADOOP_HOME输出为空说明环境变量没配那就手动找安装目录通常就是/usr/local/hadoop或/opt/hadoop确认里面有 bin、etc、share 这些子目录。项目建好后在项目 src 目录下新建 Java class类名按报告写法用 wordcount。这里有个容易踩的命名坑Java 类名默认建议大写开头但报告源码里类名是小写 wordcountpublic class 定义也是小写两者一致就行Eclipse 新建向导里填什么代码里 public class 后面就得是什么大小写不一致会在运行时报 NoClassDefFoundError。类名填 wordcountPackage 可以留空放在默认包下运行更省事。项目构建成功后Eclipse 默认会编译整个工程底部进度条没有任何红色报错说明 classpath 没问题此时直接进入下一章贴完整的 wordcount 源码跑一次作业就知道环境到底通没通。4. wordcount 源码拆解Mapper、Reducer 与 Job 三段的执行逻辑4.1 Mapper 端行偏移量、Text 与 split 的分隔符选择MapReduce 程序的执行从文件切分开始。Hadoop 会按块把输入文件切成若干 splitTextInputFormat 是默认的输入格式它会把每个 split 按行切分每行生成一条(LongWritable, Text)记录key 是行首的字节偏移量value 是这一行的文本内容。Mapper 的任务就是把每条记录拆成单词并输出(word, 1)。源码的 Mapper 部分如下。public static class MyMapper extends org.apache.hadoop.mapreduce.MapperLongWritable, Text, Text, LongWritable{ Override protected void map(LongWritable key, Text value, MapperLongWritable, Text, Text, LongWritable.Context context) throws IOException, InterruptedException { String line value.toString(); String[] splited line.split( ); for (String word : splited) { context.write(new Text(word), new LongWritable(1)); } } }这段代码里最有讨论价值的就是line.split( )。实验报告作者在注释里写了一句很重要的话如果用\t去分割用空格写的文本结果会有很大出入。反过来也一样代码里用空格分割文本里却用了 Tab整行文本会被当成一个单词统计不出预期结果。我一般在复现时直接把它改成line.split(\\s)\\s是正则表达式匹配一个或多个空白字符空格、Tab、连续空格都能正确切开输入文本的格式容错性一下子提高很多。泛型参数值得逐个解释。Mapper 的四个泛型分别是输入 key 类型、输入 value 类型、输出 key 类型、输出 value 类型。LongWritable是输入 key代表行偏移量第一个Text是这行文本输出Text是单词输出LongWritable是固定值 1。Hadoop 在这里不使用 Java 的 String 和 Integer原因在于 LongWritable、Text 这些类实现了 WritableComparable 接口能跨进程序列化传输、能被排序比较这是 MapReduce 框架数据流转的基础。新手常犯的错是在 context.write 时直接写 Java 的 String 对象类型对不上框架声明的泛型运行到一半抛 ClassCastException。4.2 Reducer 端Iterable 惰性遍历与累加Mapper 输出的海量(word, 1)会被 Shuffle 阶段排序并按键分组同一个单词的所有 value 合并为一个 Iterable 传给 Reducer。Reducer 的代码要做的事情只剩下累加。public static class MyReducer extends org.apache.hadoop.mapreduce.ReducerText, LongWritable, Text, LongWritable{ Override protected void reduce(Text k2, IterableLongWritable v2s, ReducerText, LongWritable, Text, LongWritable.Context context) throws IOException, InterruptedException { long count 0L; for (LongWritable v2 : v2s) { count v2.get(); } LongWritable v3 new LongWritable(count); context.write(k2, v3); } }这段逻辑一眼能读完count 初始化为 0遍历 Iterable 里的每个 value用v2.get()取出 long 值相加最后把单词 k2 和累计结果包装成 LongWritable 输出。这里有一个框架层面的细节Iterable 的实现是惰性的它不会一次性把该组所有 value 加载进内存而是边迭代边从服务器拉取这意味着即使某个单词出现几百万次Reducer 也不会全量驻留内存。这也是为什么计数逻辑可以放心用 long 累加的原因之一。如果对输出类型敏感会注意到 Reducer 的泛型和 Mapper 一致都是 Text 加 LongWritable。这是因为单词计数的场景里中间结果和最终结果的形态一致可以原样传递。Reducer 的 k2 命名代表着它是 Mapper 输出 key 经过分组后的对象实际运行时框架会复用对象实例如果写list.add(k2)这类操作会把同一引用反复加入这是另一个值得一提的坑课程代码里没有这个问题等以后做更复杂的聚合任务时自然会撞上。4.3 Job 配置段五个必填项、类型声明与输出路径检查main 方法做的事情是把作业的运行参数描述清楚交给 ResourceManager 调度执行。public static void main(String[] args) throws Exception { Configuration conf new Configuration(); Job job Job.getInstance(conf, wordcount.class.getSimpleName()); job.setJarByClass(wordcount.class); FileInputFormat.setInputPaths(job, hdfs://localhost:8020/data/inputdata); job.setMapperClass(MyMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(LongWritable.class); job.setReducerClass(MyReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(NullWritable.class); FileOutputFormat.setOutputPath(job, new Path(hdfs://localhost:8020/data/outputdata)); job.waitForCompletion(true); }逐段看。Job.getInstance(conf, name)创建作业实例第二个参数是作业名会上传到 YARN 的 Web 页面展示提交多个作业时靠它区分谁是谁。setJarByClass指定作业 jar 的入口类打成 jar 在集群上跑时这行不能省。FileInputFormat.setInputPaths指定输入路径直接支持目录路径Hadoop 会把目录下所有文件都当作输入。如果输入路径不存在作业会在启动阶段报路径异常等不到进入 Map 阶段这个特征也常被用来反向验证 HDFS 路径写对了没有。接下来几行是关于类型声明的最容易忽略也最容易报错。setMapperClass和setReducerClass把框架要实例化的类绑到 Job 上setMapOutputKeyClass与setMapOutputValueClass声明 Map 输出类型setOutputKeyClass与setOutputValueClass声明最终输出类型。这几行与 Mapper、Reducer 的泛型必须完全一致Hadoop 在作业初始化时会校验序列化类不一致会直接抛IOException比如常见的java.io.IOException: Type mismatch in value from map。这里要特别指出源码里的一处小瑕疵job.setOutputValueClass(NullWritable.class)与 Reducer 实际输出 LongWritable 冲突。作者可能参考了某个省略 value 的模板但在这个 Reducer 里value 是统计数字不能空。我复现时把这一行改成job.setOutputValueClass(LongWritable.class)作业立即正常完成。这个细节反复出现在各类博客里照抄代码时尤其要留意原则是输出 value 类型永远跟随 Reducer 实际写出的类型。最后的FileOutputFormat.setOutputPath设置输出目录。Hadoop 硬性要求输出目录预先不存在否则报 FileAlreadyExistsException这是为了防止误覆盖数据。每次重跑之前要删掉旧输出目录或者把路径改成带时间戳的这个坑在第 5 章细说。waitForCompletion(true)让客户端阻塞在此实时打印 Map、Reduce 进度作业失败时会输出堆栈。如果终端长时间没反应可以打开 8088 页面看任务状态NodeManager 日志里通常有真正有价值的报错信息。4.4 运行与结果校验从 DFS 终端到 part-r-00000作业运行完成后结果落在 HDFS 的/data/outputdata不在本地。用终端查看最直接。hdfs dfs -ls /data/outputdata hdfs dfs -cat /data/outputdata/part-r-00000-ls会看到两个条目_SUCCESS是一个空文件标志作业成功结束看到它基本可以确认流程走通part-r-00000是真正的输出文件文件名里的r表示来自 Reducer 阶段默认单个 reducer所以只有一个 part 文件。-cat打印的内容是每行一个单词加一个数字例如hadoop 3 hello 2 mapreduce 1 word 5在 Eclipse 里看结果则要注意刷新时的目录层级DFS Locations 的根节点下先找到 hasoop 根目录再逐层展开到 outputdata。Eclipse 不会自动感知 HDFS 的变化作业跑完必须按 F5 刷新才能看到新目录。把终端和 Eclipse 两种方式都记熟以后换到集群环境命令完全一致只是路径里的 localhost 换成 NameNode 的主机名。5. 避坑指南五个让 wordcount 跑不出来的经典翻车点5.1 VMware 版本不兼容黑屏、卡启动、网络适配器未连接现象用 VMware 15 及以下版本打开课程提供的 Hadoop 镜像虚拟机启动到一半黑屏或者能进系统但ifconfig看不到 IPHadoop 的 50070、8088 端口在宿主机浏览器里一直访问不了。原因Hadoop 镜像是按 VMware 16 的虚拟硬件配置封装的包括虚拟网卡类型、SCSI 控制器以及 VMX 中的固件设置。低版本 VMware 对这些新配置支持不完整最常见的是网卡驱动加载失败导致系统完全没有网络连接。另外低版本 VMware 的引导兼容性也不如 16黑屏多数和固件选择有关。解决直接卸载低版本装 VMware 16 后用“打开虚拟机”的方式加载镜像不要新建虚拟机重新分配硬件。镜像里 CPU、内存、硬盘参数都是封装时配好的新建会导致 Hadoop 起不来。装好后进系统第一件事查网络ifconfig能看到 eth0 的 IP 说明网卡正常。如果你是宿主机访问虚拟机页面还要确认虚拟机网络模式是 NAT并且宿主机和虚拟机在同一网段。5.2 split 分隔符与文本格式不匹配统计结果和预期出入很大现象统计结果里出现带逗号、带句号的单词条目或者明明文本里有某个高频词统计出来却是“整行大字符串”。例如输入是hello world hello结果却显示hello world 1。原因代码里line.split( )按单个空格精确切分文本用 Tab 分隔时整行不会切开文本有标点时标点会黏在单词上一起输出。Java 的 split 参数是普通字符串不是默认的模糊匹配这行代码与输入格式必须严格对齐。作者在注释里写的“用 Tab 分割导致结果有很大出入”本质就是分隔符不一致。解决不改代码就改输入文本统一用单空格去掉标点能改代码就改成line.split(\\s)这个正则匹配任意空白序列空格、Tab 全部兼容是这类问题的最优解。补充一句split 之后最好过滤一下空字符串文本行首行尾有空格时会切出空字符串输出结果里出现空 key 看着很怪过滤掉更干净。5.3 HDFS 上传文件后 Eclipse 看不见不是传错是没刷新现象终端执行hdfs dfs -put已经提示完成回 Eclipse 的 DFS Locations 里却看不到刚上传的文件刷新一次、重传一次都无效心态容易崩。原因DFS Locations 视图不监听 HDFS 目录变化它在视图打开时做了一次快照之后的文件变化不会自动同步。这是 Eclipse 插件设计上的限制不是连接问题。上传动作发生在视图之外如果不手动刷新它就一直是旧目录树。解决选中 DFS Locations 里 hasoop 根节点按 F5 刷新或者右键 Refresh新文件就会出现。刷新后还看不到再检查上传路径确认上传到了/data/inputdata而不是/user/student/data。作业跑完看输出目录也一样先刷新再找 part 文件这个习惯能省下大量无谓的等待时间。5.4 Eclipse 直接改 HDFS 文本改完重跑还是旧结果现象在 DFS Locations 里双击打开 inputdata 文件编辑保存后再跑 wordcount统计结果还是修改之前的内容甚至直接报错。原因Hadoop 插件对 DFS 文件的支持是只读优先的保存动作要么抛出 Permission denied要么根本没写回 HDFS。即使侥幸写回去了一个作业如果已经在 YARN 里启动输入数据通常已经在作业开始时被快照所以重跑仍看到旧结果。解决永远遵循“本地改好再上传”的流程。在虚拟机桌面用文本编辑器修改保存后执行hdfs dfs -put -f /home/student/inputdata.txt /data/inputdata-f参数强制覆盖同名文件避免文件已存在报错。上传完成再回 Eclipse 刷新验证文件内容后重跑作业。这套流程虽然多一步但每次都能拿到与磁盘上文本完全一致的结果不会出现改了看不到效果的诡异局面。5.5 输出目录重复FileAlreadyExistsException 与时间戳方案现象第二次运行时报org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory ... already exists手动删掉输出目录后立刻重跑偶尔还会再报一次。原因Hadoop 出于数据安全考虑禁止向已存在的输出目录写入作业启动时 FileOutputFormat 会检查输出路径。删除动作通过hdfs dfs -rm -r完成但 NameNode 的元数据更新有微小延迟删除后立刻提交作业仍可能碰到旧目录未清除干净的情况。解决运行前先删旧目录命令如下。hdfs dfs -rm -r /data/outputdata更省心的做法是把输出路径改成带时间戳的在 main 方法里拼一个动态路径String outputPath /data/output_ System.currentTimeMillis(); FileOutputFormat.setOutputPath(job, new Path(hdfs://localhost:8020 outputPath));每次运行生成独立目录互不覆盖也不用手动清理对课程实验里反复改代码重跑的场景非常实用。时间戳形式唯一的代价是输出目录会越积越多实验做完统一清理一次即可。6. 从交作业到可复用把 wordcount 改成能接真实文本的模板6.1 三处性价比最高的改动作业跑通之后值得花十分钟把这份代码升级成可复用模板。第一处split 改成line.split(\\s)之后无论输入文本用空格、Tab 还是连续空格分隔都能正确切分。第二处给 Job 加一个 Combiner一行代码job.setCombinerClass(MyReducer.class)。Combiner 会在 Map 端先做一次本机聚合把(hello, 1)、(hello, 1)先合成(hello, 2)再进入 Shuffle传输数据量明显减少文本大的时候提速显著而 Combiner 可以直接复用 Reducer 类零额外编码。第三处把输入输出路径改成命令行参数。FileInputFormat.setInputPaths(job, args[0]); FileOutputFormat.setOutputPath(job, new Path(args[1]));改完打包成 jar提交命令就变成了下面的样子和真实生产环境的提交方式一致。hadoop jar wordcount.jar wordcount /data/inputdata /data/output_$(date %s)6.2 提交前我必做的三项检查从那以后我每次帮人调试 wordcount 代码都会强制走一遍三件套先cat看一眼输入文本确认分隔符和 Mapper 的 split 一致再jps确认五个进程都在NameNode 没挂最后运行前先hdfs dfs -rm -r把旧输出目录清掉。这三步只用一分钟却拦下了我在写这门课实验时遇到过的八成翻车原因。这份带源码的实验报告下载下来建议第一件事就是把第 4 章的源码完整敲一遍再按第 6 章的改动落成模板用一段自己准备的英文文本验证统计结果。等你从伪分布式换到真实集群只需要把代码里的hdfs://localhost:8020换成 NameNode 的地址其余逻辑一行不用动这份作业的价值才算真正被用满。希望帮到你。本文还有配套的精品资源点击获取