1 - 为什么要合并小文件
HDFS 擅长存储大文件:
我们知道,HDFS 中,每个文件都有各自的元数据信息,如果 HDFS 中有大量的小文件,就会导致元数据爆炸,集群管理的元数据的内存压力会非常大。
所以在项目中,把小文件合并成大文件,是一种很有用也很常见的优化方法。
2 - 合并本地的小文件,上传到 HDFS
将本地的多个小文件,上传到 HDFS,可以通过 HDFS 客户端的 appendToFile
命令对小文件进行合并。
在本地准备2个小文件:
# user1.txt 内容如下:
1,tom,male,16
2,jerry,male,10
# user2.txt 内容如下:
101,jack,male,19
102,rose,female,18
合并方式:
hdfs dfs -appendToFile user1.txt user2.txt /test/upload/merged_user.txt
合并后的文件内容:
3 - 合并 HDFS 的小文件,下载到本地
可以通过 HDFS 客户端的 getmerge
命令,将很多小文件合并成一个大文件,然后下载到本地。
# 先上传小文件到 HDFS:
hdfs dfs -put user1.txt user2.txt /test/upload
# 下载,同时合并:
hdfs dfs -getmerge /test/upload/user*.txt ./merged_user.txt
下载、合并后的文件内容:
4 - 通过 Java API 实现文件合并和上传
代码如下(具体测试项目,可到 我的 GitHub 查看):
@Test
public void testMergeFile() throws Exception {
// 获取分布式文件系统
FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop:9000"), new Configuration(), "healchow");
FSDataOutputStream outputStream = fileSystem.create(new Path("/test/upload/merged_by_java.txt"));
// 获取本地文件系统
LocalFileSystem local = FileSystem.getLocal(new Configuration());
// 通过本地文件系统获取文件列表,这里必须指定路径
FileStatus[] fileStatuses = local.listStatus(new Path("file:/Users/healchow/bigdata/test"));
for (FileStatus fileStatus : fileStatuses) {
// 创建输入流,操作完即关闭
if (fileStatus.getPath().getName().contains("user")) {
FSDataInputStream inputStream = local.open(fileStatus.getPath());
IOUtils.copy(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
}
}
// 关闭输出流和文件系统
IOUtils.closeQuietly(outputStream);
local.close();
fileSystem.close();
}
合并的结果,和通过命令合并的完全一致: