【版权声明】版权所有,请勿转载!
【相关文章推荐】《大数据软件安装和基础编程实践指南》,详细指导VMWare、Ubuntu、Hadoop、HDFS、HBase、Hive、MapReduce、Spark、Flink的安装和基础编程
作者:郑楷阳
指导老师:厦门大学计算机系林子雨副教授
E-mail: ziyulin@xmu.edu.cn
本指南介绍Hadoop分布式文件系统HDFS,并详细指引读者对HDFS文件系统的操作实践。请务必仔细阅读完厦门大学林子雨编著的《大数据技术原理与应用(第4版)》第3章节,再结合本指南进行学习。
Hadoop分布式文件系统(Hadoop Distributed File System,HDFS)是Hadoop核心组件之一,如果已经安装了Hadoop,其中就已经包含了HDFS组件,不需要另外安装。
学习本指南需要在Linux系统安装好Hadoop.如果机器上没有安装Linux和Hadoop,请访问这篇博客,学习并安装。
第3章涉及到很多的理论知识点,主要的理论知识点包括:分布式文件系统、HDFS简介、HDFS的相关概念、HDFS体系结构、HDFS的存储原理、HDFS的数据读写过程。这些理论知识点,请自己依靠厦门大学林子雨编著的《大数据技术原理与应用(第4版)》第3章节进行学习,本指南不再重复表述。
接下来介绍Linux操作系统中关于HDFS文件操作的常用Shell命令,利用Web界面查看和管理Hadoop文件系统,以及利用Hadoop提供的Java API进行基本的文件操作。
在学习HDFS编程实践前,我们需要启动Hadoop 3.5.0。执行如下命令
cd /usr/local/hadoop
./sbin/start-dfs.sh #启动hadoop
一、利用Shell命令与HDFS进行交互
Hadoop支持很多Shell命令,其中fs是HDFS最常用的命令,利用fs可以查看HDFS文件系统的目录结构、上传和下载数据、创建文件等。
注意: 教材《大数据技术原理与应用》的命令是以"./bin/hadoop dfs"开头的Shell命令方式,实际上有三种shell命令方式。
- hadoop fs
- hadoop dfs
- hdfs dfs
hadoop fs适用于任何不同的文件系统,比如本地文件系统和HDFS文件系统
hadoop dfs只能适用于HDFS文件系统
hdfs dfs跟hadoop dfs的命令作用一样,也只能适用于HDFS文件系统
我们可以在终端输入如下命令,查看fs总共支持了哪些命令
./bin/hadoop fs

在终端输入如下命令,可以查看具体某个命令的作用
例如:我们查看put命令如何使用,可以输入如下命令
./bin/hadoop fs -help put

