• 从FTP服务器下载文件


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

    1. public void downloadFiles(String filePath, HttpServletResponse response) {
    2. String[] split = filePath.split("/");
    3. String dirPath = split[1];
    4. String fileName = split[2];
    5. FTPClient ftpClient = ftpPoolService.borrowObject();
    6. try {
    7. ftpClient.changeWorkingDirectory(dirPath);
    8. } catch (IOException e) {
    9. e.printStackTrace();
    10. }
    11. // 设置响应头,告诉浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载
    12. response.setContentType("application/octet-stream");
    13. response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
    14. try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
    15. BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
    16. ServletOutputStream out = response.getOutputStream();
    17. BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)){
    18. // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
    19. byte[] buffer = new byte[2048];// 缓冲区
    20. int bytesRead;
    21. long start2 = System.currentTimeMillis();
    22. while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
    23. bufferedOutputStream.write(buffer, 0, bytesRead);
    24. }
    25. long end2 = System.currentTimeMillis();
    26. System.out.println("循环耗时:" + (end2 - start2));
    27. } catch (IOException e) {
    28. e.printStackTrace();
    29. } finally {
    30. try {
    31. ftpClient.completePendingCommand();
    32. ftpPoolService.returnObject(ftpClient);
    33. } catch (IOException e) {
    34. e.printStackTrace();
    35. }
    36. }
    37. }

    下载文件耗时地方:

    1. // 从输入流中读取数据,并写入响应输出流中
    2. byte[] buffer = new byte[10240];// 缓冲区
    3. int bytesRead;
    4. long start2 = System.currentTimeMillis();
    5. while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
    6.    bufferedOutputStream.write(buffer, 0, bytesRead);
    7. }
    8. long end2 = System.currentTimeMillis();
    9. 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

    1. try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
    2. BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
    3. ServletOutputStream out = response.getOutputStream();
    4. BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)
    5. ){
    6. // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
    7. byte[] buffer = new byte[2048];
    8. int bytesRead;
    9. long start2 = System.currentTimeMillis();
    10. while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
    11. out .write(buffer, 0, bytesRead);
    12. }
    13. long end2 = System.currentTimeMillis();
    14. System.out.println("循环耗时:" + (end2 - start2));

    使用下载文件耗时:134810

    1. try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
    2. BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
    3. ServletOutputStream out = response.getOutputStream();
    4. BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)
    5. ){
    6. // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
    7. byte[] buffer = new byte[2048];
    8. int bytesRead;
    9. long start2 = System.currentTimeMillis();
    10. while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
    11. bufferedOutputStream.write(buffer, 0, bytesRead);
    12. }
    13. long end2 = System.currentTimeMillis();
    14. System.out.println("循环耗时:" + (end2 - start2));

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

    是否使用输入缓冲流差异

    200.3M文件

    不使用下载文件耗时:135482

    1. try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
    2. BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
    3. ServletOutputStream out = response.getOutputStream();
    4. BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)
    5. ){
    6. // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
    7. byte[] buffer = new byte[2048];
    8. int bytesRead;
    9. long start2 = System.currentTimeMillis();
    10. while ((bytesRead = inputStream .read(buffer)) != -1) {
    11. bufferedOutputStream.write(buffer, 0, bytesRead);
    12. }
    13. long end2 = System.currentTimeMillis();
    14. System.out.println("循环耗时:" + (end2 - start2));

    使用下载文件耗时:134810

    1. try (InputStream inputStream = ftpClient.retrieveFileStream(fileName);
    2. BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
    3. ServletOutputStream out = response.getOutputStream();
    4. BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)
    5. ){
    6. // 从输入流中读取数据,并写入响应输出流中。从ftp服务器传输数据到我的服务器
    7. byte[] buffer = new byte[2048];
    8. int bytesRead;
    9. long start2 = System.currentTimeMillis();
    10. while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
    11. bufferedOutputStream.write(buffer, 0, bytesRead);
    12. }
    13. long end2 = System.currentTimeMillis();
    14. System.out.println("循环耗时:" + (end2 - start2));

    BufferedInputStream 的原理

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

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

    总结

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

  • 相关阅读:
    PyCharm下载安装及配置使用教程(全过程详细截图)
    蓝桥杯python组--基础训练---求输入的第n个,斐波那契数
    RISC-V架构——物理内存保护机制设置函数(pmp_set)解析
    Linux进程和进程优先级(top+ps+kill+renice+df+free命令)
    neo4j关系文字不显示
    导出数据为zip压缩文件,解压后为json文件
    [二进制漏洞]栈(Stack)溢出漏洞 Linux篇
    Taurus.MVC 微服务框架 入门开发教程:项目部署:3、微服务应用程序版本升级:全站升级和局部模块升级。
    Mac 下生成core dump
    Python飞行棋游戏源代码,基于socket网络通信的小游戏,可设置多个游戏房间及参与飞行棋游戏的玩家
  • 原文地址:https://blog.csdn.net/weixin_52938172/article/details/134443457