前面记录到从FTP服务器下载到浏览器,使用缓冲流也没有提升速度。
现在想要通过多线程并行下载看能不能提升,将文件拆分,每个线程负责下载一部分文件到输出流。
每个线程不能共用一个输入流,目前试过是不好控制,会导致下载的数据漏掉一部分。我的做法是在每个线程里面都创建要下载的文件输入流,每个线程的输入流是一样的。
@Override
// 下载文件,使用缓冲流 + 多线程
public void downloadFiles(String filePath, HttpServletResponse response) {
String[] split = filePath.split("/");
String dirPath = split[1];
String fileName = split[2];
long fileSize = 0L;
FTPClient ftpClient = ftpPoolService.borrowObject();
try {
ftpClient.changeWorkingDirectory(dirPath);
fileSize = Long.parseLong(ftpClient.getSize(fileName));
} catch (IOException e) {
e.printStackTrace();
}
// 设置响应头,告诉浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
try (
ServletOutputStream out = response.getOutputStream();
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out)
) {
int threadNums = 3;
// 线程池任务计数器
CountDownLatch countDownLatch = new CountDownLatch(threadNums);
// 计算每个线程下载的字节数
long chunkSize = fileSize / threadNums;
long startByte = 0;
long endByte = chunkSize - 1;
for (int i = 0; i < threadNums; i++) {
if (i == threadNums - 1) {
// 最后一个线程下载剩余的部分
endByte = fileSize;
}
executor.execute(new DownloadFileTask(startByte, endByte, bufferedOutputStream, countDownLatch, ftpPoolService, fileName));
startByte = endByte + 1;
endByte += chunkSize;
}
// 等待所有任务完成
countDownLatch.await();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
ftpPoolService.returnObject(ftpClient);
}
}
package com.lin.thread.task;
import com.lin.ftp.service.FTPPoolService;
import org.apache.commons.net.ftp.FTPClient;
import java.io.*;
import java.util.concurrent.CountDownLatch;
public class DownloadFileTask implements Runnable{
private long startByte;// 输入流开始读取位置
private long endByte;// 输入流结束读取位置
private long bytes;// 当前线程要写入的字节大小
private OutputStream outputStream;// 要写入的输出流
private CountDownLatch countDownLatch;// 线程池任务计数器
private FTPPoolService ftpPoolService;// ftp服务
private String fileName;// 要下载的文件名
public DownloadFileTask() {
}
public DownloadFileTask(long startByte, long endByte, BufferedOutputStream outputStream, CountDownLatch countDownLatch,FTPPoolService ftpPoolService,String fileName) {
this.startByte = startByte;
this.endByte = endByte;
this.outputStream = outputStream;
this.countDownLatch = countDownLatch;
this.ftpPoolService = ftpPoolService;
this.fileName = fileName;
this.bytes = endByte - startByte;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + startByte + "---" + endByte);
byte[] buffer = new byte[2048];
int bytesRead = 0;
long writtenBytes = 0;
int remainingBytes = 0;
FTPClient ftpClient = ftpPoolService.borrowObject();
try {
ftpClient.changeWorkingDirectory("files");
} catch (IOException e) {
e.printStackTrace();
}
try(InputStream inputStream = ftpClient.retrieveFileStream(fileName);
BufferedInputStream bis = new BufferedInputStream(inputStream)) {
// 输入流跳到开始下载的位置
bis.skip(startByte);
while ((bytesRead = bis.read(buffer)) != -1) {
// 达到下载范围的末尾,停止写入
if (writtenBytes + bytesRead > bytes) {
remainingBytes = (int) (bytes - writtenBytes);
outputStream.write(buffer, 0, remainingBytes);
break;
}
outputStream.write(buffer, 0, bytesRead);
writtenBytes += bytesRead;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
ftpClient.completePendingCommand();
} catch (IOException e) {
e.printStackTrace();
}
ftpPoolService.returnObject(ftpClient);
}
System.out.println(Thread.currentThread().getName() + "写入大小:" + writtenBytes + ". 总的要写:" + bytes + ". 多出写入:" + remainingBytes);
countDownLatch.countDown();
}
}