由于Hadoop支持的命令众多,想更多了解Hadoop命令,请参照《大数据技术原理与应用(第2版)》3.7小节学习。
1.目录操作
需要注意的是,Hadoop系统安装好以后,第一次使用HDFS时,需要首先在HDFS中创建用户目录。本教程全部采用hadoop用户登录Linux系统,因此,需要在HDFS中为hadoop用户创建一个用户目录,命令如下:
cd /usr/local/hadoop
./bin/hdfs dfs -mkdir -p /user/hadoop
该命令中表示在HDFS中创建一个“/user/hadoop”目录,“–mkdir”是创建目录的操作,“-p”表示如果是多级目录,则父目录和子目录一起创建,这里“/user/hadoop”就是一个多级目录,因此必须使用参数“-p”,否则会出错。
“/user/hadoop”目录就成为hadoop用户对应的用户目录,可以使用如下命令显示HDFS中与当前用户hadoop对应的用户目录下的内容:
./bin/hdfs dfs -ls .
该命令中,“-ls”表示列出HDFS某个目录下的所有内容,“.”表示HDFS中的当前用户目录,也就是“/user/hadoop”目录,因此,上面的命令和下面的命令是等价的:
./bin/hdfs dfs -ls /user/hadoop
如果要列出HDFS上当前用户的所有目录,可以使用如下命令:
./bin/hdfs dfs -ls
下面,可以使用如下命令创建一个input目录:
./bin/hdfs dfs -mkdir -p input
在创建个input目录时,采用了相对路径形式,实际上,这个input目录创建成功以后,它在HDFS中的完整路径是“/user/hadoop/input”。如果要在HDFS的根目录下创建一个名称为input的目录,则需要使用如下命令:
./bin/hdfs dfs -mkdir -p /input
可以使用rm命令删除一个目录,比如,可以使用如下命令删除刚才在HDFS中创建的“/input”目录(不是“/user/hadoop/input”目录):
./bin/hdfs dfs -rm -r /input
上面命令中,“-r”参数表示如果删除“/input”目录及其子目录下的所有内容,如果要删除的一个目录包含了子目录,则必须使用“-r”参数,否则会执行失败。
2.文件操作
在实际应用中,经常需要从本地文件系统向HDFS中上传文件,或者把HDFS中的文件下载到本地文件系统中。
首先,使用vim编辑器,在本地Linux文件系统的“/home/hadoop/”目录下创建一个文件myLocalFile.txt,里面可以随意输入一些单词,比如,输入如下三行:
Hadoop
Spark
XMU DBLAB
然后,可以使用如下命令把本地文件系统的“/home/hadoop/myLocalFile.txt”上传到HDFS中的当前用户目录的input目录下,也就是上传到HDFS的“/user/hadoop/input/”目录下:
./bin/hdfs dfs -put /home/hadoop/myLocalFile.txt input
可以使用ls命令查看一下文件是否成功上传到HDFS中,具体如下:
./bin/hdfs dfs -ls input
该命令执行后会显示类似如下的信息:
Found 1 items
-rw-r--r-- 1 hadoop supergroup 36 2017-01-02 23:55 input/myLocalFile.txt
下面使用如下命令查看HDFS中的myLocalFile.txt这个文件的内容:
./bin/hdfs dfs -cat input/myLocalFile.txt
下面把HDFS中的myLocalFile.txt文件下载到本地文件系统中的“/home/hadoop/下载/”这个目录下,命令如下:
./bin/hdfs dfs -get input/myLocalFile.txt /home/hadoop/下载
可以使用如下命令,到本地文件系统查看下载下来的文件myLocalFile.txt:
cd ~
cd 下载
ls
cat myLocalFile.txt
最后,了解一下如何把文件从HDFS中的一个目录拷贝到HDFS中的另外一个目录。比如,如果要把HDFS的“/user/hadoop/input/myLocalFile.txt”文件,拷贝到HDFS的另外一个目录“/input”中(注意,这个input目录位于HDFS根目录下),可以使用如下命令:
./bin/hdfs dfs -mkdir -p /input
./bin/hdfs dfs -cp input/myLocalFile.txt /input
二、利用Web界面管理HDFS
打开Linux自带的Firefox浏览器,点击此链接HDFS的Web界面,即可看到HDFS的web管理界面。WEB界面的访问地址是http://localhost:9870。

三、利用Java API与HDFS进行交互
Hadoop提供了访问HDFS的Java API。通过这些接口,可以在Java程序中创建文件、读取文件、列出目录和合并文件。本节使用安装在Ubuntu中的TRAE CN编写、运行和打包Java程序。需要进一步了解接口时,可以查看Hadoop 3.5.0 API文档。
(一)在Ubuntu中安装TRAE
TRAE提供代码编辑、项目管理和扩展功能。本教程通过Java扩展完成HDFS程序开发。以下操作以Ubuntu中的TRAE CN界面为例,下载文件名和个别按钮可能随软件版本变化。
打开TRAE下载页面,选择适合Ubuntu的.deb安装包。本文使用64位x64虚拟机,因此选择“.deb(x64)”。

下载完成后,在Ubuntu文件管理器中找到安装包,例如TRAE_CN-linux-x64.deb。双击安装包,打开软件安装界面,点击“安装”,并按提示输入密码。

安装完成后,在Ubuntu应用菜单中找到“Trae CN”,点击启动。

首次启动时,按照界面提示完成初始化。欢迎页点击“开始”,随后根据需要选择登录方式。


本文截图中的登录页提供“跳过”入口,可先进入编辑界面完成Java实验。需要使用AI相关功能时,再按照软件提示登录。
如果启动后显示“Build With TRAE”页面,点击右上角的“IDE”,进入代码编辑窗口。

进入IDE窗口后,可以看到“打开文件夹”“新建项目”等入口。

(二)使用TRAE开发HDFS Java程序
本节的任务是:在HDFS的/user/hadoop目录中准备file1.txt、file2.txt、file3.txt、file4.abc和file5.abc,读取后缀不为.abc的普通文件,将内容合并到/user/hadoop/merge.txt。
本文使用Hadoop 3.5.0和JDK 17。Java程序运行时采用JDK 17,HDFS连接地址为hdfs://localhost:9000;如果你的实验环境采用其他地址,应将代码中的地址改为实际配置。Apache对Hadoop 3.5的Java版本要求见官方说明。
1. 安装Java扩展并创建项目
在TRAE左侧打开扩展面板,搜索java,找到“Extension Pack for Java”并点击“安装”。该扩展包的ID为vscjava.vscode-java-pack,安装完成后可以使用Java代码提示、运行、调试和项目管理等功能。

