0、写在前面的话

图片批量下载,要求下载时集成为一个压缩包进行下载。从昨天下午折腾到现在,踩坑踩得莫名其妙,还是来唠唠,给自己留个印象的同时,也希望给需要用到这个方法的人带来一些帮助。

1、先叨叨IO

叨叨IO是因为网络传输无非也就是流的传递,所以下载文件到本地的话实际上也是IO的东西,这个和读取本地文件然后写入到本地另一个文件的操作是基本一样的。

我在自己IO基础的博客中(《[03] 节点流和处理流》)其实也有提到示例,拿复写文件来说,大概是如下过程:
 
对于读取文件(不仅仅是文本)到服务器内存,常见的是通过InputStream读取File,所以你可能也经常看到如下类似的代码:
outputStream = new FileOutputStream(file);
byte[] temp = new byte[1024];
int size = -1;
while ((size = inputStream.read(temp)) != -1) { // 每次读取1KB,直至读完
outputStream.write(temp, 0, size);
}
 
1
outputStream = new FileOutputStream(file);          
2
byte[] temp = new byte[1024];
3
int size = -1;
4
while ((size = inputStream.read(temp)) != -1) { // 每次读取1KB,直至读完
5
    outputStream.write(temp, 0, size);
6
}

我这里想说的是,不论何种形式,只需要知道的是,在写出之前,要获取将写出的数据,这个数据常常是作为byte[ ]类型的。

同时,因为是写出到本地文件,所以这里图示中的OutputStream无非应该是使用FileOutputStream罢了。那么以此来类比网络传输下载的话,OutputStream显然就替换成响应HttpServletResponse中的OutputStream就可以了。本质都是输出流,不同的类型决定你输出的方式等。

2、再叨叨ajax

当你把文件数据的二进制放到了响应的流中,也确实在响应中返回了,可是浏览器就是不争气,不给面子,不启动下载。这个时候,你要看看,你发送请求的方式,是否采取了ajax请求。如下图采用ajax请求资源,确实收到了流信息,但是反馈在浏览器上却什么也没发生:
 
原因在于,ajax的返回值类型是json/text/html/xml类型,或者可以说ajax的发送,接受都只能是string字符串,不能流类型,所以无法实现文件下载,强用会出现response冲突。

但用ajax仍然可以获得文件的内容,该文件将被保留在内存中,无法将文件保存到磁盘。这是因为js无法和磁盘进行交互,否则这会是一个严重的安全问题,js无法调用到浏览器的下载处理机制和程序,会被浏览器阻塞。

所以在前端,简单一点,使用 window.location.href 的方式访问url,实现下载。

3、正儿八经的批量下载实现

下载前,因为批量下载需要打包为压缩包,所以要用到一个三方jar,maven地址如下:
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.6.5</version>
</dependency>
5
 
1
<dependency>
2
  <groupId>ant</groupId>
3
  <artifactId>ant</artifactId>
4
  <version>1.6.5</version>
5
</dependency>

先贴代码,然后再进行说明:
/**
* 批量下载
*
* @param idxs 图片的id拼接字符串,用逗号隔开
*/
public String downloadBatch(String idxs) {
String[] ids = idxs.split(",");
try {
HttpServletResponse response = ServletActionContext.getResponse();
OutputStream out = setDownloadOutputStream(response, String.valueOf(new Date().getTime()), "zip");
ZipOutputStream zipOut = new ZipOutputStream(out); for (int i = 0; i < ids.length; i++) {
Picture picture = Picture.get(Picture.class, Long.parseLong(ids[i]));
byte[] data = picture.getData();
zipOut.putNextEntry(new ZipEntry(i + "_" + picture.getName() + "." + picture.getFormat().getValue()));
writeBytesToOut(zipOut, data, BUFFER_SIZE);
zipOut.closeEntry();
}
zipOut.flush();
zipOut.close(); } catch (IOException e) {
e.printStackTrace();
log.warn("下载失败:" + e.getMessage());
} return null;
}
x
 
