一、文件上傳概述
實(shí)現(xiàn)Web開發(fā)中的文件上傳功能,需要兩步操作:
1、在Web頁面中添加上傳輸入項(xiàng)
1
2
3
4
5
6
7
8
9
10
|
< form action = "#" method = "post" enctype = "multipart/form-data" > < input type = "file" name = "filename1" />< br > < input type = "file" name = "filename2" />< br > < input type = "submit" value = "上傳" /> < form > <!-- 1、表單方式必須是post 2、必須設(shè)置encType屬性為 multipart/form-data.設(shè)置該值后,瀏覽器在上傳文件時,將會把文件數(shù)據(jù)附帶在http請求消息體中, 并使用MIME協(xié)議對上傳的文件進(jìn)行描述,以方便接收方對上傳數(shù)據(jù)進(jìn)行解析和處理。 3、必須要設(shè)置input的name屬性,否則瀏覽器將不會發(fā)送上傳文件的數(shù)據(jù)。 --> |
2、在Servlet中讀取文件上傳數(shù)據(jù),并保存到服務(wù)器硬盤
Request對象提供了一個getInputStream方法,通過這個方法可以讀取到客戶端提交過來的數(shù)據(jù)。但由于用戶可能會同時上傳多個文件,在Servlet端編程直接讀取上傳數(shù)據(jù),并分別解析出相應(yīng)的文件數(shù)據(jù)是一項(xiàng)非常麻煩的工作。
比如下面是截取的瀏覽器上傳文件時發(fā)送的請求的HTTP協(xié)議中的部分內(nèi)容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5 Content-Type: multipart/form-data; boundary=---------------------------7dfa01d1908a4 UA-CPU: AMD64 Accept-Encoding: gzip, deflate User-Agent: Mozilla/5.0 (Windows NT 6.2; Win64; x64; Trident/7.0; rv:11.0) like Gecko Content-Length: 653 Host: localhost:8080 Connection: Keep-Alive Pragma: no-cache Cookie: JSESSIONID=11CEFF8E271AB62CE676B5A87B746B5F -----------------------------7dfa01d1908a4 Content-Disposition: form-data; name="username" zhangsan -----------------------------7dfa01d1908a4 Content-Disposition: form-data; name="userpass" 1234 -----------------------------7dfa01d1908a4 Content-Disposition: form-data; name="filename1"; filename="C:\Users\ASUS\Desktop\upload.txt" Content-Type: text/plain this is first file content! -----------------------------7dfa01d1908a4 Content-Disposition: form-data; name="filename1"; filename="C:\Users\ASUS\Desktop\upload2.txt" Content-Type: text/plain this is Second file content! hello -----------------------------7dfa01d1908a4-- |
從上面的數(shù)據(jù)中也可以看出,如果自己手工的去分割讀取數(shù)據(jù)很難寫出健壯穩(wěn)定的程序。所以,為方便用戶處理上傳數(shù)據(jù),Apache開源組織提供了一個用來處理表單文件上傳的一個開源組件(Commons-fileupload),該組件性能優(yōu)異,并且其API使用極其簡單,可以讓開發(fā)人員輕松實(shí)現(xiàn)web文件上傳功能,因此在web開發(fā)中實(shí)現(xiàn)文件上傳功能,通常使用Commons-fileupload組件實(shí)現(xiàn)。
需要導(dǎo)入兩個jar包:Commons-fileupload、commons-io
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
response.setContentType( "text/html;charset=utf-8" ); //設(shè)置響應(yīng)編碼 request.setCharacterEncoding( "utf-8" ); PrintWriter writer = response.getWriter(); //獲取響應(yīng)輸出流 ServletInputStream inputStream = request.getInputStream(); //獲取請求輸入流 /* * 1、創(chuàng)建DiskFileItemFactory對象,設(shè)置緩沖區(qū)大小和臨時文件目錄 * 該類有兩個構(gòu)造方法一個是無參的構(gòu)造方法, * 另一個是帶兩個參數(shù)的構(gòu)造方法 * @param int sizeThreshold,該參數(shù)設(shè)置內(nèi)存緩沖區(qū)的大小,默認(rèn)值為10K。當(dāng)上傳文件大于緩沖區(qū)大小時,fileupload組件將使用臨時文件緩存上傳文件 * @param java.io.File repository,該參數(shù)指定臨時文件目錄,默認(rèn)值為System.getProperty("java.io.tmpdir"); * * 如果使用了無參的構(gòu)造方法,則使用setSizeThreshold(int sizeThreshold),setRepository(java.io.File repository) * 方法手動進(jìn)行設(shè)置 */ DiskFileItemFactory factory = new DiskFileItemFactory(); int sizeThreshold=1024*1024; factory.setSizeThreshold(sizeThreshold); File repository = new File(request.getSession().getServletContext().getRealPath( "temp" )); // System.out.println(request.getSession().getServletContext().getRealPath("temp")); // System.out.println(request.getRealPath("temp")); factory.setRepository(repository); /* * 2、使用DiskFileItemFactory對象創(chuàng)建ServletFileUpload對象,并設(shè)置上傳文件的大小 * * ServletFileUpload對象負(fù)責(zé)處理上傳的文件數(shù)據(jù),并將表單中每個輸入項(xiàng)封裝成一個FileItem * 該對象的常用方法有: * boolean isMultipartContent(request);判斷上傳表單是否為multipart/form-data類型 * List parseRequest(request);解析request對象,并把表單中的每一個輸入項(xiàng)包裝成一個fileItem 對象,并返回一個保存了所有FileItem的list集合 * void setFileSizeMax(long filesizeMax);設(shè)置單個上傳文件的最大值 * void setSizeMax(long sizeMax);設(shè)置上傳溫江總量的最大值 * void setHeaderEncoding();設(shè)置編碼格式,解決上傳文件名亂碼問題 */ ServletFileUpload upload = new ServletFileUpload(factory); upload.setHeaderEncoding( "utf-8" ); //設(shè)置編碼格式,解決上傳文件名亂碼問題 /* * 3、調(diào)用ServletFileUpload.parseRequest方法解析request對象,得到一個保存了所有上傳內(nèi)容的List對象 */ List<FileItem> parseRequest= null ; try { parseRequest = upload.parseRequest(request); } catch (FileUploadException e) { e.printStackTrace(); } /* * 4、對list進(jìn)行迭代,每迭代一個FileItem對象,調(diào)用其isFormField方法判斷是否是文件上傳 * true表示是普通表單字段,則調(diào)用getFieldName、getString方法得到字段名和字段值 * false為上傳文件,則調(diào)用getInputStream方法得到數(shù)據(jù)輸入流,從而讀取上傳數(shù)據(jù) * * FileItem用來表示文件上傳表單中的一個上傳文件對象或者普通的表單對象 * 該對象常用方法有: * boolean isFormField();判斷FileItem是一個文件上傳對象還是普通表單對象 * true表示是普通表單字段, * 則調(diào)用getFieldName、getString方法得到字段名和字段值 * false為上傳文件, * 則調(diào)用getName()獲得上傳文件的文件名,注意:有些瀏覽器會攜帶客戶端路徑,需要自己減除 * 調(diào)用getInputStream()方法得到數(shù)據(jù)輸入流,從而讀取上傳數(shù)據(jù) * delete(); 表示在關(guān)閉FileItem輸入流后,刪除臨時文件。 */ for (FileItem fileItem : parseRequest) { if (fileItem.isFormField()) { //表示普通字段 if ( "username" .equals(fileItem.getFieldName())) { String username = fileItem.getString(); writer.write( "您的用戶名:" +username+ "<br>" ); } if ( "userpass" .equals(fileItem.getFieldName())) { String userpass = fileItem.getString(); writer.write( "您的密碼:" +userpass+ "<br>" ); } } else { //表示是上傳的文件 //不同瀏覽器上傳的文件可能帶有路徑名,需要自己切割 String clientName = fileItem.getName(); String filename = "" ; if (clientName.contains( "\\" )) { //如果包含"\"表示是一個帶路徑的名字,則截取最后的文件名 filename = clientName.substring(clientName.lastIndexOf( "\\" )).substring(1); } else { filename = clientName; } UUID randomUUID = UUID.randomUUID(); //生成一個128位長的全球唯一標(biāo)識 filename = randomUUID.toString()+filename; /* * 設(shè)計(jì)一個目錄生成算法,如果所用用戶上傳的文件總數(shù)是億數(shù)量級的或更多,放在同一個目錄下回導(dǎo)致文件索引非常慢, * 所以,設(shè)計(jì)一個目錄結(jié)構(gòu)來分散存放文件是非常有必要,且合理的 * 將UUID取哈希算法,散列到更小的范圍, * 將UUID的hashcode轉(zhuǎn)換為一個8位的8進(jìn)制字符串, * 從這個字符串的第一位開始,每一個字符代表一級目錄,這樣就構(gòu)建了一個八級目錄,每一級目錄中最多有16個子目錄 * 這無論對于服務(wù)器還是操作系統(tǒng)都是非常高效的目錄結(jié)構(gòu) */ int hashUUID =randomUUID.hashCode(); String hexUUID = Integer.toHexString(hashUUID); //System.out.println(hexUUID); //獲取將上傳的文件存存儲在哪個文件夾下的絕對路徑 String filepath=request.getSession().getServletContext().getRealPath( "upload" ); for ( char c : hexUUID.toCharArray()) { filepath = filepath+ "/" +c; } //如果目錄不存在就生成八級目錄 File filepathFile = new File(filepath); if (!filepathFile.exists()) { filepathFile.mkdirs(); } //從Request輸入流中讀取文件,并寫入到服務(wù)器 InputStream inputStream2 = fileItem.getInputStream(); //在服務(wù)器端創(chuàng)建文件 File file = new File(filepath+ "/" +filename); BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(file)); byte [] buffer = new byte [10*1024]; int len = 0; while ((len= inputStream2.read(buffer, 0, 10*1024))!=-1) { bos.write(buffer, 0, len); } writer.write( "您上傳文件" +clientName+ "成功<br>" ); //關(guān)閉資源 bos.close(); inputStream2.close(); } } //注意Eclipse的上傳的文件是保存在項(xiàng)目的運(yùn)行目錄,而不是workspace中的工程目錄里。 |
二、文件上傳需要特別注意的問題: (這些問題在上面的代碼中都提供了簡單的解決)
1、文件存放的位置
為保證服務(wù)器的安全,上傳文件應(yīng)保存在應(yīng)用程序的WEB-INF目錄下,或者不受WEB服務(wù)器管理的目錄,如果用戶上傳一個帶有可執(zhí)行代碼的文件,如jsp文件,根據(jù)拼接訪問路徑去訪問的話,可以在服務(wù)器端做任何事情。
2、為防止多用戶上傳形同文件名的文件,而導(dǎo)致文件覆蓋的情況發(fā)生,文件上傳程序應(yīng)保證上傳文件具有唯一文件名。
使用UUID + 用戶上傳文件名的方式重命名
關(guān)于UUID:
UUID(Universally Unique Identifier)全局唯一標(biāo)識符,是指在一臺機(jī)器上生成的數(shù)字,它保證對在同一時空中的所有機(jī)器都是唯一的。按照開放軟件基金會(OSF)制定的標(biāo)準(zhǔn)計(jì)算,用到了以太網(wǎng)卡地址、納秒級時間、芯片ID碼和許多可能的數(shù)字。由以下幾部分的組合:當(dāng)前日期和時間(UUID的第一個部分與時間有關(guān),如果你在生成一個UUID之后,過幾秒又生成一個UUID,則第一個部分不同,其余相同),時鐘序列,全局唯一的IEEE機(jī)器識別號(如果有網(wǎng)卡,從網(wǎng)卡獲得,沒有網(wǎng)卡以其他方式獲得),UUID的唯一缺陷在于生成的結(jié)果串會比較長。
是一個128位長的數(shù)字,一般用16進(jìn)制表示。算法的核心思想是結(jié)合機(jī)器的網(wǎng)卡、當(dāng)?shù)貢r間、一個隨即數(shù)來生成GUID。從理論上講,如果一臺機(jī)器每秒產(chǎn)生10000000個GUID,則可以保證(概率意義上)3240年不重復(fù)。
從JDK1.5開始,生成UUID變成了一件簡單的事,以為JDK實(shí)現(xiàn)了UUID:
java.util.UUID,直接調(diào)用即可.
UUID uuid = UUID.randomUUID();
String s = UUID.randomUUID().toString();//用來生成數(shù)據(jù)庫的主鍵id非常不錯。。
UUID是由一個十六位的數(shù)字組成,表現(xiàn)出來的形式例如
550E8400-E29B-11D4-A716-446655440000
3、為防止單個目錄下文件過多,影響文件讀寫速度,處理上傳文件的程序應(yīng)該應(yīng)根據(jù)可能的上傳總量,選擇合適的目錄結(jié)構(gòu)生成算法,將上傳文件分散存儲。如使用hashcode方法構(gòu)建多級目錄。
4、如果不同用戶都上傳了相同的文件,那么在服務(wù)器端沒有必要存儲同一個文件的很多分拷貝,這樣很浪費(fèi)資源,應(yīng)該設(shè)計(jì)算法解決這種重復(fù)文件的問題。
5、JSP技術(shù)原理自動實(shí)現(xiàn)了多線程。所以開發(fā)者不需要考慮上傳文件的多線程操作
三、文件下載
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
<% ArrayList<String> fileNames = new ArrayList<String>(); fileNames.add( "file/aa.txt" ); fileNames.add( "file/bb.jpg" ); for (String fileName : fileNames) { %> <form action= "DownloadServlet" method= "get" > <input type= "hidden" name= "fileName" value= "<%=fileName %>" /> <input type= "submit" value= "下載:<%=fileName %>" /> </form> <% } %> request.setCharacterEncoding( "utf-8" ); String filename = request.getParameter( "fileName" ); String urlname = URLEncoder.encode(filename, "utf-8" ); //防止文件名中有中文亂碼 response.setHeader( "Content-Disposition" , "attachment;filename=" +urlname); FileInputStream fis = new FileInputStream( new File(request.getSession().getServletContext().getRealPath(filename))); BufferedInputStream bis = new BufferedInputStream(fis); ServletOutputStream sos = response.getOutputStream(); byte [] buffer = new byte [ 1024 ]; int len= 0 ; while ((len=bis.read(buffer, 0 , 1024 ))!=- 1 ){ sos.write(buffer, 0 , len); } bis.close(); fis.close(); |
四、在SSH中使用smartUpload組件簡化文件上傳下載
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。