先在Ubuntu终端创建用于存放项目的父目录:
mkdir -p /home/hadoop/Documents/trae_projects
在TRAE左侧资源管理器中点击鼠标右键,选择“创建Java项目...”。也可以按Ctrl+Shift+P打开命令面板,输入并选择“Java: Create Java Project...”。Java项目创建功能说明

在项目类型选择界面,选择“No build tools”,创建本实验所用的普通Java项目。

在保存位置选择父目录/home/hadoop/Documents/trae_projects,确认后将项目名称输入为HDFSExample。创建后的Ubuntu本地项目目录为:
/home/hadoop/Documents/trae_projects/HDFSExample
不同系统可能将“Documents”目录显示为“文档”。请以实际选择的保存位置为准,后面的JAR查找和复制命令也使用该项目目录。
打开刚创建的项目时,如果出现工作区信任提示,确认这是自己创建的实验项目后,点击“是,我信任此作者”。

创建完成后,项目中通常包含src、bin、lib和.vscode等目录,并在src中生成示例文件App.java。本实验将在src中创建MergeFile.java。
按Ctrl+Shift+P,执行“Java: Configure Java Runtime”,确认当前项目使用JDK 17。本文截图中的JDK路径为/usr/lib/jvm/jdk-17.0.20.1+1,请根据自己安装的位置选择。项目JDK配置说明
Java语言服务和项目程序使用的Java运行环境可以不同。当前Java扩展的平台安装包通常内置语言服务所需的JRE;若安装的是通用扩展包并提示需要JDK 21,应另外配置语言服务的运行环境,项目仍选择JDK 17。Java扩展的JDK要求
2. 为项目添加需要用到的JAR包
HDFS的Java接口及其依赖位于Hadoop安装目录中。本教程的Hadoop安装在/usr/local/hadoop,需要加载的JAR分别位于以下四个目录:
| 目录 | 添加内容 |
|---|---|
| /usr/local/hadoop/share/hadoop/common | 当前目录中的所有.jar文件 |
| /usr/local/hadoop/share/hadoop/common/lib | 当前目录中的所有.jar文件 |
| /usr/local/hadoop/share/hadoop/hdfs | 当前目录中的所有.jar文件 |
| /usr/local/hadoop/share/hadoop/hdfs/lib | 当前目录中的所有.jar文件 |
在TRAE左侧资源管理器下方找到“Java Projects”,展开HDFSExample项目,找到“Referenced Libraries(引用的库)”。将鼠标移到该行,点击右侧的“+”,打开JAR文件选择窗口。Java项目扩展支持通过这个入口添加依赖。依赖管理功能说明

先进入/usr/local/hadoop/share/hadoop/common,选中当前目录中的JAR文件,点击“Select Jar Libraries”。例如,本文环境中可以看到hadoop-common-3.5.0.jar、hadoop-common-3.5.0-tests.jar、hadoop-kms-3.5.0.jar、hadoop-nfs-3.5.0.jar和hadoop-registry-3.5.0.jar。

随后重复点击“+”,分别进入common/lib、hdfs和hdfs/lib,添加各目录中的JAR文件。选择文件时只选.jar文件;lib等子目录要单独进入后添加其中的JAR。特别是common/lib中的依赖也必须添加,其中包含woodstox-core、stax2-api等程序运行时需要用到的库。
全部添加后,展开“Referenced Libraries”,检查Hadoop的JAR和依赖JAR是否已经列出,等待Java项目加载完成。
3. 编写Java应用程序
在左侧项目文件树中,找到src目录,点击鼠标右键,选择“创建Java类 → 类...”,输入名称MergeFile,创建MergeFile.java。也可以在src中直接新建同名文件。

