在開發過程中文件的上傳下載很常用。這里簡單的總結一下:
1.文件上傳必須滿足的條件:
a、 頁面表單的method必須是post 因為get傳送的數據太小了
b、 頁面表單的enctype必須是multipart/form-data類型的
c、 表單中提供上傳輸入域
代碼細節: 客戶端表單中:<form enctype="multipart/form-data"/>
(如果沒有這個屬性,則服務端讀取的文件路徑會因為瀏覽器的不同而不同)
服務端ServletInputStream is=request.getInputStream();用流的方式獲取請求正文內容,進一步的解析。
2.上傳文件的細節:
(1)為什么設置表單類型為:multipart/form-data.是設置這個表單傳遞的不是key=value值。傳遞的是字節碼.
表單與請求的對應關系:
如上可以看出在設置表單類型為:multipart/form-data之后,在HTTP請求體中將你選擇的文件初始化為二進制,如上圖中的Cookie之下的一串的隨機字符串下的內容。
但注意,在標識文件(即一串隨機字符串)所分割出來的文件字節碼中有兩行特殊字符,即第一行內容文件頭和一行空行。之后的第三行才是二進制的文件內容。
所以在服務端接受客戶端上傳的文件時,獲取HTTP請求參數中的文件二進制時,要去掉前三行。
3.自己手工解析上傳的txt文件:
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
|
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 如果一個表單的類型是post且enctype為multipart/form-date * 則所有數據都是以二進制的方式向服務器上傳遞。 * 所以req.getParameter("xxx")永遠為null。 * 只可以通過req.getInputStream()來獲取數據,獲取正文的數據 * * @author wangxi * */ public class UpServlet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding( "UTF-8" ); String txt = req.getParameter( "txt" ); //返回的是null System.err.println( "txt is :" +txt); System.err.println( "=========================================" ); InputStream in = req.getInputStream(); // byte[] b= new byte[1024]; // int len = 0; // while((len=in.read(b))!=-1){ // String s = new String(b,0,len); // System.err.print(s); // } BufferedReader br = new BufferedReader( new InputStreamReader(in)); String firstLine = br.readLine(); //讀取第一行,且第一行是分隔符號,即隨機字符串 String fileName = br.readLine(); //第二行文件信息,從中截取出文件名 fileName = fileName.substring(fileName.lastIndexOf( "\\" )+ 1 ); // xxxx.txt" fileName = fileName.substring( 0 ,fileName.length()- 1 ); br.readLine(); br.readLine(); String data = null ; //獲取當前項目的運行路徑 String projectPath = getServletContext().getRealPath( "/up" ); PrintWriter out = new PrintWriter(projectPath+ "/" +fileName); while ((data=br.readLine())!= null ){ if (data.equals(firstLine+ "--" )){ break ; } out.println(data); } out.close(); } } |
4.使用apache-fileupload處理文件上傳:
框架:是指將用戶經常處理的業務進行一個代碼封裝。讓用戶可以方便的調用。
目前文件上傳的(框架)組件:
Apache—-fileupload -
Orialiy – COS – 2008() -
Jsp-smart-upload – 200M。
用fileupload上傳文件:
需要導入第三方包:
Apache-fileupload.jar – 文件上傳核心包。
Apache-commons-io.jar – 這個包是fileupload的依賴包。同時又是一個工具包。
核心類:
DiskFileItemFactory – 設置磁盤空間,保存臨時文件。只是一個具類。
ServletFileUpload - 文件上傳的核心類,此類接收request,并解析reqeust。
ServletfileUpload.parseRequest(requdest) - List<FileItem>
注:一個FileItem就是一個標識的開始:---------243243242342 到 ------------------245243523452—就是一個FileItem
第一步:導入包:
第二步:書寫一個servlet完成doPost方法
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
|
/** * DiskFileItemFactory構造的兩個參數 * 第一個參數:sizeThreadHold - 設置緩存(內存)保存多少字節數據,默認為10K * 如果一個文件沒有大于10K,則直接使用內存直接保存成文件就可以了。 * 如果一個文件大于10K,就需要將文件先保存到臨時目錄中去。 * 第二個參數 File 是指臨時目錄位置 * */ public class Up2Servlet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding( "UTf-8" ); //獲取項目的路徑 String path = getServletContext().getRealPath( "/up" ); //第一步聲明diskfileitemfactory工廠類,用于在指的磁盤上設置一個臨時目錄 DiskFileItemFactory disk = new DiskFileItemFactory( 1024 * 10 , new File( "/home/wang/" )); //第二步:聲明ServletFileUpoload,接收上面的臨時目錄 ServletFileUpload up = new ServletFileUpload(disk); //第三步:解析request try { List<FileItem> list = up.parseRequest(req); //如果就一個文件 FileItem file = list.get( 0 ); //獲取文件名,帶路徑 String fileName = file.getName(); fileName = fileName.substring(fileName.lastIndexOf( "\\" )+ 1 ); //獲取文件的類型 String fileType = file.getContentType(); //獲取文件的字節碼 InputStream in = file.getInputStream(); //聲明輸出字節流 OutputStream out = new FileOutputStream(path+ "/" +fileName); //文件copy byte [] b = new byte [ 1024 ]; int len = 0 ; while ((len=in.read(b))!=- 1 ){ out.write(b, 0 ,len); } out.close(); long size = file.getInputStream().available(); //刪除上傳的臨時文件 file.delete(); //顯示數據 resp.setContentType( "text/html;charset=UTf-8" ); PrintWriter op = resp.getWriter(); op.print( "文件上傳成功<br/>文件名:" +fileName); op.print( "<br/>文件類型:" +fileType); op.print( "<br/>文件大小(bytes)" +size); } catch (Exception e) { e.printStackTrace(); } } } |
5.使用該框架上傳多個文件:
第一步:修改頁面的表單為多個input type=”file”
1
2
3
4
5
|
< form action="<c:url value = '/Up3Servlet' />" method="post" enctype="multipart/form-data"> File1:< input type = "file" name = "txt" >< br /> File2:< input type = "file" name = "txt" >< br /> < input type = "submit" /> </ form > |
第二步:遍歷list
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
|
public class Up3Servlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding( "UTF-8" ); String path = getServletContext().getRealPath( "/up" ); //聲明disk DiskFileItemFactory disk = new DiskFileItemFactory(); disk.setSizeThreshold( 1024 * 1024 ); disk.setRepository( new File( "d:/a" )); //聲明解析requst的servlet ServletFileUpload up = new ServletFileUpload(disk); try { //解析requst List<FileItem> list = up.parseRequest(request); //聲明一個list<map>封裝上傳的文件的數據 List<Map<String,String>> ups = new ArrayList<Map<String,String>>(); for (FileItem file:list){ Map<String,String> mm = new HashMap<String, String>(); //獲取文件名 String fileName = file.getName(); fileName = fileName.substring(fileName.lastIndexOf( "\\" )+ 1 ); String fileType = file.getContentType(); InputStream in = file.getInputStream(); int size = in.available(); //使用工具類 FileUtils.copyInputStreamToFile(in, new File(path+ "/" +fileName)); mm.put( "fileName" ,fileName); mm.put( "fileType" ,fileType); mm.put( "size" , "" +size); ups.add(mm); file.delete(); } request.setAttribute( "ups" ,ups); //轉發 request.getRequestDispatcher( "/jsps/show.jsp" ).forward(request, response); } catch (Exception e){ e.printStackTrace(); } } } |
如上就是上傳文件的常用做法。現在我們在來看看fileupload的其他查用API.
判斷一個fileItem是否是file(type=file)對象或是text(type=text|checkbox|radio)對象:
boolean isFormField() 如果是text|checkbox|radio|select這個值就是true.
6.處理帶說明信息的圖片
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
|
public class UpDescServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding( "UTF-8" ); //可以獲取中文的文件名 String path = getServletContext().getRealPath( "/up" ); DiskFileItemFactory disk = new DiskFileItemFactory(); disk.setRepository( new File( "d:/a" )); try { ServletFileUpload up = new ServletFileUpload(disk); List<FileItem> list = up.parseRequest(request); for (FileItem file:list){ //第一步:判斷是否是普通的表單項 if (file.isFormField()){ String fileName = file.getFieldName(); //<input type="text" name="desc">=desc String value = file.getString( "UTF-8" ); //默認以ISO方式讀取數據 System.err.println(fileName+ "=" +value); } else { //說明是一個文件 String fileName = file.getName(); fileName = fileName.substring(fileName.lastIndexOf( "\\" )+ 1 ); file.write( new File(path+ "/" +fileName)); System.err.println( "文件名是:" +fileName); System.err.println( "文件大小是:" +file.getSize()); file.delete(); } } } catch (Exception e){ e.printStackTrace(); } } } |
7.文件上傳的性能提升
在解析request獲取FileItem的集合的時候,使用:
FileItemIterator it= up.getItemIterator(request);
比使用
List<FileItem> list = up.parseRequest(request);
性能上要好的多。
示例代碼:
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
|
public class FastServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding( "UTF-8" ); String path = getServletContext().getRealPath( "/up" ); DiskFileItemFactory disk = new DiskFileItemFactory(); disk.setRepository( new File( "d:/a" )); try { ServletFileUpload up = new ServletFileUpload(disk); //以下是迭代器模式 FileItemIterator it= up.getItemIterator(request); while (it.hasNext()){ FileItemStream item = it.next(); String fileName = item.getName(); fileName=fileName.substring(fileName.lastIndexOf( "\\" )+ 1 ); InputStream in = item.openStream(); FileUtils.copyInputStreamToFile(in, new File(path+ "/" +fileName)); } } catch (Exception e){ e.printStackTrace(); } } } |
8.文件的下載
既可以是get也可以是post。
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
|
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding( "UTF-8" ); String name = req.getParameter( "name" ); //第一步:設置響應的類型 resp.setContentType( "application/force-download" ); //第二讀取文件 String path = getServletContext().getRealPath( "/up/" +name); InputStream in = new FileInputStream(path); //設置響應頭 //對文件名進行url編碼 name = URLEncoder.encode(name, "UTF-8" ); resp.setHeader( "Content-Disposition" , "attachment;filename=" +name); resp.setContentLength(in.available()); //第三步:開始文件copy OutputStream out = resp.getOutputStream(); byte [] b = new byte [ 1024 ]; int len = 0 ; while ((len=in.read(b))!=- 1 ){ out.write(b, 0 ,len); } out.close(); in.close(); } 在使用J2EE流行框架時 |
使用框架內部封裝好的來完成上傳下載更為簡單:
Struts2完成上傳.
在使用Struts2進行開發時,導入的jar包不難發現存在 commons-fileupload-1.3.1.jar 包。通過上面的學習我們已經可以使用它進行文件的上傳下載了。但Struts2在進行了進一步的封裝。
view
1
2
3
4
5
6
7
|
< form action = "fileUpload.action" method = "post" enctype = "multipart/form-data" > username: < input type = "text" name = "username" >< br > file: < input type = "file" name = "file" >< br > < input type = "submit" value = "submit" > </ form > |
Controller
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
|
public class FileUploadAction extends ActionSupport { private String username; //注意,file并不是指前端jsp上傳過來的文件本身,而是文件上傳過來存放在臨時文件夾下面的文件 private File file; //提交過來的file的名字 //struts會自動截取上次文件的名字注入給該屬性 private String fileFileName; //getter和setter此時為了節約篇幅省掉 @Override public String execute() throws Exception { //保存上傳文件的路徑 String root = ServletActionContext.getServletContext().getRealPath( "/upload" ); //獲取臨時文件輸入流 InputStream is = new FileInputStream(file); //輸出文件 OutputStream os = new FileOutputStream( new File(root, fileFileName)); //打印出上傳的文件的文件名 System.out.println( "fileFileName: " + fileFileName); // 因為file是存放在臨時文件夾的文件,我們可以將其文件名和文件路徑打印出來,看和之前的fileFileName是否相同 System.out.println( "file: " + file.getName()); System.out.println( "file: " + file.getPath()); byte [] buffer = new byte [ 1024 ]; int length = 0 ; while (- 1 != (length = is.read(buffer, 0 , buffer.length))) { os.write(buffer); } os.close(); is.close(); return SUCCESS; } } |
首先我們要清楚一點,這里的file并不是真正指代jsp上傳過來的文件,當文件上傳過來時,struts2首先會尋找struts.multipart.saveDir(這個是在default.properties里面有)這個name所指定的存放位置(默認是空),我們可以在我們項目的struts2中來指定這個臨時文件存放位置。
<constant name="struts.multipart.saveDir" value="/repository"/>
如果沒有設置struts.multipart.saveDir,那么將默認使用javax.servlet.context.tempdir指定的地址,javax.servlet.context.tempdir的值是由服務器來確定的,例如:假如我的web工程的context是abc,服務器使用Tomcat,那么savePath就應該是%TOMCAT_HOME%/work/Catalina/localhost/abc_,臨時文件的名稱類似于upload__1a156008_1373a8615dd__8000_00000001.tmp,每次上傳的臨時文件名可能不同,但是大致是這種樣式。而且如果是使用Eclipse中的Servers里面配置Tomcat并啟動的話,那么上面地址中的%TOMCAT_HOME%將不會是系統中的實際Tomcat根目錄,而會是Eclipse給它指定的地址,例如我本地的地址是這樣的:/home/wang/EclipseJavaCode/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/abc/upload__1a156008_1373a8615dd__8000_00000001.tmp。
Struts2完成下載.
struts2的文件下載更簡單,就是定義一個輸入流,然后將文件寫到輸入流里面就行,關鍵配置還是在struts.xml這個配置文件里配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class FileDownloadAction extends ActionSupport { //要下載文件在服務器上的路徑 private String path; //要下載文件的文件名 private String downloadFileName; //寫入getter和setter public InputStream getDownloadFile() { return ServletActionContext.getServletContext().getResourceAsStream(path); } @Override public String execute() throws Exception { //當前action默認在valuestack的棧頂 setDownloadFileName(xxx); return SUCCESS; } } |
action只是定義了一個輸入流downloadFile,然后為其提供getter方法就行,接下來我們看看struts.xml的配置文件:
1
2
3
4
5
6
|
< action name = "fileDownload" class = "com.struts2.FileDownloadAction" > < result name = "download" type = "stream" > < param name = "contentDisposition" >attachment;fileName="${downloadFileName}"</ param > < param name = "inputName" >downloadFile</ param > </ result > </ action > |
struts.xml配置文件有幾個地方我們要注意,首先是result的類型,type一定要定義成stream類型_,告訴action這是文件下載的result,result元素里面一般還有param子元素,這個是用來設定文件下載時的參數,inputName這個屬性就是得到action中的文件輸入流,名字一定要和action中的輸入流屬性名字相同,然后就是contentDisposition屬性,這個屬性一般用來指定我們希望通過怎么樣的方式來處理下載的文件,如果值是attachment,則會彈出一個下載框,讓用戶選擇是否下載,如果不設定這個值,那么瀏覽器會首先查看自己能否打開下載的文件,如果能,就會直接打開所下載的文件,(這當然不是我們所需要的),另外一個值就是filename這個就是文件在下載時所提示的文件下載名字。在配置完這些信息后,我們就能過實現文件的下載功能了。
SpringMVC完成上傳:
view于struts2示例中的完全一樣。此出不在寫出。
Controller:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Controller @RequestMapping (value= "fileOperate" ) public class FileOperateAction { @RequestMapping (value= "upload" ) public String upload(HttpServletRequest request, @RequestParam ( "file" ) MultipartFile photoFile){ //上傳文件保存的路徑 String dir = request.getSession().getServletContext().getRealPath( "/" )+ "upload" ; //原始的文件名 String fileName = photoFile.getOriginalFilename(); //獲取文件擴展名 String extName = fileName.substring(fileName.lastIndexOf( "." )); //防止文件名沖突,把名字小小修改一下 fileName = fileName.substring( 0 ,fileName.lastIndexOf( "." )) + System.nanoTime() + extName; FileUtils.writeByteArrayToFile( new File(dir,fileName),photoFile.getBytes()); return "success" ; } } |
SpringMVC完成下載:
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
|
@RequestMapping ( "/download" ) public String download(String fileName, HttpServletRequest request, HttpServletResponse response) { response.setCharacterEncoding( "utf-8" ); response.setContentType( "multipart/form-data" ); response.setHeader( "Content-Disposition" , "attachment;fileName=" + fileName); try { InputStream inputStream = new FileInputStream( new File(文件的路徑); OutputStream os = response.getOutputStream(); byte [] b = new byte [ 2048 ]; int length; while ((length = inputStream.read(b)) > 0 ) { os.write(b, 0 , length); } // 這里主要關閉。 os.close(); inputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // 返回值要注意,要不然就出現下面這句錯誤! //java+getOutputStream() has already been called for this response return null ; } |
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。