You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FTP下载CSV拆分后上传失败:OutputStream空指针异常求助

FTP下载拆分后上传出现OutputStream空指针异常的解决方案

问题背景

我在实现从FTP下载CSV文件、拆分后上传回同一服务器的功能时遇到了问题。具体需求是:下载一个约1000行的CSV,拆分为每个100行的小文件后上传到不同目录。目前已经成功连接FTP、下载并处理了文件,但在保存小文件时出现了OutputStream空指针异常(确认ftpClient和remoteFile都不为空),单独测试上传方法是正常的。

相关代码如下:

下载拆分方法

public boolean getFile1(final String filePath, final String fileName) {
 String remoteFile = filePath + "/" + fileName;
 BufferedReader reader = null;
 try{
 InputStream is = ftpClient.retrieveFileStream(remoteFile);
 reader = new BufferedReader(new InputStreamReader(is));
 String line;
 String file ="";
 int i = 1;
 int max = 100;
 int current = 0;
 while((line = reader.readLine()) != null){
 if(current < max){
 file+=line+"\n";
 current++;
 } else{
 System.out.println("Else; File: " +file);
 InputStream stream = new ByteArrayInputStream(file.getBytes(StandardCharsets.UTF_8.name()));
 putFile(stream, "test", "MOCK_COPY"+i+".csv");
 i++;
 file = "";
 current = 0;
 file+=line+"\n";
 }
 }
 if(!file.isEmpty()){
 System.out.println("End File: " +file);
 InputStream is2 = IOUtils.toInputStream(file, "UTF-8");
 putFile(is2, "test", "MOCK_COPY"+i+".csv");
 }
 } catch (IOException e){
 e.printStackTrace();
 } finally {
 try{
 reader.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 return true;
}

上传方法

public Boolean putFile(InputStream inputStream, final String filePath, final String fileName) throws IOException {
 Long ts = System.currentTimeMillis();
 String remoteFile = filePath + "/" + fileName;
 System.out.println("Start uploading file");
 // ftpClient.changeWorkingDirectory("test");
 ////this overwrites the existing file
 // ftpClient.storeFile(remoteFile, inputStream);
 //inputStream.close();
 OutputStream outputStream = ftpClient.storeFileStream(remoteFile);
 byte[] bytesIn = new byte[4096];
 int read = 0;
 while ((read = inputStream.read(bytesIn)) != -1) {
 outputStream.write(bytesIn, 0, read);
 }
 inputStream.close();
 outputStream.close();
 System.out.println("The file is uploaded successfully in " + (System.currentTimeMillis() - ts) / 1000 + "s");
 return true;
}

连接方法

public void connectToServer(String server, String user, String pass, Integer port) throws WebApplicationException {
 port = port == null ? 21 : null;
 server = server.replace("ftp://","");
 try {
 ftpClient = new FTPClient();
 ftpClient.connect(server, port);
 ftpClient.login(user, pass);
 ftpClient.enterLocalPassiveMode();
 ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
 System.out.println("Connected to ftp");
 } catch (IOException e) {
 e.printStackTrace();
 throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND)
 .entity("FTP error: " + e.getMessage()).type(MediaType.TEXT_PLAIN).build());
 }
}

问题分析

出现空指针的核心原因是你在使用ftpClient.retrieveFileStream()下载文件后,没有调用ftpClient.completePendingCommand()来完成FTP命令。

FTP协议中,retrieveFileStream()是一个异步的获取操作:服务器发送文件数据后,需要客户端明确发送“命令完成”的确认,这个连接通道才能被用于后续的FTP操作(比如上传)。如果不调用completePendingCommand(),你的FTP客户端会一直卡在等待下载命令收尾的状态,此时调用storeFileStream()上传时,服务器不会响应,返回的OutputStream就是null,自然触发空指针异常。

另外你的代码还有两个潜在问题:

  1. connectToServer里的端口赋值逻辑错误:port = port == null ? 21 : null;,如果传入了合法的端口号,会被错误地覆盖为null,导致连接异常(你当前可能是传了null所以没暴露问题)
  2. retrieveFileStream()可能返回null(比如远程文件不存在),你的代码没有做判空处理,会直接导致后续的InputStreamReader初始化失败