打开MergeFile.java,输入下面的代码。本示例保留文件过滤与合并的练习内容,并跳过子目录和输出文件本身,便于接着前面的Shell实验和重复运行。
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.PathFilter;
/** 过滤掉文件名满足指定正则表达式的条目。 */
class MyPathFilter implements PathFilter {
private final String reg;
MyPathFilter(String reg) {
this.reg = reg;
}
@Override
public boolean accept(Path path) {
return !path.getName().matches(reg);
}
}
/** 读取符合条件的HDFS文件,并合并其内容。 */
public class MergeFile {
private final Path inputPath;
private final Path outputPath;
public MergeFile(String input, String output) {
this.inputPath = new Path(input);
this.outputPath = new Path(output);
}
public void doMerge() throws IOException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9000");
conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
try (FileSystem fs = FileSystem.newInstance(URI.create(inputPath.toString()), conf)) {
FileStatus[] sourceStatus = fs.listStatus(inputPath,
new MyPathFilter(".*\\.abc"));
Arrays.sort(sourceStatus,
Comparator.comparing(status -> status.getPath().getName()));
Path qualifiedOutput = fs.makeQualified(outputPath);
byte[] data = new byte[1024];
// 再次运行时覆盖原有输出文件。
try (FSDataOutputStream destination = fs.create(outputPath, true)) {
for (FileStatus status : sourceStatus) {
if (!status.isFile()
|| fs.makeQualified(status.getPath()).equals(qualifiedOutput)) {
continue;
}
System.out.print("路径:" + status.getPath()
+ " 文件大小:" + status.getLen()
+ " 权限:" + status.getPermission() + " 内容:");
try (FSDataInputStream source = fs.open(status.getPath())) {
int count;
while ((count = source.read(data)) != -1) {
System.out.write(data, 0, count);
destination.write(data, 0, count);
}
}
}
}
}
}
public static void main(String[] args) throws IOException {
MergeFile merge = new MergeFile(
"hdfs://localhost:9000/user/hadoop/",
"hdfs://localhost:9000/user/hadoop/merge.txt");
merge.doMerge();
}
}
代码中的MyPathFilter排除后缀为.abc的条目;循环中的判断再排除子目录和merge.txt本身。程序按文件名排序后逐一读取,使用输入流读取字节,再将同一批字节写入输出流。输入文件本身应有换行,程序不会额外添加分隔符。
4. 编译运行程序
运行前,确认HDFS服务已经启动。如果尚未启动,在Ubuntu终端执行:
cd /usr/local/hadoop
./sbin/start-dfs.sh
本节沿用基础安装教程的目录约定:Hadoop安装在/usr/local/hadoop,并已设置hadoop用户对该安装目录的读写权限。下面的示例文件创建、myapp目录创建和JAR复制均在此权限下执行。
接下来准备五个示例文件。这里将它们创建在Ubuntu本地的/usr/local/hadoop目录中。在终端执行:
cd /usr/local/hadoop
printf 'this is file1.txt\n' > file1.txt
printf 'this is file2.txt\n' > file2.txt
printf 'this is file3.txt\n' > file3.txt
printf 'this is file4.abc\n' > file4.abc
printf 'this is file5.abc\n' > file5.abc
为HDFS中的hadoop用户创建目录,并上传这些文件:
./bin/hdfs dfs -mkdir -p /user/hadoop
./bin/hdfs dfs -put file1.txt file2.txt file3.txt file4.abc file5.abc /user/hadoop/
./bin/hdfs dfs -ls /user/hadoop
file1.txt等是Ubuntu本地文件,上传目标/user/hadoop/属于HDFS。文件上传后,用hdfs dfs -ls查看HDFS中的内容。本文已在core-site.xml中将fs.defaultFS配置为hdfs://localhost:9000,因此Shell命令可以直接使用/user/hadoop/这样的路径。如果重复上传时提示同名文件已经存在,可确认示例内容后使用-put -f更新。
回到TRAE,按Ctrl+S保存MergeFile.java,等待Java项目加载完成。点击main方法上方的“Run(运行)”,也可以使用编辑器右上角的Java运行按钮。运行入口的位置如下图所示,程序代码以本节上面的文本为准。需要调试时,可以在行号左侧设置断点,再点击“Debug”。

程序运行后,TRAE底部终端会显示被合并文件的路径、大小、权限和内容,输出类似如下信息:
路径:hdfs://localhost:9000/user/hadoop/file1.txt 文件大小:18 权限:rw-r--r-- 内容:this is file1.txt
路径:hdfs://localhost:9000/user/hadoop/file2.txt 文件大小:18 权限:rw-r--r-- 内容:this is file2.txt
路径:hdfs://localhost:9000/user/hadoop/file3.txt 文件大小:18 权限:rw-r--r-- 内容:this is file3.txt
终端中类似log4j:WARN No appenders could be found的提示是日志配置警告。判断本次实验是否成功,应继续查看HDFS中是否生成了正确的输出文件;如果出现Exception及堆栈,应先处理对应错误。
在Ubuntu终端执行:
cd /usr/local/hadoop
./bin/hdfs dfs -ls /user/hadoop
./bin/hdfs dfs -cat /user/hadoop/merge.txt
预期合并结果如下:
this is file1.txt
this is file2.txt
this is file3.txt
.abc文件的内容不会写入结果。前面Shell实验创建的input子目录也会被代码跳过。
5. 应用程序的部署
完成TRAE中的运行实验后,可以将项目导出为JAR,再通过Hadoop命令运行。本文使用的TRAE导出流程会将JAR生成在项目根目录中。本节从项目根目录取得该文件,随后复制到/usr/local/hadoop/myapp,统一部署路径。
在TRAE中按Ctrl+Shift+P,搜索“export”,选择“Java: 导出到Jar文件...”或“Java: Export Jar...”。这是Java项目扩展提供的导出功能。JAR导出功能说明

