一、文件的上傳和下載 1、文件上傳的原理分析 什麼是文件上傳? 要將客戶端(瀏覽器)數據存儲到伺服器端,而不將數據直接存儲到資料庫中,而是要將數據存儲到伺服器所在的磁碟上,這就要使用文件上傳。為什麼使用文件上傳? 通過文件上傳,可以將瀏覽器端的數據直接保存到伺服器端。不將數據保存到資料庫中,而是保存 ...
一、文件的上傳和下載
1、文件上傳的原理分析
什麼是文件上傳?
要將客戶端(瀏覽器)數據存儲到伺服器端,而不將數據直接存儲到資料庫中,而是要將數據存儲到伺服器所在的磁碟
上,這就要使用文件上傳。為什麼使用文件上傳?
通過文件上傳,可以將瀏覽器端的數據直接保存到伺服器端。不將數據保存到資料庫中,而是保存到伺服器磁碟上,這樣減少了資料庫伺服器
的壓力,對數據的操作更加靈活。
1.1 文件上傳的必要前提
- a、提供form表單,method必須是
post
提交方式。 - b、form表單必須設置為
enctype="multipart/form-data"
。 - c、提供
input type="file"
類的上傳輸入域。
1.2 enctype屬性
作用
:告知伺服器請求正文的MIME類型(文件類型)
。(與請求消息頭中:Content-Type作用是一致的)
可選值
:
-
application/x-www-form-urlencoded(預設)
請求消息正文:name=tom&photo=a.txt
伺服器獲取數據:String name = request.getParameter("name"); -
multipart/form-data
請求消息正文:
伺服器獲取數據:request.getParameter(String)方法獲取指定的表單欄位字元內容,但文件上傳表單已經不再是字元內容,而是位元組內容,所以失效,所以需要位元組流的方式。
文件上傳
:解析請求正文
的每部分的內容。
2、藉助第三方的上傳組件實現文件上傳
2.1 fileupload概述
fileupload
是由apache
的commons組件
提供的上傳組件。它最主要的工作就是幫我們解析request.getInputStream()
。
使用步驟,導入commons-fileupload
相關jar包
commons-fileupload.jar,核心包。
commons-io.jar,依賴包。
2.2 fileupload的核心類有
DiskFileItemFactory、ServletFileUpload
、FileItem。
a、解析原理
2.3 fileupload簡單應用
使用fileupload組件的步驟如下:
1. 創建工廠類DiskFileItemFactory對象
DiskFileItemFactory factory = new DiskFileItemFactory();
2. 使用工廠創建解析器對象
ServletFileUpload fileUpload = new ServletFileUpload(factory);
3. 使用解析器來解析request對象
List< FileItem > list = fileUpload.parseRequest(request);
FileItem對象對應一個表單項(表單欄位)
。可以是文件欄位
或普通欄位
。
FileItem介面的方法:
boolean isFormField():判斷當前表單欄位是否為普通文本欄位
,如果返回false,則說明是文件欄位
。
String getFieldName():獲取欄位名稱
,例如:< input type="text" name="username" />,返回的是username。
String getString():獲取欄位的內容
,如果是文件欄位,那麼獲取的是文件內容,當然上傳的文件必須是文本文件。
String getName():獲取文件欄位的文件名稱
(如:a.txt)。
String getContentType():獲取上傳的文件的MIME類型
,例如:text/plain、image/pjpeg。
int getSize():獲取上傳文件的大小
。
InputStream getInputStream():獲取上傳文件對應的輸入流
。
void write(File file):把上傳的文件保存
到指定文件中。
void delete();
3、文件上傳時要考慮的幾個問題(經驗分享
)
a、保證伺服器的安全
把保存上傳文件的目錄放在用戶直接訪問不到的地方。
b、避免文件被覆蓋
讓文件名唯一即可。
c、避免同一個文件夾中的文件過多
方案一
:按照日期進行打散存儲目錄
方案二
:用文件名的hashCode計算打散的存儲目錄:二級目錄d、限制文件的大小:web方式不適合上傳大的文件
設置單個文件大小
:
ServletFileUpload.setFileSizeMax(位元組);
設置總文件大小
:(多文件上傳時)
ServletFileUpload.setSizeMax(位元組);
e、上傳欄位用戶沒有上傳的問題
通過判斷文件名是否為空
即可。
f、臨時文件的問題
DiskFileItemFactory
:
作用:產生FileItem對象。
DiskFileItemFactory內部有一個緩存
,緩存大小預設是10Kb
。如果上傳的文件超過10Kb,就用磁碟
作為緩存。
存放緩存文件的目錄在哪裡?答
:預設是系統的臨時目錄。
FileItem.delete();
FileItem.delete(); 如果自己用IO流實現的文件上傳,則要在流關閉後,清理臨時文件。
FileItem.write(File file);
把上傳的文件保存到指定文件,該方式會自動刪除臨時文件,註意:實際操作不能夠自動刪除臨時文件
(即:使用 FileItem自帶的方法上傳文件)。
完整的示例代碼如下:
package com.itheima.upload;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
public class UploadServlet2 extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// request.setCharacterEncoding("UTF-8"); // 解決伺服器接收請求的編碼問題,該方式的優先順序不高,不好!
// 要執行文件上傳的操作
// 首先判斷表單是否支持文件上傳。即:判斷 enctype="multipart/form-data"
boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);
if (!isMultipartContent) {
throw new RuntimeException("your form is not multipart/form-data");
}
// 創建一個DiskFileItemfactory工廠類對象
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File("e:\\")); // 指定臨時文件的存儲目錄
// 使用工廠創建解析器ServletFileUpload核心對象
ServletFileUpload sfu = new ServletFileUpload(factory);
// 解決上傳文件表單項出現亂碼問題
sfu.setHeaderEncoding("UTF-8");
// 使用解析器來解析request對象 ,並得到一個表單項的List集合
try {
// 限制上傳文件的大小
// sfu.setFileSizeMax(1024 * 1024 * 3); // 表示3M大小的文件
// sfu.setSizeMax(1024 * 1024 * 6); // 多個文件上傳時
List<FileItem> fileItems = sfu.parseRequest(request);
// 遍歷表單項數據
for (FileItem fileitem : fileItems) {
if (fileitem.isFormField()) {
// 文本表單項,普通文本欄位(字元串)
processFormField(fileitem);
} else {
// 文件表單項,文件欄位(使用位元組流讀取)
processUploadField(fileitem);
}
}
} catch (FileUploadBase.FileSizeLimitExceededException e) {
// throw new RuntimeException("文件過大,不能超過3M");
System.out.println("文件過大,不能超過3M"); // 自定義異常
} catch (FileUploadBase.SizeLimitExceededException e) {
System.out.println("總文件大小不能超過6M"); // 自定義異常
} catch (FileUploadException e) {
e.printStackTrace();
}
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
// 文本表單項,普通文本欄位(字元串)
private void processFormField(FileItem fileitem) {
try {
String fieldname = fileitem.getFieldName(); // 獲取欄位名
String fieldvalue = fileitem.getString("UTF-8"); // 獲取欄位值,並解決上傳普通文本表單出的亂碼問題
// 解決上傳普通文本表單項出現的亂碼問題
// fieldvalue = new String(fieldvalue.getBytes("iso-8859-1"), "utf-8");
System.out.println(fieldname + "=" + fieldvalue);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
// 文件表單項,文件欄位(使用位元組流讀取)
private void processUploadField(FileItem fileitem) {
try {
// 得到文件輸入流
InputStream is = fileitem.getInputStream();
// 創建一個文件存檔的目錄
String directoryRealPath = this.getServletContext().getRealPath("/WEB-INF/upload");
File storeDirectory = new File(directoryRealPath); // 既代表文件又代表目錄
if (!storeDirectory.exists()) {
storeDirectory.mkdirs(); // 就創建一個指定的目錄
}
// 獲取文件欄位的文件名稱
String filename = fileitem.getName(); // 文件項框中的值 F:\圖片素材\小清新\43.jpg 或者 43.jpg
// 處理文件名問題 F:\apache-tomcat-7.0.52\webapps\day18_00_upload\ upload\F:\圖片素材\小清新\43.jpg
if (filename != null) {
// filename = filename.substring(filename.lastIndexOf(File.separator) + 1);
filename = FilenameUtils.getName(filename); // 效果同上
}
// 解決文件同名的問題
filename = UUID.randomUUID() + "_" + filename;
// 法一:按日期打散(創建)目錄
// String childDirectory = makeChildDirectory(storeDirectory); // 2015-10-19
// 法二:用文件名的hashCode計算打散的存儲目錄:即二級目錄
String childDirectory = makeChildDirectory(storeDirectory, filename); // a/b
// 在storeDirectory目錄下,創建完整目錄下的文件
File file = new File(storeDirectory, childDirectory + File.separator + filename); // 絕對目錄/日期目錄/文件名
// 通過文件輸出流將上傳的文件保存到伺服器的磁碟
FileOutputStream fos = new FileOutputStream(file);
int len = 0;
byte[] b = new byte[1024];
while ((len = is.read(b)) != -1) {
fos.write(b, 0, len);
}
fos.close();
is.close();
fileitem.delete(); // 如果自己用IO流實現的文件上傳,則要在流關閉後,清除臨時文件
// 把上傳的文件保存到指定文件(使用 FileItem自帶的方法上傳文件)
// fileitem.write(new File(storeDirectory, childDirectory + File.separator + filename));
// fileitem.delete();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
// 用文件名的hashCode計算打散的存儲目錄:二級目錄
private String makeChildDirectory(File storeDirectory, String filename) {
int hashcode = filename.hashCode(); // 返回字元串轉換的32位hashcode碼
System.out.println(hashcode);
String code = Integer.toHexString(hashcode); // 把hashcode碼轉換為16進位的字元
System.out.println(code); // 例如:abdsaf2131safsd
String childDirectory = code.charAt(0) + File.separator + code.charAt(1); // 取前兩位,作為二級目錄,例如:a/b
// 創建指定目錄
File file = new File(storeDirectory, childDirectory);
if (!file.exists()) {
file.mkdirs();
}
return childDirectory;
}
/*
// 按日期打散目錄
private String makeChildDirectory(File storeDirectory) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateDirectory = sdf.format(new Date());
// 創建指定目錄
File file = new File(storeDirectory, dateDirectory);
if (!file.exists()) {
file.mkdirs();
}
return dateDirectory;
}*/
}
一次上傳一個文件的form表單代碼upload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form enctype="multipart/form-data"
action="${pageContext.request.contextPath }/servlet/uploadServlet2"
method="post">
<input type="text" name="name" /><br />
<input type="file" name="photo" /><br />
<input type="submit" value="上傳" /><br />
</form>
</body>
</html>
一次上傳兩個文件的form表單代碼upload2.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>