基於DiskFileItemFactory實現的檔案上傳和進度監聽
10月1假期圓滿結束,在家裡躺了5天,啥也沒幹,回來的時候家裡還給我拿了螃蟹什麼的,昨天就讓我吃了,哈哈哈。
這幾天一直想實現以下檔案的上傳和檔案上傳時顯示進度條,看了幾個部落格,照著自己敲了一下,將自己不明白的地方也查了百度。但仍然有幾處不太理解,我會將不明白的地方在本文的下方寫出,希望大家能幫我解答一下。
下面就是實現的過程。
一.web.xml檔案
我們後臺使用的是servlet來處理使用者請求,那麼就要在web.xml檔案中配置我們的servlet。在表單提交時,我們可以在action中填寫對應的setvlet對映名,就可以將表單資料傳送到servlet中。
<?xml version="1.0" encoding="UTF-8"?> <web-app> <!-- 配置上傳檔案所需要的sevlet --> <servlet> <servlet-name>UploadServlet</servlet-name> <servlet-class>com.java.controller.UploadServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UploadServlet</servlet-name> <url-pattern>/UploadServlet.do</url-pattern> </servlet-mapping> </web-app>
二.所需要的jar包
後臺使用servlet,那麼我們就需要servlet-api.jar,並且由DiskFileItemFactory,我們還需另外兩個jar包。所需要的3個jar包如下。
三.UploadServlet
這個servlet就是用來處理使用者上傳檔案的請求的,程式碼如下:
package com.java.controller; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.List; 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.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FileUtils; public class UploadServlet extends HttpServlet { /*這個class是一個servlet,那麼就能夠通過web.xml檔案中配置的對映路徑訪問。 *訪問的時候按照請求的方式,來決定用哪一個方法來處理請求 * */ //重寫doGet()方法和doPost()方法。 @Override public void doGet(HttpServletRequest request,HttpServletResponse response) { } @Override public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException { //首先檢查表單是否支援檔案上傳,檢查請求頭Content-type:multipart/form-data。 if(!ServletFileUpload.isMultipartContent(request)) { throw new RuntimeException("該表單不支援檔案上傳"); } /*設定響應體屬性 response.setContentType("text/html;charset=UTF-8"); */ //建立核心工廠 DiskFileItemFactory diskFileItemFactory=new DiskFileItemFactory(); //為這個製造上傳檔案物件的工廠設定屬性。 diskFileItemFactory.setSizeThreshold(1024*3); //設定工廠的記憶體緩衝區大小,預設為10kb。 diskFileItemFactory.setRepository(new File("E:\\FileUpLoad")); //設定工廠的臨時檔案目錄:當上傳檔案大於記憶體緩衝區時,使用臨時檔案目錄快取上傳的檔案。 //通過工廠建立檔案上傳解析器,核心類 ServletFileUpload servletFileUpload=new ServletFileUpload(diskFileItemFactory); //為這個上傳解析器設定屬性 servletFileUpload.setSizeMax(1024*1024*2); //設定上傳資料的最大值。所有檔案。 servletFileUpload.setFileSizeMax(1024*1024*2); //設定上傳檔案的最大值。單個檔案。 servletFileUpload.setHeaderEncoding("UTF-8"); //在記憶體中儲存的資料是以某種字符集編碼的位元組陣列? //設定檔案上傳的監聽器 servletFileUpload.setProgressListener(new MyProgressListener(request)); //要在資料解析之前進行監聽? //進行資料的解析。將表單中每個輸入項(包括表單普通標籤和表單檔案上傳標籤)封裝成一個FileItem物件。 try { List<FileItem> fileItemList=servletFileUpload.parseRequest(request); for(FileItem fileItem:fileItemList) { //判斷是表單普通標籤,還是表單檔案上傳標籤。 if(fileItem.isFormField()) { //表單普通標籤 String fieldName=fileItem.getFieldName(); //得到標籤的name名。 String fieldValue=fileItem.getString("utf-8"); //得到標籤的value值。getString()用於解決亂碼問題? System.out.println("標籤名為:"+fieldName+",value值為:"+fieldValue); }else { //為表單的檔案上傳標籤 String fileName=fileItem.getName(); //得到上傳檔案的檔名。IE瀏覽器的得到的是全路徑 : C:\Users\xxx\Desktop\abc.txt ; 其他瀏覽器得到的只是檔名 : abc.txt String fieldName=fileItem.getFieldName(); String fieldValue=fileItem.getString("utf-8"); //如果要列印檔案值,那麼就會在控制檯中列印亂碼(許多)。 System.out.println("標籤名為:"+fieldName+",value值為:"+",檔名為:"+fileName); //進行檔案的上傳(將檔案儲存到伺服器中【檔案的拷貝】)。 InputStream inputStream=fileItem.getInputStream(); //這個輸入流是從哪裡輸入? String parentDir=this.getServletContext().getRealPath("/WEB-INF/upload/"+fileName); //檔案要儲存的目的目錄。和getServletContextPath()有什麼區別?ServletContext()是什麼? String textDir=this.getServletContext().getContextPath(); System.out.println("RealPath:"+parentDir+",ContextPath:"+textDir); //進行檔案的儲存(複製) File file=new File(parentDir); FileUtils.copyInputStreamToFile(inputStream, file); //在儲存的時候,如果資料夾和檔案不存在,則建立資料夾和檔案。 //刪除臨時檔案 //fileItem.delete(); //關閉流資源 inputStream.close(); /*圖片的大小和佔用空間的區別 *b,kb等的轉換。 * * * */ } } } catch (FileUploadException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
這個class是一個servlet,那麼就能夠通過web.xml檔案中配置的對映路徑訪問。訪問的時候按照請求的方式,來決定用哪一個方法來處理請求 ,我們需要重寫其中的doGet()方法和doPost()方法。
在doPost()方法中,我們首先檢查表單是否支援檔案上傳,檢查請求頭Content-type:multipart/form-data。如果是則支援上傳。
然後我們建立核心工廠類DiskFileItemFactory,並給它設定一些基本屬性,比如記憶體緩衝大小和指定臨時檔案。
通過核心工廠類我們能夠得到與之相關聯的核心類ServletFileUpload。當然了,我們也要給這個核心類設定一些屬性,例如上傳檔案的總共大小,單個檔案上傳的總共大小。如果所需上傳檔案資料大於最大值,那麼就會發生異常。
設定完核心工廠類,我們就可以設定進度監聽。
監聽完成後,說明檔案下載完成,我們就可以對錶單提交過來各個資料進行解析。我們解析傳遞過來的request請求,解析完成後會得到一個FileItem集合。在這裡,我們可以簡單的理解為這個FileItem集合就是表單中所有表單的集合。我們通過 遍歷這個集合就可以得到每個標籤的name值,value值等信心。
我們只對檔案型別的資訊感興趣,所以我們只需要關注表單上傳檔案標籤type=file。所以呢,在遍歷的時候,我們通過fileItem.isFormField()方法來辨別出一個標籤是表單普通標籤,還是表單上傳檔案標籤。如果是上傳檔案標籤,那麼那麼我們可以的到這個標籤的name值,和上傳檔案的檔名。這裡需要注意,這個標籤的value代表的應該是檔案本身,因為我列印value的時候,在控制檯輸出了很多亂碼,亂碼代表的應該就是檔案。
我們通過getRealPath()來得到專案相對於伺服器的實際路徑,而getContextPath()得到的是專案的相對路徑,也就是專案名本身。
最後我們通過FileUtils.copyInputStreamToFile(inputStream, file)方法來將檔案拷貝到指定地方。
四.index頁面
<%@ page contentType="text/html;charset=UTF-8" %>
<HTML>
<HEAD>
<title>上傳檔案</title>
</HEAD>
<body>
<div>
<!-- 假如我們要以表單的形式進行檔案上傳,那麼表單的提交方式必須為post,並且要設定表單的 enctype 屬性值為 multipart/form-data。
上傳檔案時,表單提交要使用post方式,因為這種請求是放到httpbody中,傳輸的檔案無限制。
只有使用enctype="multipart/form-data",表單才會把檔案的內容編碼到HTML請求中,否則只會把所要上傳的檔名放到httpbody,
而不是把檔案內容編碼後放到httpbody中。
檔案的上傳:我們把需要上傳的資源,傳送給伺服器,在伺服器上儲存下來。
-->
<form action="UploadServlet.do" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>姓名</td>
<td><input name="name" type="text"></td>
<td>別名</td>
<td><input name="namex" type="text"></td>
<td>年齡</td>
<td><select id="selectAge" name="selectAge">
<option value="0">----請選擇年齡----</option>
<option value="23">23</option>
<option value="24">24</option>
<option value="25">25</option>
</select>
</td>
<td>上傳檔案</td>
<td><input name="fileUpLoad" type="file"></td>
<td><input type="submit" value="上傳"></td>
</tr>
</table>
</form>
</div>
</body>
</HTML>
假如我們要以表單的形式進行檔案上傳,那麼表單的提交方式必須為post,並且要設定表單的 enctype 屬性值為 multipart/form-data。 上傳檔案時,表單提交要使用post方式,因為這種請求是放到httpbody中,傳輸的檔案無限制。 只有使用enctype="multipart/form-data",表單才會把檔案的內容編碼到HTML請求中,否則只會把所要上傳的檔名放到httpbody,而不是把檔案內容編碼後放到httpbody中。 檔案的上傳:我們把需要上傳的資源,傳送給伺服器,在伺服器上儲存下來。
五.MyProgressListener
這個檔案就是從來監聽檔案上傳的進度的。
package com.java.controller;
import org.apache.commons.fileupload.ProgressListener;
import java.text.NumberFormat;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.ProgressListener;
public class MyProgressListener implements ProgressListener {
//定義session,擴大request作用域為session。
private HttpSession httpSession;
public MyProgressListener(HttpServletRequest request) {
httpSession=request.getSession();
}
/**
* arg0表示已經讀到的內容,
* arg1表示需要讀取的資料總數,
* arg2表示讀取當前的列表項
*/
@Override
public void update(long arg0, long arg1, int arg2) {
//進行資料的格式化,將位元組B轉換為MB。
double read=arg0/1024.0/1024.0;
double totle=arg1/1024.0/1024.0;
double percent=read/totle; //以讀取的%比。
String read_format=dataFormat(read);
String total_format=dataFormat(totle);
NumberFormat numberFormat=NumberFormat.getPercentInstance();
String percentReault=numberFormat.format(percent);
//將資訊存入session
httpSession.setAttribute("progress", percentReault);
System.out.println("已讀:"+arg0+"----總數:"+arg1+"----當前Item:"+arg2);
System.out.println("已讀:"+read+"MB"+"----總數:"+totle+"MB"+"----讀取進度:"+percent+"%");
System.out.println("已讀:"+read_format+"----總數:"+total_format+"----讀取進度:"+percentReault);
System.out.println("------------------------");
}
public String dataFormat(double data){
//進行資料的個格式化,
String formdata="";
if (data>=1024*1024) { //大於等於1M
formdata=Double.toString(data/1024/1024)+"M";
}else if(data>=1024){//大於等於1KB
formdata=Double.toString(data/1024)+"KB";
}else{//小於1KB
formdata=Double.toString(data)+"byte";
}
return formdata.substring(0, formdata.indexOf(".")+2); //取小數點後兩位。
/*double型別的資料有幾個小數點。
*該方法中的其他方法的作用。
* */
}
}
通過列印到控制檯的資料,我們可以看到update()方法被指定了很多次。
列印到控制檯部分資料:
已讀:806162----總數:827192----當前Item:4
已讀:0.7688159942626953MB----總數:0.7888717651367188MB----讀取進度:0.9745766400061896%
已讀:0.7----總數:0.7----讀取進度:97%
------------------------
已讀:810216----總數:827192----當前Item:4
已讀:0.7726821899414062MB----總數:0.7888717651367188MB----讀取進度:0.9794775578100369%
已讀:0.7----總數:0.7----讀取進度:98%
------------------------
已讀:810300----總數:827192----當前Item:4
已讀:0.7727622985839844MB----總數:0.7888717651367188MB----讀取進度:0.9795791061809108%
已讀:0.7----總數:0.7----讀取進度:98%
------------------------
已讀:814354----總數:827192----當前Item:4
已讀:0.7766284942626953MB----總數:0.7888717651367188MB----讀取進度:0.9844800239847581%
已讀:0.7----總數:0.7----讀取進度:98%
------------------------
已讀:818408----總數:827192----當前Item:4
已讀:0.7804946899414062MB----總數:0.7888717651367188MB----讀取進度:0.9893809417886053%
已讀:0.7----總數:0.7----讀取進度:99%
------------------------
已讀:818492----總數:827192----當前Item:4
已讀:0.7805747985839844MB----總數:0.7888717651367188MB----讀取進度:0.9894824901594793%
已讀:0.7----總數:0.7----讀取進度:99%
------------------------
已讀:822546----總數:827192----當前Item:4
已讀:0.7844409942626953MB----總數:0.7888717651367188MB----讀取進度:0.9943834079633266%
已讀:0.7----總數:0.7----讀取進度:99%
------------------------
已讀:826600----總數:827192----當前Item:4
已讀:0.7883071899414062MB----總數:0.7888717651367188MB----讀取進度:0.9992843257671737%
已讀:0.7----總數:0.7----讀取進度:100%
------------------------
已讀:826684----總數:827192----當前Item:4
已讀:0.7883872985839844MB----總數:0.7888717651367188MB----讀取進度:0.9993858741380478%
已讀:0.7----總數:0.7----讀取進度:100%
------------------------
已讀:827192----總數:827192----當前Item:4
已讀:0.7888717651367188MB----總數:0.7888717651367188MB----讀取進度:1.0%
已讀:0.7----總數:0.7----讀取進度:100%
------------------------
標籤名為:name,value值為:qiao
標籤名為:namex,value值為:qiaox
標籤名為:selectAge,value值為:24
標籤名為:fileUpLoad,value值為:,檔名為:桌布.png
RealPath:F:\Development\DevelopmentTool\apache-tomcat-9.0.4-windows-x64\apache-tomcat-9.0.4\webapps\FileUploadAndProgressBar\WEB-INF\upload\桌布.png,ContextPath:/FileUploadAndProgressBar
六.不明白的問題
在給工廠設定記憶體快取時,如果檔案大小大於記憶體快取,是否將檔案全部快取到臨時檔案中?還是將記憶體快取佔滿後,其它的資料在放到臨時檔案中?
設定相應體的屬性 response.setContentType("text/html;charset=UTF-8");的意義和作用是什麼?
給檔案解析器設定servletFileUpload.setHeaderEncoding("UTF-8");的意義和作用是什麼?
進度監聽是否需要在解析request請求之前進行監聽?這個我猜是,因為列印的時候先把檔案下載完才進行解析的。
InputStream inputStream=fileItem.getInputStream();這句程式碼是從哪裡獲得輸入的,檔案在複製的過程中,是怎樣進行流的轉換的,流是從哪裡去了哪裡?