从FTP服务器下载文件

最近写了一个接口,客户端浏览器请求,从FTP服务器下载文件,现在达成的指标是88.6M的文件,请求需要12s,不知道这个指标算不算正常。记录一下。

public void downloadFiles(String filePath, HttpServletResponse response) {
        String[] split = filePath.split("/");
        String dirPath = split[1];
        String fileName = split[2];

        FTPClient ftpClient = ftpPoolService.borrowObject();
        try {
            ftpClient.changeWorkingDirectory(dirPath);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 设置响应头,告诉浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=" + fileName);

        try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
             ServletOutputStream out = response.getOutputStream();
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)){


            // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
            byte[] buffer = new byte[2048];// 缓冲区
            int bytesRead;
            long start2 = System.currentTimeMillis();
            while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
                bufferedOutputStream.write(buffer, 0, bytesRead);
            }
            long end2 = System.currentTimeMillis();
            System.out.println("循环耗时:" + (end2 - start2));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                ftpClient.completePendingCommand();
                ftpPoolService.returnObject(ftpClient);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

下载文件耗时地方:

// 从输入流中读取数据,并写入响应输出流中
byte[] buffer = new byte[10240];// 缓冲区
int bytesRead;
long start2 = System.currentTimeMillis();
while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
    bufferedOutputStream.write(buffer, 0, bytesRead);
}
long end2 = System.currentTimeMillis();
System.out.println("循环耗时:" + (end2 - start2));

循环将文件输入流写到输出流中最耗时。

1 InputStream inputStream = ftpClient.retrieveFileStream(fileName); 从ftp服务器获取一个InputStream 对象, 该流对象用于读取ftp服务器文件的数据, 这个过程设计磁盘的IO操作, 因为ftp服务器文件是存储在磁盘上, 当通过流对象读取文件时, 就涉及到磁盘的IO操作。

2 bufferedInputStream.read(buffer) 这个就是通过一个流对象从FTP服务器中读取数据, 涉及到磁盘的IO操作.

3 bufferedOutputStream.write(buffer, 0, bytesRead); 将读取到的数据(字节)写入到输出流, 将数据写入了 ServletOutputStream,而不是直接写入磁盘。这个操作主要涉及到将数据从内存(字节数组 buffer)写入到输出流中,以便将数据发送到客户端浏览器。 客户端浏览器收到数据后,会根据响应头中的信息进行处理,例如下载文件或显示内容。 ServletOutputStream 流连接到浏览器,用于传输数据。

这个过程跟网络带宽有关,单位时间内传输的数据量。

这个过程中,数据被写入 BufferedOutputStream 的缓冲区,当缓冲区满了或者 flush() 被调用时,数据将被发送到底层的 ServletOutputStream,最终传递给客户端浏览器。这样的设计可以提高性能,因为它减少了直接写入底层流的次数,而是通过缓冲区进行批量写入。如果没有缓冲区,数据会直接写入到ServletOutputStream流中。

是否使用输出缓冲流差异

0.99 GB文件

不使用下载文件耗时:135265

try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
             ServletOutputStream out = response.getOutputStream();
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)
        ){


            // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
            byte[] buffer = new byte[2048];
            int bytesRead;
            long start2 = System.currentTimeMillis();
            while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
                out .write(buffer, 0, bytesRead);
            }
            long end2 = System.currentTimeMillis();
            System.out.println("循环耗时:" + (end2 - start2));

使用下载文件耗时:134810

try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
             ServletOutputStream out = response.getOutputStream();
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)
        ){


            // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
            byte[] buffer = new byte[2048];
            int bytesRead;
            long start2 = System.currentTimeMillis();
            while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
                bufferedOutputStream.write(buffer, 0, bytesRead);
            }
            long end2 = System.currentTimeMillis();
            System.out.println("循环耗时:" + (end2 - start2));

查阅资料好像是说ServletOutputStream 本身就是一种输出缓冲流,它可能在内部实现了缓冲机制。因此,添加额外的 BufferedOutputStream 可能没有太大的性能提升,因为在网络传输的过程中,底层的缓冲机制已经存在。那么现在添加输出缓冲流不是一个优化方案。

是否使用输入缓冲流差异

200.3M文件

不使用下载文件耗时:135482

try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
             ServletOutputStream out = response.getOutputStream();
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)
        ){


            // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
            byte[] buffer = new byte[2048];
            int bytesRead;
            long start2 = System.currentTimeMillis();
            while ((bytesRead = inputStream .read(buffer)) != -1) {
                bufferedOutputStream.write(buffer, 0, bytesRead);
            }
            long end2 = System.currentTimeMillis();
            System.out.println("循环耗时:" + (end2 - start2));

使用下载文件耗时:134810

try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
             ServletOutputStream out = response.getOutputStream();
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)
        ){


            // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
            byte[] buffer = new byte[2048];
            int bytesRead;
            long start2 = System.currentTimeMillis();
            while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
                bufferedOutputStream.write(buffer, 0, bytesRead);
            }
            long end2 = System.currentTimeMillis();
            System.out.println("循环耗时:" + (end2 - start2));

BufferedInputStream 的原理

通过预先读入一整段原始输入流数据至缓冲区(默认是8KB)中,而外界对BufferedInputStream的读取操作实际上是在缓冲区上进行,如果读取的数据超过了缓冲区的范围,那么BufferedInputStream负责重新从原始输入流中载入下一截数据填充缓冲区,然后外界继续通过缓冲区进行数据读取。

这样的设计的好处是:避免了大量的磁盘IO,因为原始的InputStream类实现的read是即时读取的,即每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),可想而知,如果数据量巨大,这样的磁盘消耗非常可怕。而通过缓冲区的实现,读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO,载入一段数据填充缓冲,那么下一次读取一般情况下就直接可以从缓冲区读取,减少了磁盘IO。

总结

使用缓冲流好像对于读和写都没有很大的提升,不知道原因在哪里,有知道的朋友评论区一起讨论。

你可能感兴趣的:(Java,服务器)