在“Export Jar: Determine main class”界面选择MergeFile作为主类。

随后进入“Export Jar: Determine elements”界面。下图展示的是默认选择状态。

本例将在已有Hadoop环境中通过hadoop jar运行,因此导出时取消全选,仅勾选项目的编译输出目录bin,然后点击“确定”。bin中包括MergeFile.class和MyPathFilter.class;Hadoop相关依赖由运行环境提供。
点击“确定”后,等待导出完成。导出成功时,终端会显示类似下面的完成提示:
[SUCCESS] Export Jar process is finished successfully
如果导出日志同时出现大量duplicate entry警告,应检查导出范围,确认只选择项目的bin目录后重新导出。
当前项目名为HDFSExample,生成的HDFSExample.jar会出现在项目根目录中,本文对应的位置为:
/home/hadoop/Documents/trae_projects/HDFSExample/HDFSExample.jar
在TRAE左侧项目文件树中展开项目根目录,可以找到HDFSExample.jar;也可以用Ubuntu文件管理器打开该项目目录查看。

导出完成后,打开Ubuntu终端,先确认JAR存在,再创建部署目录并复制文件:
ls -l /home/hadoop/Documents/trae_projects/HDFSExample/HDFSExample.jar
mkdir -p /usr/local/hadoop/myapp
cp /home/hadoop/Documents/trae_projects/HDFSExample/HDFSExample.jar /usr/local/hadoop/myapp/
ls -l /usr/local/hadoop/myapp/HDFSExample.jar
如果项目保存位置不同,请将第一条和第三条命令中的项目路径改为实际位置。这里的项目目录、myapp目录和JAR文件均属于Ubuntu本地文件系统。
现在通过Hadoop运行JAR:
cd /usr/local/hadoop
./bin/hadoop jar ./myapp/HDFSExample.jar MergeFile
./bin/hdfs dfs -cat /user/hadoop/merge.txt
./myapp/HDFSExample.jar相对于当前的/usr/local/hadoop目录,实际位置为/usr/local/hadoop/myapp/HDFSExample.jar。命令最后的MergeFile明确指定程序入口。Hadoop支持使用hadoop jar运行Java应用,详见Hadoop 3.5.0命令说明。
本版代码会排除旧的输出文件,并覆盖生成新的merge.txt。最后一条命令的预期结果为:
this is file1.txt
this is file2.txt
this is file3.txt
得到上述内容,说明导出的JAR完成了相同的HDFS合并任务。
附录:自己练习用的代码文件
下面给出几个代码文件,供读者自己练习。
1.写入文件
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.Path;
public class Chapter3 {
public static void main(String[] args) {
try {
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://localhost:9000");
conf.set("fs.hdfs.impl","org.apache.hadoop.hdfs.DistributedFileSystem");
FileSystem fs = FileSystem.get(conf);
byte[] buff = "Hello world".getBytes(); // 要写入的内容
String filename = "test"; //要写入的文件名
FSDataOutputStream os = fs.create(new Path(filename));
os.write(buff,0,buff.length);
System.out.println("Create:"+ filename);
os.close();
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.判断文件是否存在
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class Chapter3 {
public static void main(String[] args) {
try {
String filename = "test";
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://localhost:9000");
conf.set("fs.hdfs.impl","org.apache.hadoop.hdfs.DistributedFileSystem");
FileSystem fs = FileSystem.get(conf);
if(fs.exists(new Path(filename))){
System.out.println("文件存在");
}else{
System.out.println("文件不存在");
}
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.读取文件
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FSDataInputStream;
public class Chapter3 {
public static void main(String[] args) {
try {
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://localhost:9000");
conf.set("fs.hdfs.impl","org.apache.hadoop.hdfs.DistributedFileSystem");
FileSystem fs = FileSystem.get(conf);
Path file = new Path("test");
FSDataInputStream getIt = fs.open(file);
BufferedReader d = new BufferedReader(new InputStreamReader(getIt));
String content = d.readLine(); //读取文件一行
System.out.println(content);
d.close(); //关闭文件
fs.close(); //关闭hdfs
} catch (Exception e) {
e.printStackTrace();
}
}
}
