核心API-DiskFileItemFactory:
DiskFileItemFactory是創(chuàng)建FileItem對(duì)象的工廠,這個(gè)工廠常用方法:
1. public DiskFileItemFactory(int sizeThreshold, java.io.File repository),常用的構(gòu)造函數(shù)。
2. public void setSizeThreshold(int sizeThreshold),設(shè)置內(nèi)存緩沖區(qū)的大小,默認(rèn)值為10K。當(dāng)上傳文件大于緩沖區(qū)大小時(shí), fileupload組件將使用臨時(shí)文件緩存上傳文件。
3. public void setRepository(java.io.File repository),指定臨時(shí)文件目錄,默認(rèn)值為System.getProperty("java.io.tmpdir")。
核心API-ServletFileupLoad:
ServletFileUpload 負(fù)責(zé)處理上傳的文件數(shù)據(jù),并將表單中每個(gè)輸入項(xiàng)封裝到一個(gè)FileItem對(duì)象中。常用方法有:
1. boolean isMultipartContent(HttpServletRequest request),判斷上傳表單是否為上傳表單類型。
2. List parseRequest(HttpServletRequest request),解析request對(duì)象,并把表單中的每一個(gè)輸入項(xiàng)包裝到一個(gè)fileItem 對(duì)象中,并返回一個(gè)保存了所有FileItem的list集合。
3. setFileSizeMax(long fileSizeMax),設(shè)置上傳文件的最大尺寸值。
4. setSizeMax(long sizeMax),設(shè)置上傳文件總量的最大值。
5. setHeaderEncoding(java.lang.String encoding),設(shè)置編碼格式。如果文件路徑中存在中文可能會(huì)造成文件路徑亂碼,用此方法處理可以解決。
6. setProgressListener(ProgressListener pListener),設(shè)置進(jìn)程監(jiān)聽器,與AWT和Swing的事件處理機(jī)制一樣。文件上傳一點(diǎn)就會(huì)觸發(fā)ProgressListener,這樣我們就可以獲取文件上傳的進(jìn)度。
上傳文件案例:
public class FileuploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 創(chuàng)建文件處理工廠,它用于生成FileItem對(duì)象。
DiskFileItemFactory difactory = new DiskFileItemFactory();
//設(shè)置緩存大小,如果上傳文件超過緩存大小,將使用臨時(shí)目錄做為緩存。
difactory.setSizeThreshold(1024 * 1024);
// 設(shè)置處理工廠緩存的臨時(shí)目錄,此目錄下的文件需要手動(dòng)刪除。
String dir = this.getServletContext().getRealPath("/");
File filedir = new File(dir + "filetemp");
if (!filedir.exists())
filedir.mkdir();
difactory.setRepository(filedir);
// 設(shè)置文件實(shí)際保存的目錄
String userdir = dir + "files";
File fudir = new File(userdir);
if (!fudir.exists())
fudir.mkdir();
// 創(chuàng)建request的解析器,它會(huì)將數(shù)據(jù)封裝到FileItem對(duì)象中。
ServletFileUpload sfu = new ServletFileUpload(difactory);
// 解析保存在request中的數(shù)據(jù)并返回list集合
List list = null;
try {
list = sfu.parseRequest(request);
} catch (FileUploadException e) {
e.printStackTrace();
}
// 遍歷list集合,取出每一個(gè)輸入項(xiàng)的FileItem對(duì)象,并分別獲取數(shù)據(jù)
for (Iterator it = list.iterator(); it.hasNext();) {
FileItem fi = (FileItem) it.next();
if (fi.isFormField()) {
System.out.println(fi.getFieldName());
System.out.println(fi.getString());
} else {
//由于客戶端向服務(wù)器發(fā)送的文件是客戶端的全路徑,在這我們只需要文件名即可
String filename = fi.getName();
int index = filename.lastIndexOf("""");
if(index != -1)
filename = filename.substring(index+1);
//向服務(wù)器寫出文件
InputStream in = fi.getInputStream();
FileOutputStream fos = new FileOutputStream(fudir + "/" +filename);
byte[] buf = newbyte[1024];
int len = -1;
while((len = in.read(buf)) != -1){
fos.write(buf, 0, len);
}
// 關(guān)閉流
if(in != null){
try{
in.close();
}finally{
if(fos!=null)
fos.close();
}
}
}
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
|
上面的代碼只是功能的練習(xí),實(shí)際開發(fā)中的文件上傳需要考慮諸多因素,我們接下來繼續(xù)學(xué)習(xí)。
JS動(dòng)態(tài)添加文件上傳框和按鈕的JavaScript代碼:
function add(){
var file = document.createElement("input");
file.type = "file";
file.name = "file";
var butt = document.createElement("input");
butt.type = "button";
butt.value = "刪除";
butt.onclick = function rem(){
//必須使用按鈕的父節(jié)點(diǎn)DIV的父節(jié)點(diǎn)來刪除自己和自己的父節(jié)點(diǎn)DIV。
this.parentNode.parentNode.removeChild(this.parentNode);
};
var div = document.createElement("div");
div.appendChild(file);
div.appendChild(butt);
var parent = document.getElementById("files");
parent.appendChild(div);
}
|
上傳文件的處理細(xì)節(jié)(1):
1. 中文文件亂碼的問題,可以調(diào)用兩個(gè)方法來設(shè)置字符編碼:servletUpLoader.setHeaderEncoding()或request.setCharacterEncoding()。我們可以在源文件的創(chuàng)建ServletFileUpload對(duì)象后邊添加如下代碼:
sfu.setHeaderEncoding("UTF-8");
|
2. 臨時(shí)文件的刪除,如果臨時(shí)文件大于setSizeThreshold設(shè)置的緩存大小,Commons-fileupload組件將使用setRepository設(shè)置的臨時(shí)目錄來保存上傳的文件,上傳完成后我們需要手動(dòng)調(diào)用FileItem.delete來刪除臨時(shí)文件。建議不要修改緩存區(qū)大小,如果設(shè)置緩存為1MB,1000個(gè)用戶上傳文件就需要1000MB內(nèi)存,服務(wù)器會(huì)受不了的。我們刪除掉setSizeThreshold的代碼,并在每次完成一個(gè)文件后添加下而的代碼:
// 刪除臨時(shí)目錄中的文件
fi.delete();
|
上傳文件的處理細(xì)節(jié)(2):
1. 在上面的代碼中,我們將文件的實(shí)際保存目錄設(shè)置在WEB-INF目錄之外。這樣外部可以直接訪問被上傳的文件,這會(huì)造成安全問題。比如用戶上傳了一個(gè)帶有惡意腳本功能的JSP文件,然后從外部訪問執(zhí)行了JSP文件…后果不堪設(shè)想。所以我們將源代碼中對(duì)應(yīng)位置處修改如下:
// 之所以放在"WEB-INF"目錄下是為了防止上傳的文件被直接被訪問的安全問題
String userdir = dir + "WEB-INF/files";
|
2. 一個(gè)WEB應(yīng)用會(huì)許多不同的用戶訪問,不同的用戶可能會(huì)上傳相同名稱的文件,如果這樣可能會(huì)造成文件覆蓋的情況發(fā)生,所以我們必須保證文件名稱的唯一性,我編寫一個(gè)方法來生成唯一性名稱的文件名:
/**
*生成具有唯一性的UUID文件名稱
*@paramfileName
*@return
*/
private String uuidName(String fileName){
UUID uuid = UUID.randomUUID();
return uuid.toString() + "_" + fileName;
}
|
我們將代碼“filename = filename.substring(index + 1);”修改為:filename = uuidName(filename.substring(index + 1));
3. 如果一個(gè)目錄下的文件過多,會(huì)極大減慢文件的訪問速度。比如一個(gè)目錄下的文件如果超過1000個(gè),達(dá)到1萬個(gè)呢?恐怖!我們必須編寫一個(gè)目錄結(jié)構(gòu)生成算法,來分散存上傳的文件。我們一個(gè)方法:
/**
*使用哈希算法生成的文件路徑
*@paramdir
*@paramfileName
*@return
*/
private String hashPath(String dir, String fileName) {
int hashCode = fileName.hashCode();
int dir1 = (hashCode >> 4) & 0xf;
int dir2 = hashCode & 0xf;
String newpath = dir + "/" + dir1 + "/" + dir2 + "/";
File file = new File(newpath);
if(!file.exists()){
file.mkdirs();
}
return newpath + uuidName(fileName);
}
|
上傳文件的處理細(xì)節(jié)(3)
1. 使用ProgressListener顯示上傳文件進(jìn)度,在創(chuàng)建ServletFileUpload之后添加如下代碼:
// 設(shè)置文件上傳進(jìn)度監(jiān)聽器
sfu.setProgressListener(new ProgressListener() {
public void update(long pBytesRead, long pContentLength, int pItems) {
System.out.println("已上傳:" + pBytesRead + "總大小:"
+ pContentLength);
}
});
|
2. 上面的代碼會(huì)造成頻繁的打印,為了使它在上傳一定數(shù)量后再打印,比如上傳10KB后再打印,我們修改上面的代碼如下:
// 設(shè)置文件上傳進(jìn)度監(jiān)聽器
sfu.setProgressListener(new ProgressListener() {
longtemp = -1;
public void update(long pBytesRead, long pContentLength, int pItems) {
long size = pBytesRead / 1024 * 1024 * 10;
if(temp == size)
return;
temp = size;
if(pBytesRead != -1)
System.out.println("已上傳:" + pBytesRead + "總大小:"
+ pContentLength);
else
System.out.println("上傳完成!");
}
});
|
上面的代碼比較經(jīng)典,好好回味一下。
文件下載:
WEB應(yīng)用中實(shí)現(xiàn)文件下載的兩種方式:
1. 超鏈接直接指向下載資源
2. 程序?qū)崿F(xiàn)下載需設(shè)置兩個(gè)響應(yīng)頭:
(1). 設(shè)置Content-Type 的值為:application/x-msdownload。Web 服務(wù)器需要告訴瀏覽器其所輸出的內(nèi)容的類型不是普通的文本文件或 HTML 文件,而是一個(gè)要保存到本地的下載文件。
(2). Web 服務(wù)器希望瀏覽器不直接處理相應(yīng)的實(shí)體內(nèi)容,而是由用戶選擇將相應(yīng)的實(shí)體內(nèi)容保存到一個(gè)文件中,這需要設(shè)置 Content-Disposition 報(bào)頭。該報(bào)頭指定了接收程序處理數(shù)據(jù)內(nèi)容的方式,在 HTTP 應(yīng)用中只有 attachment 是標(biāo)準(zhǔn)方式,attachment 表示要求用戶干預(yù)。在 attachment 后面還可以指定 filename 參數(shù),該參數(shù)是服務(wù)器建議瀏覽器將實(shí)體內(nèi)容保存到文件中的文件名稱。在設(shè)置 Content-Dispostion 之前一定要指定 Content-Type。
為實(shí)現(xiàn)文件下載,首先我們遍歷目錄下所有文件,Servlet:
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ListFileServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 獲取目錄
String dir = this.getServletContext().getRealPath("/WEB-INF/files");
HashMap map = new HashMap();
listFile(new File(dir), map);
// 將文件列表設(shè)置到request的屬性中,然后由JSP頁(yè)面打印列表。
request.setAttribute("filemap", map);
request.getRequestDispatcher("/list.jsp").forward(request, response);
}
/**
*使用遞歸算法,將所有子目錄中的文件添加到列表中
*
*@paramf
*@paraml
*/
private void listFile(File f, HashMap map) {
if (f.isFile()) {
String path = f.getAbsolutePath().substring(
this.getServletContext().getRealPath("/").length());
String name = f.getName();
name = name.substring(name.indexOf("_")+1);
//BASE64Encoder encoder = new BASE64Encoder();
map.put(path, name);
} else {
File[] files = f.listFiles();
for (int i = 0; i < files.length; i++) {
listFile(files[i], map);
}
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}