HDFS Java API 常用操作¶
前置条件¶
- Windows 本地开发环境(Maven / IDEA / winutils / HADOOP_HOME)配置成功;
- 默认已能连上
hdfs://node0:9000、操作用户为zhangsan,且项目已引入hadoop-client依赖。
一、开发环境与依赖¶
任何 Java 程序读写 HDFS 前,都要先拿到 FileSystem 这个统一入口;而拿到它之前,得把依赖与运行配置就位。
01 Maven 依赖¶
hadoop-client 把 HDFS / YARN / MapReduce 所需的所有客户端类打包进来,普通业务程序引入它即可。
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
02 连接三要素¶
无论用哪种方式获取 FileSystem,本质都是在组装三样东西:
| 要素 | 含义 | 来源 |
|---|---|---|
| URI | NameNode 地址,如 hdfs://node0:9000 |
代码中 URI,或配置文件 fs.defaultFS |
| Configuration | 客户端配置,决定连哪个集群、副本数等 | new Configuration() 自动读 classpath 下 core-site.xml,或代码 set |
| 用户 | 决定以谁的权限操作 HDFS | FileSystem.get(uri, conf, user) 的第三个参数 |
URI 末尾的 / 只影响"根路径"基准,写不写都不影响连接;node0 换成你集群实际的主机名或 IP 即可。
二、获取 FileSystem¶
FileSystem 本质是一个与 NameNode 通信的客户端
FileSystem.get(URI, conf, user)¶
把地址、配置、用户一次性交给 get。下面这段也是验证"能不能连上"的最小 demo。
public class ConnectDemo {
public static void main(String[] args) throws Exception {
URI uri = URI.create("hdfs://node0:9000");
Configuration conf = new Configuration();
// 第三参数指定以 zhangsan 的身份操作,决定权限与家目录
FileSystem fs = FileSystem.get(uri, conf, "zhangsan");
System.out.println("FileSystem 连接成功: " + fs.getUri());
Path dst = new Path("/user/zhangsan/hadoop/hdfs/bigdata.txt");
System.out.println("exists? " + fs.exists(dst));
fs.close();
}
}
真实输出(集群正常时):
若输出 java.io.IOException: ... node0:9000 failed on connection 之类,先查 NameNode 是否起来、地址/端口是否写错、以及 Windows 下 HADOOP_HOME 是否配置。
三、目录与文件基本操作(CRUD)¶
增删改查是最高频的操作,API 大多集中在 FileSystem 上,语义与 Shell 命令一一对应:会 hdfs dfs -put 就大致知道 copyFromLocalFile 在做什么。
01 创建目录 mkdirs¶
对应 Shell 的 hdfs dfs -mkdir -p。父目录不存在时也会一并创建。
Path dir = new Path("/user/zhangsan/hadoop/hdfs/logs/name");
boolean ok = fs.mkdirs(dir);
System.out.println("mkdirs -> " + ok);
// 也可带权限位(类比 -mkdir -p 后再 chmod)
fs.mkdirs(dir, new FsPermission(FsAction.ALL, FsAction.NONE, FsAction.NONE));
返回 true 表示创建成功或目录已存在;false 通常是权限不足。
02 上传文件 copyFromLocalFile¶
对应 hdfs dfs -put / -copyFromLocal
复制¶
Path src = new Path("D:/studybigdata/software/2.hadoop/hadoop-3.1.3.tar.gz"); // 本地文件
Path dst = new Path("/user/zhangsan/hadoop/hdfs/hadoop-3.1.3.tar.gz");
// 基础版:本地 -> HDFS,dst 写成具体文件名
fs.copyFromLocalFile(src, dst);
复制并删除¶
Path src = new Path("D:/studybigdata/code/data/hadoop/bigdata.txt"); // 本地文件
Path dst = new Path("/user/zhangsan/hadoop/hdfs/bigdata.txt");
// 复制并删除版:带 delSrc(传 true 上传后删除本地源)
fs.copyFromLocalFile(true, src, dst);
03 下载文件 copyToLocalFile¶
对应 hdfs dfs -get。Hadoop 3.x 推荐用四参版本,二参 copyToLocalFile(src, dst) 在新版已被更严格的校验取代,容易因 .crc 处理或路径问题报错。
Path src = new Path("/user/zhangsan/hadoop/hdfs/bigdata.txt");
Path dst = new Path("D:/studybigdata/code/data/hadoop/bigdata.txt"); // 必须是"文件名",不是目录
// 四参:delSrc / 源 / 目的 / useRawLocalFileSystem
fs.copyToLocalFile(false, src, dst, true);
注意点:
dst要写到具体文件名;若写成已存在的目录会失败。useRawLocalFileSystem=true可避免下载时在本地落.crc文件。- 本地父目录需先存在,否则抛
FileNotFoundException。
04 删除 delete / deleteOnExit¶
对应 hdfs dfs -rm / -rm -r。删除非空目录必须把第二个参数 recursive 设为 true。
Path p = new Path("/user/zhangsan/hadoop/hdfs/logs");
// 删文件或空目录
fs.delete(p, false);
// 递归删整个目录树
fs.delete(p, true);
集群若开启回收站(默认
fs.trash.interval>0),delete实际是把文件移进.Trash,可用hdfs dfs -mv从回收站找回;想彻底删除需-skipTrash对应到 API 的Trash类处理(进阶,本文不展开)。
05 重命名 rename(兼移动)¶
rename 不只是改名,跨目录调用它就是"移动",对应 hdfs dfs -mv。
Path src = new Path("/user/zhangsan/hadoop/hdfs/bigdata.txt");
Path dst = new Path("/user/zhangsan/hadoop/hdfs/bigdata_v2.txt");
boolean ok = fs.rename(src, dst); // 同源目录内 = 改名
System.out.println("rename -> " + ok);
// 跨目录即移动
fs.rename(src, new Path("/tmp/bigdata.txt"));
06 判断与状态 exists / isDirectory / isFile¶
在真正读写前先判断路径类型,能避免一大批 FileNotFoundException / PathIsDirectoryException。
Path p = new Path("/user/zhangsan/hadoop/hdfs");
System.out.println("exists -> " + fs.exists(p));
System.out.println("isDir -> " + fs.isDirectory(p));
System.out.println("isFile -> " + fs.isFile(p));
FileStatus st = fs.getFileStatus(p);
System.out.println("length -> " + st.getLen());
System.out.println("owner -> " + st.getOwner());
System.out.println("modTime -> " + st.getModificationTime());
四、列举与文件状态¶
遍历目录、查看元数据、定位块位置,是写"按文件处理"程序和就近读取的基础。Shell 的 -ls 背后就是这一节的方法。
01 listStatus 列举一层¶
listStatus 只列举当前目录的直接子项,不递归,对应 hdfs dfs -ls。
Path dir = new Path("/user/zhangsan");
FileStatus[] statuses = fs.listStatus(dir);
for (FileStatus s : statuses) {
String type = s.isDirectory() ? "DIR " : "FILE";
System.out.printf("%s %s %12d %s%n",
type, s.getPermission(), s.getLen(), s.getPath());
}
02 listFiles 递归列举(带块位置)¶
需要"遍历整棵目录树"时用 listFiles,第二个参数 true 表示递归;它返回的 LocatedFileStatus 直接带块位置信息,省去再调一次 getFileBlockLocations。
RemoteIterator<LocatedFileStatus> it = fs.listFiles(dir, true);
while (it.hasNext()) {
LocatedFileStatus f = it.next();
System.out.println(f.getPath() + " blocks=" + f.getBlockLocations().length);
}
03 getFileStatus 与 getFileChecksum¶
getFileStatus 拿单个文件的完整元数据;
Path p = new Path("/user/zhangsan/hadoop/hdfs/hadoop-3.1.3.tar.gz");
FileStatus st = fs.getFileStatus(p);
System.out.println("replication=" + st.getReplication());
System.out.println("blockSize =" + st.getBlockSize());
getFileChecksum 取校验和,可用来比对"上传前后内容是否一致"。
04 getFileBlockLocations 块位置¶
返回文件每个块的物理位置(落在哪些 DataNode 上)。这是"就近读取"和"计算切分(split)"的底层依据:MapReduce 正是先查块位置,再把任务调度到离数据最近的节点。
FileStatus st = fs.getFileStatus(p);
BlockLocation[] blocks = fs.getFileBlockLocations(p, 0, st.getLen());
for (BlockLocation b : blocks) {
System.out.printf("offset=%d len=%d hosts=%s%n",
b.getOffset(), b.getLength(), Arrays.toString(b.getHosts()));
}
返回字段含义:
| 字段 | 含义 |
|---|---|
getOffset() |
该块在文件中的起始字节偏移 |
getLength() |
该块大小(最后一块通常不足 blockSize) |
getHosts() |
存放该块副本的 DataNode 主机名 |
五、IO 流方式读写¶
-
copyFromLocalFile/copyToLocalFile是封装好的便捷方法; -
要精细控制(断点续传、定位读、追加写)就得直接用流。
-
FSDataInputStream/FSDataOutputStream是带定位能力的增强流。
01 流方式上传 create + IOUtils.copyBytes¶
fs.create(path) 拿到输出流,把本地输入流拷过去即可,等价于手写版 copyFromLocalFile。
try (InputStream in = new FileInputStream("D:/studybigdata/software/2.hadoop/hadoop-3.1.3.tar.gz");
FSDataOutputStream out = fs.create(new Path("/user/zhangsan/hadoop/hdfs/hadoop-3.1.3.tar.gz"))) {
IOUtils.copyBytes(in, out, 4096, false); // 最后一个 false = 不关闭输入流
}
IOUtils.copyBytes(in, out, bufferSize, closeStreams):第四个参数决定是否由它顺手关流;用 try-with-resources 管理时传 false。
02 流方式下载 open + IOUtils.copyBytes¶
fs.open(path) 拿到输入流,拷到本地文件输出流,等价于手写版 copyToLocalFile。
FSDataInputStream in = fs.open(new Path("/user/zhangsan/hadoop/hdfs/hadoop-3.1.3.tar.gz"));
OutputStream out = new FileOutputStream("D:/studybigdata/software/2.hadoop/hadoop-3.1.3.tar.gz");
IOUtils.copyBytes(in, out, 4096, false);
03 定位读取 seek(分片下载思想)¶
FSDataInputStream 支持 seek(offset) 跳到任意字节位置再读,这是"大文件分片下载 / 断点续传"的核心:把文件按偏移切成 N 段,每段独立 seek 后读取。
FSDataInputStream in = fs.open(new Path("/user/zhangsan/hadoop/hdfs/bigdata.txt"));
in.seek(2); // 跳到第 2 字节
byte[] buf = new byte[5];
int n = in.read(buf); // 从 offset=2 起读 5 字节
System.out.println(new String(buf, StandardCharsets.UTF_8));
循环读到文件末尾¶
FSDataInputStream in = fs.open(new Path("/user/zhangsan/hadoop/hdfs/bigdata.txt"));
in.seek(2); // 跳到第 2 字节
byte[] buf = new byte[5];
for (int n; (n = in.read(buf)) != -1; ) { // 循环读到文件末尾
System.out.print(new String(buf, 0, n, StandardCharsets.UTF_8));
}
System.out.println();
六、综合实战:可运行 Demo¶
把前面操作串成一个完整程序:上传 → 校验 → 列目录 → 读内容 → 下载 → 删除,并打印真实输出。照着跑一遍,所有 API 就串起来了。
01 完整代码¶
下面是一个自包含的主类,按顺序演示六类操作;LOCAL 改成你机器上的真实路径即可运行。
package com.studybigdata.hadoop.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import java.io.*;
import java.net.URI;
public class HdfsApiDemo {
public static void main(String[] args) throws Exception {
String LOCAL = "D:/studybigdata/code/data/hadoop/bigdata.txt"; // 本地待上传文件,请先自行创建
URI uri = URI.create("hdfs://node0:9000");
Configuration conf = new Configuration();
try (FileSystem fs = FileSystem.get(uri, conf, "zhangsan")) {
Path dir = new Path("/user/zhangsan/hadoop/hdfs");
Path file = new Path(dir, "bigdata.txt");
// 1) 建目录
fs.mkdirs(dir);
// 2) 上传(已存在则先删)
if (fs.exists(file)) fs.delete(file, false);
fs.copyFromLocalFile(new Path(LOCAL), file);
System.out.println("[upload] exists=" + fs.exists(file));
// 3) 列目录
System.out.println("[list]");
for (FileStatus s : fs.listStatus(dir)) {
System.out.println(" " + (s.isDirectory() ? "DIR " : "FILE")
+ " " + s.getLen() + " " + s.getPath().getName());
}
// 4) 流方式读取前 64 字节
try (FSDataInputStream in = fs.open(file)) {
byte[] buf = new byte[64];
int n = in.read(buf);
System.out.println("[read] " + n + " bytes: " + new String(buf, 0, n).trim());
}
// 5) 下载到本地
fs.copyToLocalFile(false, file, new Path("D:/download/bigdata.txt"), true);
System.out.println("[download] done");
// 6) 清理
fs.delete(dir, true);
System.out.println("[cleanup] exists=" + fs.exists(dir));
}
}
}
HDFS Java API 小结
- 入口统一是
FileSystem:连接 = URI + Configuration + 用户 - 便捷方法(copyFromLocalFile / copyToLocalFile / mkdirs / delete / rename)语义与 Shell 一一对应
- 列举与状态靠 listStatus / listFiles / getFileStatus / getFileBlockLocations
- 精细控制(定位读、分片、追加)走 FSDataInputStream / FSDataOutputStream
- 资源用完即关,地址与权限是两大高频坑
API 是 Shell 的程序化映射:先理解命令,再看方法名就一目了然。