1
/**
2
 * 批量下载
3
 *
4
 * @param idxs 图片的id拼接字符串,用逗号隔开
5
 */
6
public String downloadBatch(String idxs) {
7
    String[] ids = idxs.split(",");
8
    try {
9
        HttpServletResponse response = ServletActionContext.getResponse();
10
        OutputStream out = setDownloadOutputStream(response, String.valueOf(new Date().getTime()), "zip");
11
        ZipOutputStream zipOut = new ZipOutputStream(out);
12

13
        for (int i = 0; i < ids.length; i++) {
14
            Picture picture = Picture.get(Picture.class, Long.parseLong(ids[i]));
15
            byte[] data = picture.getData();
16
            zipOut.putNextEntry(new ZipEntry(i + "_" + picture.getName() + "." + picture.getFormat().getValue()));
17
            writeBytesToOut(zipOut, data, BUFFER_SIZE);
18
            zipOut.closeEntry();
19
        }
20
        zipOut.flush();
21
        zipOut.close();
22

23
    } catch (IOException e) {
24
        e.printStackTrace();
25
        log.warn("下载失败:" + e.getMessage());
26
    }
27

28
    return null;
29
}

/**
* 设置文件下载的response格式
*
* @param response 响应
* @param fileName 文件名称
* @param fileType 文件类型
* @return 设置后响应的输出流OutputStream
* @throws IOException
*/
private static OutputStream setDownloadOutputStream(HttpServletResponse response, String fileName, String fileType) throws IOException {
fileName = new String(fileName.getBytes(), "ISO-8859-1");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + "." + fileType);
response.setContentType("multipart/form-data");
return response.getOutputStream();
}
15
 
1
/**
2
 * 设置文件下载的response格式
3
 *
4
 * @param response 响应
5
 * @param fileName 文件名称
6
 * @param fileType 文件类型
7
 * @return 设置后响应的输出流OutputStream
8
 * @throws IOException
9
 */
10
private static OutputStream setDownloadOutputStream(HttpServletResponse response, String fileName, String fileType) throws IOException {
11
    fileName = new String(fileName.getBytes(), "ISO-8859-1");
12
    response.setHeader("Content-Disposition", "attachment;filename=" + fileName + "." + fileType);
13
    response.setContentType("multipart/form-data");
14
    return response.getOutputStream();
15
}

/**
* 将byte[]类型的数据,写入到输出流中
*
* @param out 输出流
* @param data 希望写入的数据
* @param cacheSize 写入数据是循环读取写入的,此为每次读取的大小,单位字节,建议为4096,即4k
* @throws IOException
*/
private static void writeBytesToOut(OutputStream out, byte[] data, int cacheSize) throws IOException {
int surplus = data.length % cacheSize;
int count = surplus == 0 ? data.length / cacheSize : data.length / cacheSize + 1;
for (int i = 0; i < count; i++) {
if (i == count - 1 && surplus != 0) {
out.write(data, i * cacheSize, surplus);
continue;
}
out.write(data, i * cacheSize, cacheSize);
}
}
 
1
/**
2
 * 将byte[]类型的数据,写入到输出流中
3
 *
4
 * @param out 输出流
5
 * @param data 希望写入的数据
6
 * @param cacheSize 写入数据是循环读取写入的,此为每次读取的大小,单位字节,建议为4096,即4k
7
 * @throws IOException
8
 */
9
private static void writeBytesToOut(OutputStream out, byte[] data, int cacheSize) throws IOException {
10
    int surplus = data.length % cacheSize;
11
    int count = surplus == 0 ? data.length / cacheSize : data.length / cacheSize + 1;
12
    for (int i = 0; i < count; i++) {
13
        if (i == count - 1 && surplus != 0) {
14
            out.write(data, i * cacheSize, surplus);
15
            continue;
16
        }
17
        out.write(data, i * cacheSize, cacheSize);
18
    }
19
}

