使用Java编写上传文件到HDFS代码

将本地一个或多个文件或目录上传到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);
    }

以上代码是自己使用过程中编写的,如有问题可随时指正和交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值