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()方法被指定了很多次。并且上述中有java.lang.Double.toString() 方法,作用是傳回此Double對象的字元串表示形式。而且傳回的不是小數點後2位,而是小數點後一位(注釋寫錯了)。
列印到控制台部分資料:
已讀: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();這句代碼是從哪裡獲得輸入的,檔案在複制的過程中,是怎樣進行流的轉換的,流是從哪裡去了哪裡?