第一段代码为下载的主方法,用到了两个子方法,分别贴在之后的第二段代码和第三段代码。

文件的下载其实很简单,刚才在叨叨IO中也提到了,所以对于网络传输下载的IO来说,整体也就三个步骤:
  • 设置文件ContentType类型和文件头
  • 读取文件数据为byte[]
  • 将数据写入到响应response的输出流中

设置请求头信息,在方法 setDownloadOutputStream() 中已经写明了,是文件所以要告知文件处理应该为 attachement,并附上文件名(转码ISO-8859-1避免中文乱码)。而文件内容的类型,统一设置为 multipart/form-data 即可,交给浏览器自行判断下载的文件类型。

读取文件数据,因为本例中我的图片数据直接存储在数据库字段中,所以取出时直接获取的就是byte[]。如果你的方式是文件存放在本地,数据库只是存储了文件的物理地址,那么你得多做做操作,用FileInputStream把文件的内容先读出来,结果和目的都是一样的,就是获取文件的byte[]。

获得了数据,那么直接通过OutputStream的write方法写入即可。(这里的写入方法我用了循环写入,当初是想着避免内存紧张,可是现在回过头来想不对啊,读文件的时候才吃内存的,这里我已经读完了再写,循环与否已经不重要了。所以实际上应该是边读边写,才是良性的,可是我的byte[]存在数据库字段里,取出来时就全部读入到内存中了,所以这里实际上是不需要循环写入的,我这是画蛇添足了。另外,如果是从File读的话,则边读边写,见目录1叨叨IO中的小段代码)

写入到输出流了,flush()刷新一下,即可。

上面这些是对于文件下载通用的,如果是批量压缩包形式,在第一段代码中黄色部分,来进行重点说明:
  • ZipOutputStream zipOut = new ZipOutputStream(out);
  • //把响应的输出流包装一下而已,便于使用相关压缩方法,就像多了个包装袋

  • zipOut.putNextEntry(new ZipEntry(i + "_" + picture.getName() + "." + picture.getFormat().getValue()));
  • zipOut.closeEntry();
  • //压缩包中的多个文件,实际上每个就是这里的ZipEntry对象,每开始写入某个文件的内容时,必须先putNextEntry(new ZipEntry(String fileName)),然后才可以写入,写完这个文件,必须使用closeEntry()说明,已经写完了第一个文件。就好像putNextEntry是在说 “我要开始写压缩包的下一个文件啦”,而closeEntry则是在说“压缩包里的这个文件我已经写完啦”。循环反复,最终把所有文件写入这个“披着压缩输出流外壳的响应输出流”

  • zipOut.flush();
  • 写入完成后,刷一下即可。就像去超市买东西,购物车装好了,总得结一次帐才能把东西拿走吧。当然,最后别忘了close关闭。

4、参考链接



