JavaWeb之上传与下载
文件上傳概述:
1,文件上傳對頁面的要求:
必須使用表單,而不能是超鏈接
表單的method必須是post
表單的enctype必須是multipart/form-data
在表單中添加file表單字段,即<input type=”file”…/>比如:
<form action="${pageContext.request.contextPath }/XXXServlet" method="post" enctype="multipart/form-data">用戶名:<input type="text" name="username"/><br/>文件1:<input type="file" name="file1"/><br/>文件2:<input type="file" name="file2"/><br/><input type="submit" value="提交"/></form>?2,對比文件上傳表單與普通表單的區(qū)別
通過httpWatch查看“文件上傳表單”和“普通文本表單”的區(qū)別。
文件上傳表單的enctype=”multipart/form-data”,表示多部件表單數(shù)據(jù);
普通文本表單可以不設(shè)置enctype屬性:
當method=”post”時,enctype的默認值為application/x-www-form-urlencoded,表示使用url編碼正文;
當method=”get”時,enctype的默認值為null,沒有正文,所以就不需要enctype了
通過httpWatch測試,查看表單的請求數(shù)據(jù)正文,我們發(fā)現(xiàn)請求中只有文件名稱,而沒有文件內(nèi)容。也就是說,當表單的enctype不是multipart/form-data時,請求中不包含文件內(nèi)容,而只有文件的名稱,這說明普通文本表單中input:file與input:text沒什么區(qū)別了。
通過httpWatch測試,查看表單的請求數(shù)據(jù)正文部分,發(fā)現(xiàn)正文部分是由多個部件組成,每個部件對應(yīng)一個表單字段,每個部件都有自己的頭信息。頭信息下面是空行,空行下面是字段的正文部分。多個部件之間使用隨機生成的分隔線隔開。
文本字段的頭信息中只包含一條頭信息,即Content-Disposition,這個頭信息的值有兩個部分,第一部分是固定的,即form-data,第二部分為字段的名稱。在空行后面就是正文部分了,正文部分就是在文本框中填寫的內(nèi)容。
文件字段的頭信息中包含兩條頭信息,Content-Disposition和Content-Type。Content-Disposition中多出一個filename,它指定的是上傳的文件名稱。而Content-Type指定的是上傳文件的類型。文件字段的正文部分就是文件的內(nèi)容。
?
請注意,因為我們上傳的文件都是普通文本文件,即txt文件,所以在httpWatch中是可以正常顯示的,如果上傳的是exe、mp3等文件,那么在httpWatch看到的就是亂碼了
?
3,文件上傳對Servlet的要求
當提交的表單是文件上傳表單時,那么對Servlet也是有要求的。
首先我們要肯定一點,文件上傳表單的數(shù)據(jù)也是被封裝到request對象中的。
request.getParameter(String)方法獲取指定的表單字段字符內(nèi)容,但文件上傳表單已經(jīng)不在是字符內(nèi)容,而是字節(jié)內(nèi)容,所以失效。
這時可以使用request的getInputStream()方法獲取ServletInputStream對象,它是InputStream的子類,這個ServletInputStream對象對應(yīng)整個表單的正文部分(從第一個分隔線開始,到最后),這說明我們需要的解析流中的數(shù)據(jù)。當然解析它是很麻煩的一件事情,而Apache已經(jīng)幫我們提供了解析它的工具:commons-fileupload
需要使用的jar包
commons-fileupload.jar,核心包;最主要的工作就是幫我們解析request.getInputStream()
commons-io.jar,依賴包
Fileupload詳述
fileupload的核心類DiskFileItemFactory,ServletFileUpload, FileItem
使用fileupload組件的步驟如下:
1.????? 創(chuàng)建工廠類DiskFileItemFactory對象:DiskFileItemFactory factory = new DiskFileItemFactory()
2.????? 使用工廠創(chuàng)建解析器對象:ServletFileUpload fileUpload = new ServletFileUpload(factory)
3.????? 使用解析器來解析request對象:List<FileItem> list = fileUpload.parseRequest(request)
隆重介紹FileItem類,它才是我們最終要的結(jié)果。一個FileItem對象對應(yīng)一個表單項(表單字段)。一個表單中存在文件字段和普通字段,可以使用FileItem類的isFormField()方法來判斷表單字段是否為普通字段,如果不是普通字段,那么就是文件字段了
常用方法如下
String getName():獲取文件字段的文件名稱;
String getString():獲取字段的內(nèi)容,如果是文件字段,那么獲取的是文件內(nèi)容,當然上傳的文件必須是文本文件;
String getFieldName():獲取字段名稱,例如:<input type=”text” name=”username”/>,返回的是username;
String getContentType():獲取上傳的文件的類型,例如:text/plain。
int getSize():獲取上傳文件的大小;
boolean isFormField():判斷當前表單字段是否為普通文本字段,如果返回false,說明是文件字段;
InputStream getInputStream():獲取上傳文件對應(yīng)的輸入流;
void write(File):把上傳的文件保存到指定文件中
?我們先舉個例子
數(shù)顯在jsp文件,建立表單
<form action="${pageContext.request.contextPath }/AServlet" method="post" enctype="multipart/form-data">文件:<input type="file" name="file2"/><br/><input type="submit" value="提交"/></form>然后寫服務(wù)器端Servlet文件
public class AServlet extends HttpServlet {public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("成功");request.setCharacterEncoding("utf-8");//?也可以用fileUpload.setHeaderEncdoing(String)response.setContentType("text/html;charset=utf-8");DiskFileItemFactory factory = new DiskFileItemFactory();//可以設(shè)置臨時目錄和緩存大小,當上傳的文件超出了緩存的大小就會保存到臨時目錄ServletFileUpload fileUpload = new ServletFileUpload(factory);// 設(shè)置上傳的單個文件的上限為10KB,一定要在fileUploadRequest()方法之前調(diào)用,//超出上限,則拋出FileUploadBase.SizeLimitExceededException異常fileUpload.setFileSizeMax(1024 * 100);try {//一個FileItem對象對應(yīng)一個表單項(表單字段),一個表單中存在文件字段和普通字段List<FileItem> list = fileUpload.parseRequest(request);System.out.println("已經(jīng)獲取表單信息");for(FileItem fileItem:list) {if(fileItem.isFormField()) {//如果當前表單項為普通表單項String filename=fileItem.getFieldName();// 獲取當前表單項的字段名稱 System.out.println(filename);}else {//如果當前表單項不是普通表單項,說明就是文件字段String name = fileItem.getName();//獲取上傳文件的名稱// 如果上傳的文件名稱為空,即沒有指定上傳文件if(name == null || name.isEmpty()) {continue;}int index=name.lastIndexOf("\\");String path=name.substring(index+1);//得到上傳文件真實的文件名System.out.println("文件名稱為:"+path);// 獲取真實路徑,對應(yīng)${項目目錄}/upLoads,當然,這個目錄必須存在String savepath = "E:\\ProgramFiles\\apache-tomcat-9.0.11\\webapps\\Upload\\WEB-INF\\upLoads";//指定文件保存路徑//this.getServletContext().getRealPath("/WEB-INF/upLoads");//也可以通過這個來獲取文件真實路徑System.out.println("文件保存在"+savepath);//-----------------------------File f = new File(savepath);if(!f.exists()){//如果文件不存在f.mkdirs();//創(chuàng)建目錄,也就是文件夾 }File file = new File(savepath, path);if(!file.exists()){try {file.createNewFile();} catch (IOException e) {e.printStackTrace();}}//-----------------------------fileItem.write(file);// 把上傳文件保存到指定位置response.getWriter().print("上傳文件名:" + name + "<br/>");response.getWriter().print("上傳文件大小:" + fileItem.getSize() + "<br/>");response.getWriter().print("上傳文件類型:" + fileItem.getContentType() + "<br/>");}}}catch(Exception e) {throw new ServletException(e);}}文件上傳之細節(jié)
1 把上傳的文件放到WEB-INF目錄下
如果沒有把用戶上傳的文件存放到WEB-INF目錄下,那么用戶就可以通過瀏覽器直接訪問上傳的文件,這是非常危險的。
假如說用戶上傳了一個a.jsp文件,然后用戶在通過瀏覽器去訪問這個a.jsp文件,那么就會執(zhí)行a.jsp中的內(nèi)容,如果在a.jsp中有如下語句:Runtime.getRuntime().exec(“shutdown –s –t 1”);,那么就會關(guān)機
通常我們會在WEB-INF目錄下創(chuàng)建一個uploads目錄來存放上傳的文件,而在Servlet中找到這個目錄需要使用ServletContext的getRealPath(String)方法,例如在我的upload1項目中有如下語句:
ServletContext servletContext = this.getServletContext();
String savepath = servletContext.getRealPath(“/WEB-INF/uploads”);
?
其中savepath為:F:\tomcat\webapps\upload\WEB-INF\uploads
2 ? 文件名稱(完整路徑、文件名稱)
上傳文件名稱可能是完整路徑:
IE6獲取的上傳文件名稱是完整路徑,而其他瀏覽器獲取的上傳文件名稱只是文件名稱而已。瀏覽器差異的問題我們還是需要處理一下的。
| ?????????? String name = file1FileItem.getName(); ?????????? response.getWriter().print(name); |
?
使用不同瀏覽器測試,其中IE6就會返回上傳文件的完整路徑,不知道IE6在搞什么,這給我們帶來了很大的麻煩,就是需要處理這一問題。
處理這一問題也很簡單,無論是否為完整路徑,我們都去截取最后一個“\\”后面的內(nèi)容就可以了。
| ?????????? String name = file1FileItem.getName(); ?????????? int lastIndex = name.lastIndexOf("\\");//獲取最后一個“\”的位置 ?????????? if(lastIndex != -1) {//注意,如果不是完整路徑,那么就不會有“\”的存在。 ????????????? name = name.substring(lastIndex + 1);//獲取文件名稱 ?????????? } ?????????? response.getWriter().print(name); |
3 ? 中文亂碼問題
上傳文件名稱中包含中文:
當上傳的誰的名稱中包含中文時,需要設(shè)置編碼,commons-fileupload組件為我們提供了兩種設(shè)置編碼的方式:
request.setCharacterEncoding(String):這種方式是我們最為熟悉的方式了;
fileUpload.setHeaderEncdoing(String):這種方式的優(yōu)先級高與前一種。
上傳文件的文件內(nèi)容包含中文:
通常我們不需關(guān)心上傳文件的內(nèi)容,因為我們會把上傳文件保存到硬盤上!也就是說,文件原來是什么樣子,到服務(wù)器這邊還是什么樣子!
但是如果你有這樣的需求,非要在控制臺顯示上傳的文件內(nèi)容,那么你可以使用fileItem.getString(“utf-8”)來處理編碼。
文本文件內(nèi)容和普通表單項內(nèi)容使用FileItem類的getString(“utf-8”)來處理編碼。
?
4 上傳文件同名問題(文件重命名)
通常我們會把用戶上傳的文件保存到uploads目錄下,但如果用戶上傳了同名文件呢?這會出現(xiàn)覆蓋的現(xiàn)象。處理這一問題的手段是使用UUID生成唯一名稱,然后再使用“_”連接文件上傳的原始名稱。
例如用戶上傳的文件是“我的一寸照片.jpg”,在通過處理后,文件名稱為:“891b3881395f4175b969256a3f7b6e10_我的一寸照片.jpg”,這種手段不會使文件丟失擴展名,并且因為UUID的唯一性,上傳的文件同名,但在服務(wù)器端是不會出現(xiàn)同名問題的。
String uuid = CommonUtils.uuid();//生成uuid String filename = uuid + "_" + name;//新的文件名稱為uuid + 下劃線 + 原始名稱5 一個目錄不能存放過多的文件(存放目錄打散)
一個目錄下不應(yīng)該存放過多的文件,一般一個目錄存放1000個文件就是上限了,如果在多,那么打開目錄時就會很“卡”。你可以嘗試打印C:\WINDOWS\system32目錄,你會感覺到的。
也就是說,我們需要把上傳的文件放到不同的目錄中。但是也不能為每個上傳的文件一個目錄,這種方式會導(dǎo)致目錄過多。所以我們應(yīng)該采用某種算法來“打散”!
打散的方法有很多,例如使用日期來打散,每天生成一個目錄。也可以使用文件名的首字母來生成目錄,相同首字母的文件放到同一目錄下。
日期打散算法:如果某一天上傳的文件過多,那么也會出現(xiàn)一個目錄文件過多的情況;
首字母打散算法:如果文件名是中文的,因為中文過多,所以會導(dǎo)致目錄過多的現(xiàn)象。
?
我們這里使用hash算法來打散:
1.????? 獲取文件名稱的hashCode:int hCode = name.hashCode();;
2.????? 獲取hCode的低4位,然后轉(zhuǎn)換成16進制字符;
3.????? 獲取hCode的5~8位,然后轉(zhuǎn)換成16進制字符;
4.????? 使用這兩個16進制的字符生成目錄鏈。例如低4位字符為“5”
?
這種算法的好處是,在uploads目錄下最多生成16個目錄,而每個目錄下最多再生成16個目錄,即256個目錄,所有上傳的文件都放到這256個目錄下。如果每個目錄上限為1000個文件,那么一共可以保存256000個文件。
例如上傳文件名稱為:新建 文本文檔.txt,那么把“新建 文本文檔.txt”的哈希碼獲取到,再獲取哈希碼的低4位,和5~8位。假如低4位為:9,5~8位為1,那么文件的保存路徑為uploads/9/1/。
?
| ??? int hCode = name.hashCode();//獲取文件名的hashCode ??? //獲取hCode的低4位,并轉(zhuǎn)換成16進制字符串 ??? String dir1 = Integer.toHexString(hCode & 0xF); ??? //獲取hCode的低5~8位,并轉(zhuǎn)換成16進制字符串 ??? String dir2 = Integer.toHexString(hCode >>> 4 & 0xF); ??? //與文件保存目錄連接成完整路徑 ??? savepath = savepath + "/" + dir1 + "/" + dir2; ??? //因為這個路徑可能不存在,所以創(chuàng)建成File對象,再創(chuàng)建目錄鏈,確保目錄在保存文件之前已經(jīng)存在 ??? new File(savepath).mkdirs(); |
6 上傳的單個文件的大小限制
限制上傳文件的大小很簡單,ServletFileUpload類的setFileSizeMax(long)就可以了。參數(shù)就是上傳文件的上限字節(jié)數(shù),例如servletFileUpload.setFileSizeMax(1024*10)表示上限為10KB。
一旦上傳的文件超出了上限,那么就會拋出FileUploadBase.FileSizeLimitExceededException異常。我們可以在Servlet中獲取這個異常,然后向頁面輸出“上傳的文件超出限制”。
7 上傳文件的總大小限制
上傳文件的表單中可能允許上傳多個文件。
有時我們需要限制一個請求的大小。也就是說這個請求的最大字節(jié)數(shù)(所有表單項之和)!實現(xiàn)這一功能也很簡單,只需要調(diào)用ServletFileUpload類的setSizeMax(long)方法即可。
例如fileUpload.setSizeMax(1024 * 10);,顯示整個請求的上限為10KB。當請求大小超出10KB時,ServletFileUpload類的parseRequest()方法會拋出FileUploadBase.SizeLimitExceededException異常
8 緩存大小與臨時目錄
大家想一想,如果我上傳一個藍光電影,先把電影保存到內(nèi)存中,然后再通過內(nèi)存copy到服務(wù)器硬盤上,那么你的內(nèi)存能吃的消么?
所以fileupload組件不可能把文件都保存在內(nèi)存中,fileupload會判斷文件大小是否超出10KB,如果是那么就把文件保存到硬盤上,如果沒有超出,那么就保存在內(nèi)存中。
10KB是fileupload默認的值,我們可以來設(shè)置它。
當文件保存到硬盤時,fileupload是把文件保存到系統(tǒng)臨時目錄,當然你也可以去設(shè)置臨時目錄。
像這樣,fileItem.write(path(savepath, name));
?
轉(zhuǎn)載于:https://www.cnblogs.com/QianYue111/p/9840421.html
總結(jié)
以上是生活随笔為你收集整理的JavaWeb之上传与下载的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机术语 日语,常用日语计算机词汇~~
- 下一篇: iOS端如何实现带UI截屏分享