将本地一个或多个文件或目录上传到HDFS的指定目录下,上传之前先进行源文件和路径的检查,然后对比本地路径和目录路径是否存在文件重复,检查完毕之后再上传写入。代码如下:
public class HdfsUpload extends HdfsBase {
private Map<File, Path> uploadFiles; // 要上传的文件列表,src->dest,local->hdfs
private FileSystem hdfs;
private FileSystem localfs;
public HdfsUpload() {
// 初始化本地和HDFS文件系统对象
Configuration conf = new Configuration();
// uri代替了conf中的fs.defaultFS
this.hdfs = FileSystem.get(URI.create("hdfs://ip:port"), conf, "user_name");
this.localfs = FileSystem.getLocal(conf);
uploadFiles = new HashMap<>();
}
//============================== 公共调用方法 ==============================//
/**
* 上传文件,dest必须为目录,如果src为文件则上传到dest目录下,如果src为目录,则将该目录及其下的内容上传到dest目录下。
* 上传文件格式:/tmp/directory,/tmp/data.txt,/tmp/*,/tmp/a*b。都将directory或data.txt或匹配的文件或目录放在dest下。
*
* @param src 源文件
* @param dest 目标位置
*/
public void upload(String src, String dest) {
if (this.hdfs == null || this.localfs == null) {
return;
}
Path hdfsPath = new Path(dest);
try {
File[] files = parseSourcePath(src);
if (files != null && files.length > 0 && checkDestPath(hdfsPath, files)) {
uploadSchedule();
}
} catch (IOException ex) {
ex.printStackTrace();
} finally {
this.hdfs.close();
this.localfs.close();
this.uploadFiles.clear();
}
}
/**
* 上传文件,采用控制台进度百分比和进度条技术
*/
private void uploadSchedule() {
if (this.uploadFiles.size() == 0) {
printMessage("上传的文件总数量为0。");
return;
}
printMessage(String.format("上传文件个数:%d", this.uploadFiles.size()));
this.uploadFiles.forEach((localFile, hdfsPath) -> {
long fileSize = localFile.length();
try (InputStream in = new BufferedInputStream(new FileInputStream(localFile))) {
printMessage(String.format("正在上传第%d个文件", ++fileCount));
FSDataOutputStream out = this.hdfs.create(hdfsPath, new HdfsProgressable(fileSize));
// 第一个字段输入本地文件系统用BufferedInputStream,HDFS用FSDataInputStream
IOUtils.copyBytes(in, out, 1 << 16); // buffSize参数是接收读取时缓存大小,即inputStream.read(buff)
/*
1. hflush将Client缓存的所有数据(packet)立即发送给DataNode。close也会触发flush。
2. hsync除了hflush还执行sync,让DataNode执行一次fsync操作,将数据写入磁盘,
触发DataNode更新Block的length,此后read就可以读取到。
*/
out.hsync();
out.close();
} catch (IOException ex) {
ex.printStackTrace();
}
});
}
// 验证上传的文件结构是否和HDFS上的同名,目标文件存在则返回False,验证方式:localPaths作为hdfsPath子内容
// 只验证文件,目标位置目录和本地目录同名没关系,所以可能存在上传新文件到已存在子目录中,这里务必注意
private boolean checkDestPath(Path hdfsPath, File[] localFiles) throws IOException {
if (CheckUtil.isEmpty(localFiles)) {
return true;
}
FileStatus fileStatus = this.hdfs.getFileStatus(hdfsPath);
if (fileStatus.isFile()) {
printMessage("HDFS上已经存在和要上传到的目录同名的文件!");
return false;
}
for (File localFile : localFiles) {
Path hdfsChildPath = new Path(hdfsPath, localFile.getName());
boolean existsHdfs = this.hdfs.exists(hdfsChildPath);
if (localFile.isDirectory()) {
if (existsHdfs && this.hdfs.getFileStatus(hdfsChildPath).isFile()) {
printMessage("HDFS上存在与要上传的目录同名的文件!");
return false;
}
// 获取父路径下的所有文件/目录列表,排除了.开头的隐藏文件(Windows未排除)
File[] childFiles = localFile.listFiles(x -> !x.getName().startsWith("."));
if (!checkDestPath(hdfsChildPath, childFiles)) {
return false;
}
} else if (existsHdfs) {
printMessage("HDFS上存在与要上传的文件同名的文件或目录!");
return false;
} else {
this.uploadFiles.put(localFile, hdfsChildPath);
}
}
return true;
}
// 解析上传的本地文件
private File[] parseSourcePath(String src) throws IOException {
src = src.endsWith(File.separator) ? src.substring(0, src.length() - 1) : src; // 去掉末端的分隔符
char match = '*';
int lastSeparatorIndex = src.lastIndexOf(File.separatorChar);
int matchIndex = src.indexOf(match);
if (matchIndex < 0) {
File file = new File(src);
if (!file.exists()) {
printMessage("要上传的文件不存在!");
return null;
}
return new File[]{file};
} else if (lastSeparatorIndex < matchIndex && src.lastIndexOf(match) == matchIndex) {
String base = src.substring(0, lastSeparatorIndex); // 保存要上传文件的基目录
File basePath = new File(base);
if (!basePath.isDirectory()) {
printMessage("参数指定的父目录不正确,原因可能是不存在!");
return null;
}
String[] nameParts = src.substring(lastSeparatorIndex + 1).split("\\*");
return basePath.listFiles(x -> !x.getName().startsWith(".")
&& x.getName().startsWith(nameParts[0])
&& x.getName().endsWith(nameParts[1]));
} else {
printMessage("本地路径参数不正确!");
return null;
}
}
protected static void printMessage(String message) {
System.out.println(message);
}
以上代码是自己使用过程中编写的,如有问题可随时指正和交流。