修复方案

下面是针对问题的具体修复步骤和优化后的代码:

1. 修正下载方法:完成FTP命令+资源优化

在下载完成后必须调用completePendingCommand(),同时用StringBuilder代替String拼接提升性能,增加输入流判空:

public boolean getFile1(final String filePath, final String fileName) {
    String remoteFile = filePath + "/" + fileName;
    BufferedReader reader = null;
    InputStream is = null;
    try {
        is = ftpClient.retrieveFileStream(remoteFile);
        // 判空:避免文件不存在时的空指针
        if (is == null) {
            System.err.println("Failed to download file: " + remoteFile);
            return false;
        }
        reader = new BufferedReader(new InputStreamReader(is));
        String line;
        // 用StringBuilder替代String拼接,减少内存开销
        StringBuilder fileContent = new StringBuilder();
        int i = 1;
        final int max = 100;
        int current = 0;

        while ((line = reader.readLine()) != null) {
            if (current < max) {
                fileContent.append(line).append("\n");
                current++;
            } else {
                System.out.println("Preparing to upload split file " + i);
                // 使用try-with-resources自动关闭流
                try (InputStream stream = new ByteArrayInputStream(fileContent.toString().getBytes(StandardCharsets.UTF_8))) {
                    putFile(stream, "test", "MOCK_COPY" + i + ".csv");
                }
                i++;
                fileContent.setLength(0);
                current = 0;
                fileContent.append(line).append("\n");
            }
        }

        // 处理最后一批不足100行的内容
        if (fileContent.length() > 0) {
            System.out.println("Preparing to upload final split file " + i);
            try (InputStream is2 = IOUtils.toInputStream(fileContent.toString(), StandardCharsets.UTF_8.name())) {
                putFile(is2, "test", "MOCK_COPY" + i + ".csv");
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    } finally {
        try {
            // 按顺序关闭资源
            if (reader != null) reader.close();
            if (is != null) is.close();
            // 关键:完成下载命令,释放连接通道
            if (ftpClient.isConnected()) {
                ftpClient.completePendingCommand();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return true;
}

2. 修正连接方法:修复端口赋值错误

public void connectToServer(String server, String user, String pass, Integer port) throws WebApplicationException {
    // 正确逻辑:如果port为null用21,否则用传入的port
    port = port == null ? 21 : port;
    server = server.replace("ftp://", "");
    try {
        ftpClient = new FTPClient();
        ftpClient.connect(server, port);
        ftpClient.login(user, pass);
        ftpClient.enterLocalPassiveMode();
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
        System.out.println("Connected to ftp");
    } catch (IOException e) {
        e.printStackTrace();
        throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND)
                .entity("FTP error: " + e.getMessage()).type(MediaType.TEXT_PLAIN).build());
    }
}

3. 修正上传方法:增加OutputStream判空+完成上传命令

public Boolean putFile(InputStream inputStream, final String filePath, final String fileName) throws IOException {
    Long ts = System.currentTimeMillis();
    String remoteFile = filePath + "/" + fileName;
    System.out.println("Start uploading file: " + remoteFile);

    OutputStream outputStream = ftpClient.storeFileStream(remoteFile);
    // 判空:避免上传失败时的空指针
    if (outputStream == null) {
        System.err.println("Failed to get upload stream for: " + remoteFile);
        inputStream.close();
        return false;
    }

    byte[] bytesIn = new byte[4096];
    int read = 0;
    while ((read = inputStream.read(bytesIn)) != -1) {
        outputStream.write(bytesIn, 0, read);
    }
    inputStream.close();
    outputStream.close();

    // 完成上传命令,确认服务器接收成功
    if (!ftpClient.completePendingCommand()) {
        System.err.println("Upload failed for file: " + remoteFile);
        return false;
    }

    System.out.println("The file is uploaded successfully in " + (System.currentTimeMillis() - ts) / 1000 + "s");
    return true;
}

内容的提问来源于stack exchange,提问作者user1974566

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:52:38