一、文件上傳介紹
要將客戶端(瀏覽器)大數(shù)據(jù)存儲(chǔ)到服務(wù)器端,不將數(shù)據(jù)直接存儲(chǔ)到數(shù)據(jù)庫(kù)中,而是要將數(shù)據(jù)存儲(chǔ)到服務(wù)器所在的磁盤上,這就要使用文件上傳。
作用:減少了數(shù)據(jù)庫(kù)服務(wù)器的壓力,對(duì)數(shù)據(jù)的操作更加靈活。
二、文件上傳原理分析
所謂的文件上傳就是服務(wù)器端通過request對(duì)象獲取輸入流,將瀏覽器端上傳的數(shù)據(jù)讀取出來,保存到服務(wù)器端
瀏覽器端操作
請(qǐng)求方式必須是post
使用<input type=’file’ name=’xxx’>,必須有name屬性且有值
表單必須設(shè)置encType=”multipart/form-data”
服務(wù)器端操作
通過request對(duì)象,獲取inputStream,就可以將瀏覽器提交的所有數(shù)據(jù)讀取到.
注意:
這時(shí)候,獲取其他上傳參數(shù)的時(shí)候,通過request.getParameter(“”)獲取不到了.
三、Commons-fileupload
commons-fileupload介紹
Apache 開源組織提供了一個(gè)用來處理表單文件上傳的一個(gè)開源組件( Commons-fileupload ),該組件性能優(yōu)異,并且其API使用極其簡(jiǎn)單,可以讓開發(fā)人員輕松實(shí)現(xiàn)web文件上傳功能,因此在web開發(fā)中實(shí)現(xiàn)文件上傳功能,通常使用Commons-fileupload組件實(shí)現(xiàn)。
使用Commons-fileupload組件實(shí)現(xiàn)文件上傳,需要導(dǎo)入該組件相應(yīng)的支撐jar包:
Commons-fileupload和commons-io。
commons-io 不屬于文件上傳組件的開發(fā)jar文件,但Commons-fileupload 組件從1.1 版本開始,它工作時(shí)需要commons-io包的支持。
commons-fileupload API簡(jiǎn)單介紹
DiskFileItemFactory,用于設(shè)置緩存大小及臨時(shí)文件存儲(chǔ)位置
ServletFileUpload,真正用于文件上傳的核心類
FIleItem,代表的是一個(gè)上傳項(xiàng)
入門案例
在瀏覽器端創(chuàng)建一個(gè)可以上傳文件的表單,在服務(wù)器端通過commons-fileupload完成文件上傳。
瀏覽器端注意三件事情:
1.表單的提交方式為post
2.在表單上添加屬性 encType=”multipart/form-data”
3.使用<input type=’file’ >,添加name屬性且有值
服務(wù)器端:
創(chuàng)建DiskFileItemFactory
創(chuàng)建ServletFileUpload
通過ServletFileUpload的parseRequest方法得到所有的FileItem
瀏覽器端
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f">
<input type="submit" value="上傳">
</form>
服務(wù)器端
// 1.創(chuàng)建一個(gè) DiskFileItemFactory
DiskFileItemFactory factory = new DiskFileItemFactory();
// 2.創(chuàng)建一個(gè)ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(factory);
// 3.完成上傳操作
try {
// 3.1 得到所有上傳項(xiàng)
List<FileItem> items = upload.parseRequest(request);
// 3.2遍歷上傳項(xiàng)
for (FileItem item : items) {
// 3.3判斷是否是上傳組件
if (!item.isFormField()) {
// 3.4 將文件真正上傳
IOUtils.copy(item.getInputStream(), new FileOutputStream(
"f:/upload/a.txt"));
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
1. Commons-fileupload詳解-DiskFileItemFactory
設(shè)置緩存大小
factory.setSizeThreshold(1024*1024); //設(shè)置為1m 默認(rèn)是10k
設(shè)置臨時(shí)文件存儲(chǔ)位置
File temp=new File(this.getServletContext().getRealPath("/temp"));
factory.setRepository(temp); //可以指定臨時(shí)文件存儲(chǔ)位置,默認(rèn)是系統(tǒng)的臨時(shí)文件存儲(chǔ)位置
2. Commons-fileupload詳解-ServletFileUpload
parseRequest方法
List<FileItem> pareRequest(HttpServletRequest request)
得到所有的上傳信息,將每一部分映射成FileItem對(duì)象.
isMultipartContent方法
boolean isMultipartContent(HttpServletRequest request)
這個(gè)方法返回值是boolean,它是用于判斷當(dāng)前表單是否是一個(gè)上傳的表單,
簡(jiǎn)單說,就判斷它的encType的值是否是 multipart/form-data.
setHeaderEncoding方法
用于解決上傳文件名稱中文亂碼問題
設(shè)置上傳文件大小
void setFileSizeMax(long fileSizeMax) 設(shè)置單個(gè)文件上傳大小
void setSizeMax(long sizeMax) 設(shè)置總文件上傳大小
3. Commons-fileupload詳解-FileItem
isFormField方法
這個(gè)方法返回的是boolean類型,
它是判斷當(dāng)前組件是否是上傳組件 簡(jiǎn)單說,就是判斷type="file",
如果返回true,代表不是上傳組件,返回false,代表是上傳組件
getName方法
獲取上傳組件的上傳文件的名稱,如果是非上傳組件,返回的是null
getFieldName方法
獲取組件名稱,簡(jiǎn)單說,就是表單的元素的name值。
getString方法
獲取非上傳組件的value值,
通過它也可以獲取上傳的文件內(nèi)容,但是,使用它獲取不合適。如果使用了commons-fileupload進(jìn)行文件上傳,而上傳表單中包含了 非上傳組件,獲取其值,不能使用request獲取.getString()有一個(gè)重載的方法
getString(String encoding)可以解決亂碼問題
getInputStream方法
通過FileItem.getInputStream();可以獲取一個(gè)輸入流,這個(gè)輸入流就可以讀取出上傳文件內(nèi)容。
InputStream is = item.getInputStream(); // 用于讀取上傳文件內(nèi)容的輸入流.
FileOutputStream fos = new FileOutputStream("f:/upload/" + filename);
int len = -1;
byte[] b = new byte[1024 * 10];
while ((len = is.read(b)) != -1) {
fos.write(b, 0, len);
fos.flush();
}
fos.close();
is.close();
可以使用IOUtils工具完成文件copy操作
IOUtils.copy(is, fos);
delete方法
它是用于上傳完成后,刪除臨時(shí)文件的
四、 多文件上傳
我們?cè)趯戉]件中可以添加多個(gè)附件,那么我們?cè)谖募蟼鲿r(shí),是不是也可以上傳多個(gè)文件哪,答案是一定的,那么怎樣實(shí)現(xiàn)多個(gè)文件上傳哪?
我們可以通過js實(shí)現(xiàn)瀏覽器端的上傳文件框的動(dòng)態(tài)添加。服務(wù)器端代碼不需要改變。
function addFile() {
//1.得到id叫content的div
var div=document.getElementById("content");
//2.向其中添加一段html代碼
div.innerHTML+="<div><input type='file' name='f'><input type='button' value='remove File' onclick='removeFile(this);'></div>";
}
function removeFile(btn){
document.getElementById("content").removeChild(btn.parentNode);
}
五、文件上傳問題
1. 文件重名
每一個(gè)客戶端都可以進(jìn)行文件上傳操作,那么當(dāng)我們上傳的文件過多,一定會(huì)出現(xiàn)同名的文件,那么在服務(wù)器端只能保存一個(gè),對(duì)于這個(gè)問題,我們?cè)谏蟼魑募r(shí),就需要考慮文件重名問題.
一般情況下,對(duì)于上傳文件,為了保證不重名,會(huì)給文件起一個(gè)隨機(jī)名.
一種方案是使用uuid.
一種方案是使用毫秒值
2. 存儲(chǔ)位置
本質(zhì)就是上傳的文件是否允許瀏覽器端直接訪問。
例如:商品添加時(shí)需要一個(gè)圖片,這個(gè)圖片一定是可以直接被瀏覽器端訪問的。
允許被瀏覽器端訪問:放置在WebRoot下,但不能是WEB-INF或META-INF下其及子目錄下
不允許被瀏覽器端訪問:
若放在工程下則放置在WEB-INF或META-INF及其子目錄下.
也可以不放在工程下
3. 目錄分離
當(dāng)我們上傳文件過多,并且保存在同一個(gè)目錄下時(shí),我們就需要考慮怎樣處理它們,因?yàn)橐粋€(gè)目錄下文件過多,不僅降低性能,操作時(shí)也不方便。
為了防止同一個(gè)目錄下方上傳文件數(shù)量過多
- 按照上傳時(shí)間進(jìn)行目錄分離 (周、月 )
- 按照上傳用戶進(jìn)行目錄分離 ----- 為每個(gè)用戶建立單獨(dú)目錄
- 按照固定數(shù)量進(jìn)行目錄分離 ------ 假設(shè)每個(gè)目錄只能存放3000個(gè)文件 ,每當(dāng)一個(gè)目錄存滿3000個(gè)文件后,創(chuàng)建一個(gè)新的目錄
- 按照唯一文件名的hashcode 進(jìn)行目錄分離
public static String generateRandomDir(String uuidFileName) {
// 獲得唯一文件名的hashcode
int hashcode = uuidFileName.hashCode();
// 獲得一級(jí)目錄
int d1 = hashcode & 0xf;
// 獲得二級(jí)目錄
int d2 = (hashcode >>> 4) & 0xf;
return "/" + d2 + "/" + d1;// 共有256目錄
}
六、案例:使用Servlet3.0技術(shù)完成文件的上傳
技術(shù)分析:
【Servlet3.0】
Servlet3.0 與 Servlet2.5:
* Servlet3.0需要運(yùn)行在tomcat7以上的服務(wù)器中.
* Servlet3.0以后web.xml就不是必須的.
1.Servlet3.0支持注解開發(fā).
2.支持文件上傳.
【Servlet3.0支持注解開發(fā)】
使用@WebServlet替換web.xml中配置的Servlet:
@WebServlet(urlPatterns="/ServletDemo1",loadOnStartup=2,initParams=@WebInitParam(name="username",value="root"))
使用@WebListener替換web.xml中監(jiān)聽器的配置:
@WebListener
[使用@WebFilter替換web.xml中的過濾器的配置:](mailto:使用@WebFilter替換web.xml中的過濾器的配置:)
@WebFilter(urlPatterns="/*")
【Servlet3.0的文件上傳】
文件上傳:
文件上傳:指的是將本地的文件 寫到 服務(wù)器上.
文件上傳的要素:
1.表單的提交的方式必須是POST.
2.表單中必須有一個(gè)文件上傳項(xiàng):<input type=”file”>,而且文件上傳項(xiàng)必須有name屬性和值.
<input type=”file” name=”upload”/>
3.表單的enctype屬性的值必須是multipart/form-data
文件上傳的抓包分析:
未修改enctype屬性的時(shí)候:
POST /WEB17_WEB/demo1/demo1.jsp HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
X-HttpWatch-RID: 22325-10011
Referer: http://localhost:8080/WEB17_WEB/demo1/demo1.jsp
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: localhost:8080
Content-Length: 47
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=99CD51DA9A47D29200168968AD983E9E
upload=C%3A%5CUsers%5Capple%5CDesktop%5Caaa.txt
已經(jīng)修改了enctype屬性:
POST /WEB17_WEB/demo1/demo1.jsp HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
X-HttpWatch-RID: 22325-10026
Referer: http://localhost:8080/WEB17_WEB/demo1/demo1.jsp
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Type: multipart/form-data; boundary=---------------------------7e02e526160b66
Accept-Encoding: gzip, deflate
Host: localhost:8080
Content-Length: 224
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=99CD51DA9A47D29200168968AD983E9E
-----------------------------7e02e526160b66
Content-Disposition: form-data; name="upload"; filename="C:\Users\apple\Desktop\aaa.txt"
Content-Type: text/plain
Hello shouyi
-----------------------------7e02e526160b66—
【文件上傳的原理】
根據(jù)分割線將請(qǐng)求體的部分分成幾塊:
判斷 每塊是 普通項(xiàng)還是文件上傳項(xiàng).
普通項(xiàng):獲得名稱和值.
文件上傳項(xiàng):獲得文件名 和 文件內(nèi)容輸入流.
【文件上傳的技術(shù)】
JspSmartUpload: jspSmartUpload組件是應(yīng)用JSP進(jìn)行B/S程序開發(fā)過程中經(jīng)常使用的上傳下載組件,它使用簡(jiǎn)單,方便?,F(xiàn)在我又為其加上了下載中文名字的文件的支持,真?zhèn)€是如虎添翼,必將贏得更多開發(fā)者的青睞。-Model1年代的文件上傳的工具.
FileUpload 是 Apache commons下面的一個(gè)子項(xiàng)目,用來實(shí)現(xiàn)Java環(huán)境下面的文件上傳功能,與常見的SmartUpload齊名.應(yīng)用在Model2年代了.
代碼實(shí)現(xiàn):
/**
* 文件上傳的Servlet
*/
@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 接收普通項(xiàng):
request.setCharacterEncoding("UTF-8");
String desc = request.getParameter("desc");
System.out.println("文件描述:"+desc);
Part part = request.getPart("upload");
// 獲得上傳的文件的大小
long size = part.getSize();
System.out.println("文件大小"+size);
String type = part.getContentType();
System.out.println("文件類型"+type); // text/plain image/jpeg
String name = part.getName();
System.out.println(name);
// 獲得文件名:
String header = part.getHeader("Content-Disposition");
System.out.println(header);
int idx = header.lastIndexOf("filename=\"");
String fileName = header.substring(idx+10, header.length()-1);
System.out.println(fileName);
// 獲得文件內(nèi)容:
InputStream is = part.getInputStream();
// 獲得文件上傳路徑:
String path = this.getServletContext().getRealPath("/upload");
OutputStream os = new FileOutputStream(path+"/"+fileName);
int len = 0;
byte[] b = new byte[1024];
while((len = is.read(b))!=-1){
os.write(b, 0, len);
}
is.close();
os.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}