在開(kāi)發(fā)過(guò)程中文件的上傳下載很常用。這里簡(jiǎn)單的總結(jié)一下:
1.文件上傳必須滿足的條件:
a、 頁(yè)面表單的method必須是post 因?yàn)間et傳送的數(shù)據(jù)太小了
b、 頁(yè)面表單的enctype必須是multipart/form-data類(lèi)型的
c、 表單中提供上傳輸入域
代碼細(xì)節(jié): 客戶端表單中:<form enctype="multipart/form-data"/>
(如果沒(méi)有這個(gè)屬性,則服務(wù)端讀取的文件路徑會(huì)因?yàn)闉g覽器的不同而不同)
服務(wù)端ServletInputStream is=request.getInputStream();用流的方式獲取請(qǐng)求正文內(nèi)容,進(jìn)一步的解析。
2.上傳文件的細(xì)節(jié):
(1)為什么設(shè)置表單類(lèi)型為:multipart/form-data.是設(shè)置這個(gè)表單傳遞的不是key=value值。傳遞的是字節(jié)碼.
表單與請(qǐng)求的對(duì)應(yīng)關(guān)系:
如上可以看出在設(shè)置表單類(lèi)型為:multipart/form-data之后,在HTTP請(qǐng)求體中將你選擇的文件初始化為二進(jìn)制,如上圖中的Cookie之下的一串的隨機(jī)字符串下的內(nèi)容。
但注意,在標(biāo)識(shí)文件(即一串隨機(jī)字符串)所分割出來(lái)的文件字節(jié)碼中有兩行特殊字符,即第一行內(nèi)容文件頭和一行空行。之后的第三行才是二進(jìn)制的文件內(nèi)容。
所以在服務(wù)端接受客戶端上傳的文件時(shí),獲取HTTP請(qǐng)求參數(shù)中的文件二進(jìn)制時(shí),要去掉前三行。
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; /** * 如果一個(gè)表單的類(lèi)型是post且enctype為multipart/form-date * 則所有數(shù)據(jù)都是以二進(jìn)制的方式向服務(wù)器上傳遞。 * 所以req.getParameter("xxx")永遠(yuǎn)為null。 * 只可以通過(guò)req.getInputStream()來(lái)獲取數(shù)據(jù),獲取正文的數(shù)據(jù) * * @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(); //讀取第一行,且第一行是分隔符號(hào),即隨機(jī)字符串 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 ; //獲取當(dāng)前項(xiàng)目的運(yùn)行路徑 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處理文件上傳:
框架:是指將用戶經(jīng)常處理的業(yè)務(wù)進(jìn)行一個(gè)代碼封裝。讓用戶可以方便的調(diào)用。
目前文件上傳的(框架)組件:
Apache—-fileupload -
Orialiy – COS – 2008() -
Jsp-smart-upload – 200M。
用fileupload上傳文件:
需要導(dǎo)入第三方包:
Apache-fileupload.jar – 文件上傳核心包。
Apache-commons-io.jar – 這個(gè)包是fileupload的依賴(lài)包。同時(shí)又是一個(gè)工具包。
核心類(lèi):
DiskFileItemFactory – 設(shè)置磁盤(pán)空間,保存臨時(shí)文件。只是一個(gè)具類(lèi)。
ServletFileUpload - 文件上傳的核心類(lèi),此類(lèi)接收request,并解析reqeust。
ServletfileUpload.parseRequest(requdest) - List<FileItem>
注:一個(gè)FileItem就是一個(gè)標(biāo)識(shí)的開(kāi)始:---------243243242342 到 ------------------245243523452—就是一個(gè)FileItem
第一步:導(dǎo)入包:
第二步:書(shū)寫(xiě)一個(gè)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構(gòu)造的兩個(gè)參數(shù) * 第一個(gè)參數(shù):sizeThreadHold - 設(shè)置緩存(內(nèi)存)保存多少字節(jié)數(shù)據(jù),默認(rèn)為10K * 如果一個(gè)文件沒(méi)有大于10K,則直接使用內(nèi)存直接保存成文件就可以了。 * 如果一個(gè)文件大于10K,就需要將文件先保存到臨時(shí)目錄中去。 * 第二個(gè)參數(shù) File 是指臨時(shí)目錄位置 * */ public class Up2Servlet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding( "UTf-8" ); //獲取項(xiàng)目的路徑 String path = getServletContext().getRealPath( "/up" ); //第一步聲明diskfileitemfactory工廠類(lèi),用于在指的磁盤(pán)上設(shè)置一個(gè)臨時(shí)目錄 DiskFileItemFactory disk = new DiskFileItemFactory( 1024 * 10 , new File( "/home/wang/" )); //第二步:聲明ServletFileUpoload,接收上面的臨時(shí)目錄 ServletFileUpload up = new ServletFileUpload(disk); //第三步:解析request try { List<FileItem> list = up.parseRequest(req); //如果就一個(gè)文件 FileItem file = list.get( 0 ); //獲取文件名,帶路徑 String fileName = file.getName(); fileName = fileName.substring(fileName.lastIndexOf( "\\" )+ 1 ); //獲取文件的類(lèi)型 String fileType = file.getContentType(); //獲取文件的字節(jié)碼 InputStream in = file.getInputStream(); //聲明輸出字節(jié)流 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(); //刪除上傳的臨時(shí)文件 file.delete(); //顯示數(shù)據(jù) resp.setContentType( "text/html;charset=UTf-8" ); PrintWriter op = resp.getWriter(); op.print( "文件上傳成功<br/>文件名:" +fileName); op.print( "<br/>文件類(lèi)型:" +fileType); op.print( "<br/>文件大?。╞ytes)" +size); } catch (Exception e) { e.printStackTrace(); } } } |
5.使用該框架上傳多個(gè)文件:
第一步:修改頁(yè)面的表單為多個(gè)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); //聲明一個(gè)list<map>封裝上傳的文件的數(shù)據(jù) 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(); //使用工具類(lèi) 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); //轉(zhuǎn)發(fā) request.getRequestDispatcher( "/jsps/show.jsp" ).forward(request, response); } catch (Exception e){ e.printStackTrace(); } } } |
如上就是上傳文件的常用做法。現(xiàn)在我們?cè)趤?lái)看看fileupload的其他查用API.
判斷一個(gè)fileItem是否是file(type=file)對(duì)象或是text(type=text|checkbox|radio)對(duì)象:
boolean isFormField() 如果是text|checkbox|radio|select這個(gè)值就是true.
6.處理帶說(shuō)明信息的圖片
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){ //第一步:判斷是否是普通的表單項(xiàng) if (file.isFormField()){ String fileName = file.getFieldName(); //<input type="text" name="desc">=desc String value = file.getString( "UTF-8" ); //默認(rèn)以ISO方式讀取數(shù)據(jù) System.err.println(fileName+ "=" +value); } else { //說(shuō)明是一個(gè)文件 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的集合的時(shí)候,使用:
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" ); //第一步:設(shè)置響應(yīng)的類(lèi)型 resp.setContentType( "application/force-download" ); //第二讀取文件 String path = getServletContext().getRealPath( "/up/" +name); InputStream in = new FileInputStream(path); //設(shè)置響應(yīng)頭 //對(duì)文件名進(jìn)行url編碼 name = URLEncoder.encode(name, "UTF-8" ); resp.setHeader( "Content-Disposition" , "attachment;filename=" +name); resp.setContentLength(in.available()); //第三步:開(kāi)始文件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流行框架時(shí) |
使用框架內(nèi)部封裝好的來(lái)完成上傳下載更為簡(jiǎn)單:
Struts2完成上傳.
在使用Struts2進(jìn)行開(kāi)發(fā)時(shí),導(dǎo)入的jar包不難發(fā)現(xiàn)存在 commons-fileupload-1.3.1.jar 包。通過(guò)上面的學(xué)習(xí)我們已經(jīng)可以使用它進(jìn)行文件的上傳下載了。但Struts2在進(jìn)行了進(jìn)一步的封裝。
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上傳過(guò)來(lái)的文件本身,而是文件上傳過(guò)來(lái)存放在臨時(shí)文件夾下面的文件 private File file; //提交過(guò)來(lái)的file的名字 //struts會(huì)自動(dòng)截取上次文件的名字注入給該屬性 private String fileFileName; //getter和setter此時(shí)為了節(jié)約篇幅省掉 @Override public String execute() throws Exception { //保存上傳文件的路徑 String root = ServletActionContext.getServletContext().getRealPath( "/upload" ); //獲取臨時(shí)文件輸入流 InputStream is = new FileInputStream(file); //輸出文件 OutputStream os = new FileOutputStream( new File(root, fileFileName)); //打印出上傳的文件的文件名 System.out.println( "fileFileName: " + fileFileName); // 因?yàn)閒ile是存放在臨時(shí)文件夾的文件,我們可以將其文件名和文件路徑打印出來(lái),看和之前的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; } } |
首先我們要清楚一點(diǎn),這里的file并不是真正指代jsp上傳過(guò)來(lái)的文件,當(dāng)文件上傳過(guò)來(lái)時(shí),struts2首先會(huì)尋找struts.multipart.saveDir(這個(gè)是在default.properties里面有)這個(gè)name所指定的存放位置(默認(rèn)是空),我們可以在我們項(xiàng)目的struts2中來(lái)指定這個(gè)臨時(shí)文件存放位置。
<constant name="struts.multipart.saveDir" value="/repository"/>
如果沒(méi)有設(shè)置struts.multipart.saveDir,那么將默認(rèn)使用javax.servlet.context.tempdir指定的地址,javax.servlet.context.tempdir的值是由服務(wù)器來(lái)確定的,例如:假如我的web工程的context是abc,服務(wù)器使用Tomcat,那么savePath就應(yīng)該是%TOMCAT_HOME%/work/Catalina/localhost/abc_,臨時(shí)文件的名稱(chēng)類(lèi)似于upload__1a156008_1373a8615dd__8000_00000001.tmp,每次上傳的臨時(shí)文件名可能不同,但是大致是這種樣式。而且如果是使用Eclipse中的Servers里面配置Tomcat并啟動(dòng)的話,那么上面地址中的%TOMCAT_HOME%將不會(huì)是系統(tǒng)中的實(shí)際Tomcat根目錄,而會(huì)是Eclipse給它指定的地址,例如我本地的地址是這樣的:/home/wang/EclipseJavaCode/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/abc/upload__1a156008_1373a8615dd__8000_00000001.tmp。
Struts2完成下載.
struts2的文件下載更簡(jiǎn)單,就是定義一個(gè)輸入流,然后將文件寫(xiě)到輸入流里面就行,關(guān)鍵配置還是在struts.xml這個(gè)配置文件里配置:
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 { //要下載文件在服務(wù)器上的路徑 private String path; //要下載文件的文件名 private String downloadFileName; //寫(xiě)入getter和setter public InputStream getDownloadFile() { return ServletActionContext.getServletContext().getResourceAsStream(path); } @Override public String execute() throws Exception { //當(dāng)前action默認(rèn)在valuestack的棧頂 setDownloadFileName(xxx); return SUCCESS; } } |
action只是定義了一個(gè)輸入流downloadFile,然后為其提供getter方法就行,接下來(lái)我們看看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配置文件有幾個(gè)地方我們要注意,首先是result的類(lèi)型,type一定要定義成stream類(lèi)型_,告訴action這是文件下載的result,result元素里面一般還有param子元素,這個(gè)是用來(lái)設(shè)定文件下載時(shí)的參數(shù),inputName這個(gè)屬性就是得到action中的文件輸入流,名字一定要和action中的輸入流屬性名字相同,然后就是contentDisposition屬性,這個(gè)屬性一般用來(lái)指定我們希望通過(guò)怎么樣的方式來(lái)處理下載的文件,如果值是attachment,則會(huì)彈出一個(gè)下載框,讓用戶選擇是否下載,如果不設(shè)定這個(gè)值,那么瀏覽器會(huì)首先查看自己能否打開(kāi)下載的文件,如果能,就會(huì)直接打開(kāi)所下載的文件,(這當(dāng)然不是我們所需要的),另外一個(gè)值就是filename這個(gè)就是文件在下載時(shí)所提示的文件下載名字。在配置完這些信息后,我們就能過(guò)實(shí)現(xiàn)文件的下載功能了。
SpringMVC完成上傳:
view于struts2示例中的完全一樣。此出不在寫(xiě)出。
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(); //獲取文件擴(kuò)展名 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); } // 這里主要關(guān)閉。 os.close(); inputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // 返回值要注意,要不然就出現(xiàn)下面這句錯(cuò)誤! //java+getOutputStream() has already been called for this response return null ; } |
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。