最近有个任务是根据获取到的地址一批url,打包成一个zip文件下载。
这里有两个考虑:
- 不创建本来临时文件,临时文件创建涉及到临时目录,文件的创建删除等操作,吃力不讨好。
- 整个过程尽可能节省时间及内存空间
基于上面两个考虑,整个过程都在流缓冲区,这时考虑到NIO的Pipe管道。
为了不使管道中数据过大,读满一个缓冲区,就写出流数据。
1. 导出包裹对象
FileExport 是一个包裹对象,包含了文件全称,以及路径地址
@Data
@NoArgsConstructor
public class FileExport {
/**
* 文件名包含后缀
*/
private String filename;
/**
* 文件地址
*/
private String url;
public FileExport(String filename, String url) {
this.filename = filename;
this.url = url;
}
}
2. 导出函数
@Slf4j
public class ZipExport {
/**
* 缓冲区大小
*/
private final static int BYTE_SIZE = 130 * 1024;
/**
* 导出zip文件
* @param response
* @param fileList 要导出的文件集合
*/
public static void exportZipFile(HttpServletResponse response,String exportFileName ,List<FileExport> fileList) throws UnsupportedEncodingException {
if (CollectionUtils.isEmpty(fileList)) {
return;
}
response.setContentType("application/octet-stream;charset=UTF-8");
response.setCharacterEncoding(GlobalConstant.Sys.UTF8);
// 防止中文乱码
String fileName = URLEncoder.encode(exportFileName.concat(".zip"), GlobalConstant.Sys.UTF8);
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
try (WritableByteChannel out = Channels.newChannel(response.getOutputStream())) {
Pipe pipe = Pipe.open();
// 异步任务,这里是为了边写边读
CompletableFuture.runAsync(() -> runTask(pipe, fileList));
//获取读通道
ReadableByteChannel readableByteChannel = pipe.source();
ByteBuffer buffer = ByteBuffer.allocate(BYTE_SIZE);
while (readableByteChannel.read(buffer) >= 0) {
buffer.flip();
out.write(buffer);
buffer.clear();
}
} catch (Exception e) {
log.error("导出压缩文件异常:{}", e.getMessage());
}
}
/**
* 将获取的文件写入管道
* @param pipe 管道
* @param fileList 要导出的文件集合
*/
public static void runTask(Pipe pipe, List<FileExport> fileList) {
ReadableByteChannel readableByteChannel = null;
// 预估可能的文件大小
ByteBuffer buffer = ByteBuffer.allocate(BYTE_SIZE);
URL url;
try (ZipOutputStream zos = new ZipOutputStream(Channels.newOutputStream(pipe.sink()));
WritableByteChannel out = Channels.newChannel(zos)) {
for (FileExport fileExport : fileList) {
zos.putNextEntry(new ZipEntry(fileExport.getFilename()));
url = new URL(fileExport.getUrl());
readableByteChannel = Channels.newChannel(url.openStream());
while (readableByteChannel.read(buffer) > 0) {
buffer.flip();
out.write(buffer);
buffer.clear();
}
readableByteChannel.close();
}
} catch (Exception e) {
log.error("添加压缩文件异常:{}", e.getMessage());
} finally {
if (readableByteChannel != null) {
try {
readableByteChannel.close();
} catch (IOException e) {
log.error("关闭网络文件流异常:{}", e.getMessage());
}
}
}
}
}