跳到主要內容

JSP環境示範 Java Zip 文字檔案

這篇連同上篇示範是最近做給客戶的一個功能,將數個CSV檔壓縮成一個Zip檔提供下載
我覺得這幾個都是看過JSP版本就很容易改出Java用的版本
Java本身就有API能很簡單的做到檔案的Zip

<%@ page import="java.util.LinkedList"%>
<%@ page import="java.net.URLEncoder"%>
<%@ page import="java.util.AbstractMap.SimpleEntry"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.zip.Adler32"%>
<%@ page import="java.util.zip.CheckedOutputStream"%>
<%@ page import="java.util.zip.ZipOutputStream"%>
<%@ page import="java.util.zip.ZipEntry"%>
<%@ page import="java.util.Map"%>
<%@ page import="java.util.HashSet"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%!
enum Files {
    Customer, Guarantor
}
%>
<%
// CSV file name
HashSet<String> fileNameChecker = new HashSet<String>();
String fileName1 = request.getParameter("fn1");
String fileName2 = request.getParameter("fn2");

// CSV file content
LinkedList<String> f1Content = new LinkedList<String>();
fileNameChecker.add(fileName1);
f1Content.add("Customer Name,Sex,Age");
SimpleEntry<String, LinkedList<String>> f1Entry = new SimpleEntry<String, LinkedList<String>>(fileName1, f1Content);

LinkedList<String> f2Content = new LinkedList<String>();
while(fileNameChecker.contains(fileName2)){ fileName2+=".backup"; }
f2Content.add("Guarantor Name,Sex,Age");
SimpleEntry<String, LinkedList<String>> f2Entry = new SimpleEntry<String, LinkedList<String>>(fileName2, f2Content);


HashMap<Files, SimpleEntry<String, LinkedList<String>>> filesCollection = new HashMap<>();
filesCollection.put(Files.Customer, f1Entry);
filesCollection.put(Files.Guarantor, f2Entry);

printEntrytoCSV(filesCollection);

// zip file name
String fileName = "Sample.zip";

//no caching for download files
response.setHeader("Cache-Control", "no-cache"); //HTTP 1.1
response.setHeader("Pragma", "no-cache"); //HTTP 1.0
response.setDateHeader("Expires", 0);

response.setHeader("Cache-Control", "max-age=60");
response.setHeader("Cache-Control", "must-revalidate");
response.setContentType("application/zip");
String uriencodedFilename = fileName;
try {
    uriencodedFilename = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException e) { }
response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + uriencodedFilename + ";filename=\"" + uriencodedFilename + "\"");

ZipOutputStream zout = new ZipOutputStream(new CheckedOutputStream(response.getOutputStream(), new Adler32()), Charset.forName("UTF-8"));
for(Map.Entry<Files, SimpleEntry<String, LinkedList<String>>> entry : filesCollection.entrySet()){
    SimpleEntry<String, LinkedList<String>> filePairEntry = entry.getValue();
    LinkedList<String> CSVContent = filePairEntry.getValue();
    ZipEntry e = new ZipEntry(filePairEntry.getKey()+".csv");
    zout.putNextEntry(e);
 
    for(String string : CSVContent){
        String line = string+"\r\n";
        byte[] data = line.getBytes("BIG5");
        zout.write(data, 0, data.length);
    }
    zout.closeEntry();
}

zout.flush();
zout.close();
%>
<%!
private void printEntrytoCSV(HashMap<Files, SimpleEntry<String, LinkedList<String>>> filesCollection){
    for(Map.Entry<Files, SimpleEntry<String, LinkedList<String>>> entry : filesCollection.entrySet()){
        Files fileKey = entry.getKey();
        SimpleEntry<String, LinkedList<String>> filePairEntry = entry.getValue();
        LinkedList<String> CSVContent = filePairEntry.getValue();
     
        switch(fileKey){
            case Customer:
                CSVContent .add("John,M,19");
                break;
            case Guarantor:
                CSVContent .add("Ray,M,25");
                break;
        }
    }
}
%>

我先說明這是從相對複雜的內容簡化來的
如果真的輸出很單純的內容是不見得要像這個例子這樣寫

因為不同的檔案需要輸出不同的內容
所以用了Enum來標註不同的檔案各自需要輸入的內容
SimpleEntry 作為最簡單的 Key Value pair容器,拿來裝 file name 跟 file 本身
需要注意 file name不能重複,不然zip時就會丟Exception
這裡我是選擇事先就檢查 filename 是否重複

Content-Disposition 放了不同的 filename 屬性確保下載回來的檔名顯示正確
這也算是撰寫時需要注意的地方

接著看輸出Zip的細節
response的content type 設定成 application/zip
ZipOutputStream 在這裡直接拿 response 的 outputStream來用
最簡單的寫法如下
ZipOutputStream zout = new ZipOutputStream(response.getOutputStream(), Charset.forName("UTF-8"));

ZipOutputStream的第2參數是Java 7 b57版後新增的 constructor
用來指定Zip的檔案中的各個檔案名稱的編碼
在這個版後前Java只能透過第3方Library或自行撰寫較複雜的流程,才能作檔名的編碼處理
例子裡多包了 CheckedOutputStream 則是用來新增 Zip檔的CRC
CRC可以讓系統判斷下載的內容是否正確及完整
Adler32是Java實作的其中一種CRC格式,他沒那麼準確,但是編碼跟解碼的速度較好

輸出的內容可以看到 byte[] data = line.getBytes("BIG5"); 這行
因為這次要輸出非UTF-8的文字格式,這行呼叫 Java 輸出改變編碼的文字內容
如果要改成一般檔案的版本,則用FileInoutStream一次讀指定byte大小的內容後再寫入

留言