最近写了一个接口,客户端浏览器请求,从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));
通过预先读入一整段原始输入流数据至缓冲区(默认是8KB)中,而外界对BufferedInputStream的读取操作实际上是在缓冲区上进行,如果读取的数据超过了缓冲区的范围,那么BufferedInputStream负责重新从原始输入流中载入下一截数据填充缓冲区,然后外界继续通过缓冲区进行数据读取。
这样的设计的好处是:避免了大量的磁盘IO,因为原始的InputStream类实现的read是即时读取的,即每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),可想而知,如果数据量巨大,这样的磁盘消耗非常可怕。而通过缓冲区的实现,读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO,载入一段数据填充缓冲,那么下一次读取一般情况下就直接可以从缓冲区读取,减少了磁盘IO。
使用缓冲流好像对于读和写都没有很大的提升,不知道原因在哪里,有知道的朋友评论区一起讨论。