Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/380.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在WebApp中创建和下载巨大ZIP(从几个blob)的最佳实践_Java_Web Applications_Filesystems_Zip - Fatal编程技术网

Java 在WebApp中创建和下载巨大ZIP(从几个blob)的最佳实践

Java 在WebApp中创建和下载巨大ZIP(从几个blob)的最佳实践,java,web-applications,filesystems,zip,Java,Web Applications,Filesystems,Zip,我需要从我的Web应用程序执行大量文件下载 显然,这是一个长期运行的操作(它将每年使用一次[-每个客户]),因此时间不是问题(除非它达到某个超时,但我可以通过创建某种形式的keepalive心跳来处理)。我知道如何创建隐藏的iframe,并将其与内容处置:附件一起使用,以尝试下载文件,而不是在浏览器中打开文件,以及如何为绘制进度表引用客户端-服务器通信 下载的实际大小(和文件的数量)是未知的,但为了简单起见,我们实际上可以将其视为1GB,由100个文件组成,每个10MB。 因为这应该是一个一键操

我需要从我的Web应用程序执行大量文件下载

显然,这是一个长期运行的操作(它将每年使用一次[-每个客户]),因此时间不是问题(除非它达到某个超时,但我可以通过创建某种形式的keepalive心跳来处理)。我知道如何创建隐藏的iframe,并将其与
内容处置:附件
一起使用,以尝试下载文件,而不是在浏览器中打开文件,以及如何为绘制进度表引用客户端-服务器通信

下载的实际大小(和文件的数量)是未知的,但为了简单起见,我们实际上可以将其视为1GB,由100个文件组成,每个10MB。 因为这应该是一个一键操作,所以我的第一个想法是在从数据库读取文件时,将所有文件分组到动态生成的ZIP中,然后要求用户保存ZIP

问题是:在一个WebApp中从多个小字节数组创建大型存档时,最佳做法是什么,已知的缺点和陷阱是什么?

可随机分为:

  • 每个字节数组应该在物理临时文件中转换,还是可以添加到内存中的ZIP
  • 如果是,我知道我必须处理名称可能相等的问题(它们可以在数据库中的不同记录中具有相同的名称,但不能在同一文件系统或ZIP中):是否会想到任何其他可能的问题(假设文件系统始终有足够的物理空间)
  • 由于我不能依靠内存中有足够的RAM来执行整个操作,我想应该先创建ZIP文件,然后再将其发送给用户;有没有什么不同的方法(例如使用websocket),比如询问用户将文件保存在哪里,然后启动从服务器到客户端的恒定数据流(我想是科幻小说)
  • 如您有任何其他相关已知问题或最佳实践,我们将不胜感激

可能是您想同时尝试多个下载。我在这里找到了一个与此相关的讨论-


希望这有帮助。

对于无法立即放入内存的大型内容,请将内容从数据库流式传输到响应

这种事情其实很简单。您不需要AJAX或WebSocket,可以通过用户单击的简单链接来流式下载大型文件。而且现代浏览器有自己的进度条,有像样的下载管理器——为什么要重新发明轮子呢

如果从头开始为此编写servlet,则访问数据库BLOB,获取其输入流并将内容复制到HTTP响应输出流。如果您有ApacheCommons IO库,您可以使用,否则您可以自己做

动态创建ZIP文件可以通过以下步骤完成。通过响应输出流(从servlet或框架提供的任何内容)创建其中一个BLOB,然后从数据库中获取每个BLOB,首先使用
putNextEntry()
,然后按照前面的描述对每个BLOB进行流式处理

潜在陷阱/问题:

  • 根据下载大小和网络速度,完成请求可能需要很多时间。防火墙等可以阻止这种情况,并提前终止请求
  • 希望您的用户在请求这些文件时处于良好的公司网络上。远程/道奇/移动连接的情况要糟糕得多(如果在下载1.9G或2.0G后退出,用户必须重新启动)
  • 它可以在服务器上增加一些负载,尤其是压缩巨大的ZIP文件。如果这是一个问题,那么在创建
    ZipOutputStream
    时,关闭压缩可能是值得的
  • 超过2GB(即4GB)的ZIP文件可能与某些ZIP程序存在问题。我认为最新的Java7使用了ZIP64扩展,所以这个版本的Java可以正确地编写巨大的ZIP文件,但是客户端会有支持大型ZIP文件的程序吗?我以前肯定遇到过这些问题,尤其是在旧的Solaris服务器上

