一、檔案上傳概述
實現Web開發中的檔案上傳功能,需要兩步操作:
1、在Web頁面中添加上傳輸入項
<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、必須設定encType屬性為 multipart/form-data.設定該值後,瀏覽器在上傳檔案時,將會把檔案資料附帶在http請求訊息體中, 並使用MIME協議對上傳的檔案進行描述,以方便接收方對上傳資料進行解析和處理。 3、必須要設定input的name屬性,否則瀏覽器將不會發送上傳檔案的資料。 -->
2、在Servlet中讀取檔案上傳資料,並儲存到伺服器硬碟
Request對象提供了一個getInputStream方法,通過這個方法可以讀取到用戶端提交過來的資料。但由於使用者可能會同時上傳多個檔案,在Servlet端編程直接讀取上傳資料,並分別解析出相應的檔案資料是一項非常麻煩的工作。
比如下面是截取的瀏覽器上傳檔案時發送的請求的HTTP協議中的部分內容:
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5Content-Type: multipart/form-data; boundary=---------------------------7dfa01d1908a4UA-CPU: AMD64Accept-Encoding: gzip, deflateUser-Agent: Mozilla/5.0 (Windows NT 6.2; Win64; x64; Trident/7.0; rv:11.0) like GeckoContent-Length: 653Host: localhost:8080Connection: Keep-AlivePragma: no-cacheCookie: JSESSIONID=11CEFF8E271AB62CE676B5A87B746B5F-----------------------------7dfa01d1908a4Content-Disposition: form-data; name="username"zhangsan-----------------------------7dfa01d1908a4Content-Disposition: form-data; name="userpass"1234-----------------------------7dfa01d1908a4Content-Disposition: form-data; name="filename1"; filename="C:\Users\ASUS\Desktop\upload.txt"Content-Type: text/plainthis is first file content!-----------------------------7dfa01d1908a4Content-Disposition: form-data; name="filename1"; filename="C:\Users\ASUS\Desktop\upload2.txt"Content-Type: text/plainthis is Second file content!hello-----------------------------7dfa01d1908a4--
從上面的資料中也可以看出,如果自己手工的去分割讀取資料很難寫出健壯穩定的程式。所以,為方便使用者處理上傳資料,Apache開源組織提供了一個用來處理表單檔案上傳的一個開源組件(Commons-fileupload),該組件效能優異,並且其API使用極其簡單,可以讓開發人員輕鬆實現web檔案上傳功能,因此在web開發中實現檔案上傳功能,通常使用Commons-fileupload組件實現。
需要匯入兩個jar包:Commons-fileupload、commons-io
response.setContentType("text/html;charset=utf-8");//設定響應編碼 request.setCharacterEncoding("utf-8"); PrintWriter writer = response.getWriter();//擷取響應輸出資料流 ServletInputStream inputStream = request.getInputStream();//擷取請求輸入資料流 /* * 1、建立DiskFileItemFactory對象,設定緩衝區大小和臨時檔案目錄 * 該類有兩個構造方法一個是無參的構造方法, * 另一個是帶兩個參數的構造方法 * @param int sizeThreshold,該參數設定記憶體緩衝區的大小,預設值為10K。當上傳檔案大於緩衝區大小時,fileupload組件將使用臨時檔案快取上傳檔案 * @param java.io.File repository,該參數指定臨時檔案目錄,預設值為System.getProperty("java.io.tmpdir"); * * 如果使用了無參的構造方法,則使用setSizeThreshold(int sizeThreshold),setRepository(java.io.File repository) * 方法手動進行設定 */ 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對象建立ServletFileUpload對象,並設定上傳檔案的大小 * * ServletFileUpload對象負責處理上傳的檔案資料,並將表單中每個輸入項封裝成一個FileItem * 該對象的常用方法有: * boolean isMultipartContent(request);判斷上傳表單是否為multipart/form-data類型 * List parseRequest(request);解析request對象,並把表單中的每一個輸入項封裝成一個fileItem 對象,並返回一個儲存了所有FileItem的list集合 * void setFileSizeMax(long filesizeMax);設定單個上傳檔案的最大值 * void setSizeMax(long sizeMax);設定上傳溫江總量的最大值 * void setHeaderEncoding();設定編碼格式,解決上傳檔案名稱亂碼問題 */ ServletFileUpload upload = new ServletFileUpload(factory); upload.setHeaderEncoding("utf-8");//設定編碼格式,解決上傳檔案名稱亂碼問題 /* * 3、調用ServletFileUpload.parseRequest方法解析request對象,得到一個儲存了所有上傳內容的List對象 */ List<FileItem> parseRequest=null; try { parseRequest = upload.parseRequest(request); } catch (FileUploadException e) { e.printStackTrace(); } /* * 4、對list進行迭代,每迭代一個FileItem對象,調用其isFormField方法判斷是否是檔案上傳 * true表示是普通表單欄位,則調用getFieldName、getString方法得到欄位名和欄位值 * false為上傳檔案,則調用getInputStream方法得到資料輸入流,從而讀取上傳資料 * * FileItem用來表示檔案上傳表單中的一個上傳檔案對象或者普通的表單對象 * 該對象常用方法有: * boolean isFormField();判斷FileItem是一個檔案上傳對象還是普通表單對象 * true表示是普通表單欄位, * 則調用getFieldName、getString方法得到欄位名和欄位值 * false為上傳檔案, * 則調用getName()獲得上傳檔案的檔案名稱,注意:有些瀏覽器會攜帶用戶端路徑,需要自己減除 * 調用getInputStream()方法得到資料輸入流,從而讀取上傳資料 * delete(); 表示在關閉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位長的全球唯一標識 filename = randomUUID.toString()+filename; /* * 設計一個目錄產生演算法,如果所用使用者上傳的檔案總數是億數量級的或更多,放在同一個目錄下回導致檔案索引非常慢, * 所以,設計一個目錄結構來分散存放檔案是非常有必要,且合理的 * 將UUID取雜湊演算法,散列到更小的範圍, * 將UUID的hashcode轉換為一個8位的8進位字串, * 從這個字串的第一位開始,每一個字元代表一級目錄,這樣就構建了一個八級目錄,每一級目錄中最多有16個子目錄 * 這無論對於伺服器還是作業系統都是非常高效的目錄結構 */ 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輸入資料流中讀取檔案,並寫入到伺服器 InputStream inputStream2 = fileItem.getInputStream(); //在伺服器端建立檔案 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>"); //關閉資源 bos.close(); inputStream2.close(); } } //注意Eclipse的上傳的檔案是儲存在項目的運行目錄,而不是workspace中的工程目錄裡。
二、檔案上傳需要特別注意的問題: (這些問題在上面的代碼中都提供了簡單的解決)
1、檔案存放的位置
為保證伺服器的安全,上傳檔案應儲存在應用程式的WEB-INF目錄下,或者不受WEB伺服器管理的目錄,如果使用者上傳一個帶有可執行代碼的檔案,如jsp檔案,根據拼接訪問路徑去訪問的話,可以在伺服器端做任何事情。
2、為防止多使用者上傳形同檔案名稱的檔案,而導致檔案覆蓋的情況發生,檔案上傳程式應保證上傳檔案具有唯一檔案名。
使用UUID + 使用者上傳檔案名稱的方式重新命名
關於UUID:
UUID(Universally Unique Identifier)通用唯一識別碼,是指在一台機器上產生的數字,它保證對在同一時空中的所有機器都是唯一的。按照開放軟體基金會(OSF)制定的標準計算,用到了乙太網路卡地址、納秒級時間、晶片ID碼和許多可能的數字。由以下幾部分的組合:當前日期和時間(UUID的第一個部分與時間有關,如果你在產生一個UUID之後,過幾秒又產生一個UUID,則第一個部分不同,其餘相同),時鐘序列,全域唯一的IEEE機器識別號(如果有網卡,從網卡獲得,沒有網卡以其他方式獲得),UUID的唯一缺陷在於產生的結果串會比較長。
是一個128位長的數字,一般用16進位表示。演算法的核心思想是結合機器的網卡、當地時間、一個隨即數來產生GUID。從理論上講,如果一台機器每秒產生10000000個GUID,則可以保證(機率意義上)3240年不重複。
從JDK1.5開始,產生UUID變成了一件簡單的事,以為JDK實現了UUID:
java.util.UUID,直接調用即可.
UUID uuid = UUID.randomUUID();
String s = UUID.randomUUID().toString();//用來產生資料庫的主鍵id非常不錯。。
UUID是由一個十六位的數字組成,表現出來的形式例如
550E8400-E29B-11D4-A716-446655440000
3、為防止單個目錄下檔案過多,影響檔案讀寫速度,處理上傳檔案的程式應該應根據可能的上傳總量,選擇合適的目錄結構產生演算法,將上傳檔案分散儲存。如使用hashcode方法構建多級目錄。
4、如果不同使用者都上傳了相同的檔案,那麼在伺服器端沒有必要儲存同一個檔案的很多分拷貝,這樣很浪費資源,應該設計演算法解決這種重複檔案的問題。
5、JSP技術原理自動實現了多線程。所以開發人員不需要考慮上傳檔案的多線程操作
三、檔案下載
<% 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組件簡化檔案上傳下載
以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。