批量下载,多文件压缩打包zip下载的更多相关文章

  1. 打包zip下载

    //首先引入的文件为org.apache的切记不是jdk的import org.apache.tools.zip.ZipOutputStream;import org.apache.tools.zip ...

  2. java实现将文件压缩成zip格式

    以下是将文件压缩成zip格式的工具类(复制后可以直接使用): zip4j.jar包下载地址:http://www.lingala.net/zip4j/download.php package util ...

  3. Web端文件打包.zip下载

    使用ant.jar包的API进行文件夹打包.直接上代码: String zipfilename = "test.zip"; File zipfile = new File(zipf ...

  4. Linux文件压缩/打包/解压

    在Linux日常维护中,经常需要备份同步一些比较重要的文件,而在传输过程中如果文件比较大往往会非常慢,而且还会非常占用空间,这时候就需要我们使用压缩工具对大文件进行压缩打包,下面我们来介绍一下常用的压 ...

  5. Linux中的文件压缩,打包和备份命令

    压缩解压命令 gzip  文件   -c : 将压缩数据输出到屏幕,可用来重定向 -v   显示压缩比等信息 -d   解压参数 -t    用来检验一个压缩文件的一致性看看档案有没错 -数字 : 压 ...

  6. 文件压缩:zip

    [root@localhost ~]# yum install -y zip unzip // 安装 zip 和 unzip [root@localhost ~]# ..txt // 压缩文件,要同时 ...

  7. Java实现多文件压缩打包的方法

    package com.biao.test; import java.io.File; import java.io.FileInputStream; import java.io.FileOutpu ...

  8. [转]C#如何把文件夹压缩打包然后下载

    public partial class _Default2 : System.Web.UI.Page{ protected void Page_Load(object sender, EventAr ...

  9. Java Springboot 根据图片链接生成图片下载链接 及 多个图片打包zip下载链接

    现有一些图片在服务器上的链接,在浏览器中打开这些链接是直接显示在浏览器页面的形式. 现在需要生成这些图片的单独下载以及打包下载链接,即在浏览器中打开下载链接后弹出下载框提示下载.由于前端存在跨域问题, ...

随机推荐

  1. js 复制粘贴

    input输入框<div id="top-title" style="position: relative"> <img class=&quo ...

  2. 学习MVC之租房网站(十二)-缓存和静态页面

    在上一篇<学习MVC之租房网站(十一)-定时任务和云存储>学习了Quartz的使用.发邮件,并将通过UEditor上传的图片保存到云存储.在项目的最后,再学习优化网站性能的一些技术:缓存和 ...

  3. 安卓基础之Sqlite数据库最最基础操作

    Sqlite数据库基础操作 摘要:在应用中新建一个数据库,并创建一个数据表写入数据,然后读取表中数据并展示. 主要逻辑: 1.通过继承SQLiteOpenHelper自定义类,定制数据库的表结构,初始 ...

  4. 关于通过ServletContext获取数据出现的http500的错误的解决方案

    1.问题的简述 我创建了一个两个servlet以及一个jsp页面,假定给两个servlet分别命名(初始化数据servlet)和(数据处理servlet),jsp页面用于传递数据至数据处理servle ...

  5. Win10家庭版、专业版、企业版、教育版各版本功能区别对照表

    关于Win10系统的版本问题,MS酋长之前曾经分享过Windows10有哪些版本,在这篇文章中简单地介绍了一下Win10各版本的功能区别及适宜用户群,但是并没有对各版本的功能区别做一详细的对比.日前微 ...

  6. Oracle EBS OPM 子库存转移

    --子库存转移 --created by jenrry DECLARE l_mtl_txn_rec mtl_transactions_interface%ROWTYPE; l_mtl_txn_lot_ ...

  7. SQL删除多列语句

    最近在写SQL过程中发现需要对一张表结构作调整(此处是SQL Server),其中需要删除多列,由于之前都是一条SQL语句删除一列,于是猜想是否可以一条语句同时删除多列,如果可以,怎么写法? 第一次猜 ...

  8. [cb]NGUI事件及复杂UI管理

    事件管理 看了有些文章关于NGUI的事件管理,许多人的做法的是封装一个事件处理层,避免在每个UI控件上都绑定事件处理脚本.本文说说我们项目中的UI事件管理吧. UIEventListener 我们项目 ...

  9. October 20th 2017 Week 42nd Friday

    My life is in these books. Read these and know my heart. 我的人生就在这些书中,读完他们就能读懂我的心. Some people say tha ...

  10. 折射向量计算(Refraction Vector Calculation)

    上个月学习Peter Shirley-Ray Tracing in One Weekend的系列三本书,收获真的很多.这个系列的书真的是手把手教你如何从零开始构建一个光线跟踪渲染器,对新手(像我)非常 ...