通过将每个BLOB从数据库直接流式传输到客户端文件系统,创建了一个完全动态的ZIP文件

使用具有以下性能的巨大档案进行测试:

  • 服务器磁盘空间成本:0兆字节
  • 服务器RAM成本:~xx兆字节。内存消耗是不可测试的(或者至少我不知道如何正确测试),因为在循环之前、期间和之后多次运行同一例程(通过使用
    Runtime.getRuntime().freemory()
    )会得到不同的、显然是随机的结果)。但是,内存消耗比使用字节[]要低,这就足够了

FileStreamDto.java使用
InputStream
而不是
byte[]


Java Servlet(或Struts2操作)

/*读取要从数据库传输到文件系统的数据量,
将Oracle的所有BLOB、PostgreSQL的ABYTE等的大小相加:
从包含my_条件的my_表中选择sum(长度(my_blob_字段))
*/          
Long-overallSize=getMyService().precalculateZipSize();
//告诉浏览器是一个ZIP
response.setContentType(“应用程序/zip”);
//告诉浏览器文件名,需要下载而不是打开
addHeader(“内容处置”、“附件;文件名=\”myArchive.zip\”);
//告诉浏览器总体尺寸,以便它可以显示真实的进度条
setHeader(“内容长度”,String.valueOf(overallSize));
ServletOutputStream sos=response.getOutputStream();
ZipoutStream zos=新ZipoutStream(sos);
//建立一个
public class FileStreamDto implements Serializable {
    @Getter @Setter private String filename;
    @Getter @Setter private InputStream inputStream; 
}
/* Read the amount of data to be streamed from Database to File System,
   summing the size of all Oracle's BLOB, PostgreSQL's ABYTE etc: 
   SELECT sum(length(my_blob_field)) FROM my_table WHERE my_conditions
*/          
Long overallSize = getMyService().precalculateZipSize();

// Tell the browser is a ZIP
response.setContentType("application/zip"); 
// Tell the browser the filename, and that it needs to be downloaded instead of opened
response.addHeader("Content-Disposition", "attachment; filename=\"myArchive.zip\"");        
// Tell the browser the overall size, so it can show a realistic progressbar
response.setHeader("Content-Length", String.valueOf(overallSize));      

ServletOutputStream sos = response.getOutputStream();       
ZipOutputStream zos = new ZipOutputStream(sos);

// Set-up a list of filenames to prevent duplicate entries
HashSet<String> entries = new HashSet<String>();

/* Read all the ID from the interested records in the database, 
   to query them later for the streams: 
   SELECT my_id FROM my_table WHERE my_conditions */           
List<Long> allId = getMyService().loadAllId();

for (Long currentId : allId){
    /* Load the record relative to the current ID:         
       SELECT my_filename, my_blob_field FROM my_table WHERE my_id = :currentId            
       Use resultset.getBinaryStream("my_blob_field") while mapping the BLOB column */
    FileStreamDto fileStream = getMyService().loadFileStream(currentId);

    // Create a zipEntry with a non-duplicate filename, and add it to the ZipOutputStream
    ZipEntry zipEntry = new ZipEntry(getUniqueFileName(entries,fileStream.getFilename()));
    zos.putNextEntry(zipEntry);

    // Use Apache Commons to transfer the InputStream from the DB to the OutputStream
    // on the File System; at this moment, your file is ALREADY being downloaded and growing
    IOUtils.copy(fileStream.getInputStream(), zos);

    zos.flush();
    zos.closeEntry();

    fileStream.getInputStream().close();                    
}

zos.close();
sos.close();    
private String getUniqueFileName(HashSet<String> entries, String completeFileName){                         
    if (entries.contains(completeFileName)){                                                
        int extPos = completeFileName.lastIndexOf('.');
        String extension = extPos>0 ? completeFileName.substring(extPos) : "";          
        String partialFileName = extension.length()==0 ? completeFileName : completeFileName.substring(0,extPos);
        int x=1;
        while (entries.contains(completeFileName = partialFileName + "(" + x + ")" + extension))
            x++;
    } 
    entries.add(completeFileName);
    return completeFileName;
}