用TOMCAT作简单的Java web开发-JSP中实现文件上传
介绍了jsp页面中get/post方法传递参数后,在后面介绍到的文件上传部分,我们似乎已经看到了我们能够通过HttpServletRequest对象的getInputStream()方法取得form表单传递的文件以及其他参数的流,能够得到它,原则上我们就已经可以实现jsp间文件上传的功能了。剩下的主要就是细节问题了。今天就从准备工作开始,一点一点介绍怎么实现jsp间的文件上传。大体分以下步骤。
复制内容到剪贴板
代码:
1. 准备工作,分析需要的技术。技术包括Java方面的,以及文件上传的技术文档。
2.分析业务流程以及实现的一些细节问题。
3. 初步实现功能。
4. 抽出class,使此功能共通化。
5. 测试。其中5测试是程序开发中必要的一个环节,即使你的代码能够实现你的功能,你也要测试在不同的情况下程序的正确性。因为用户并不一定会完全按照你预想的操作步骤来执行你的程序,那么,就需要考虑到在不同情况下程序的反应,避免出错。但是这里,就进行简单测试了。如果有朋友对这个功能感兴趣,使用的时候,出现问题可以反馈给我。
好了,现在开始进行准备工作,分析需要的技术。
a,对String对象要非常熟悉,因为在我们取得String流之后,按行取得字符串之后,剩下的操作主要就是对String对象的各种操作。String对象是基础,对它的熟悉是必要的,要了解它的各种方法的用法。
b,对java的文件操作要非常熟悉,至少,要了解如何生成一个文件。
c,要了解文件上传的一些知识。前2点都是很容易就可以找到来学习的。现在说说文件上传知识从哪里来的。首先,文件上传的时候,form表单的enctype为multipart/form-data。这很容易让我们想到要查找DHTML的知识,到微软的MSDN上找到FORM表单的介绍,发现并没有提供我们想要了解的知识。那么,就去W3C(http://www.w3.org/TR/REC-html32.html)去找找看吧,在w3c网站上找到的FORM信息如下:
复制内容到剪贴板
代码:
FORM
<!ENTITY % HTTP-Method "GET | POST"
-- as per HTTP specification
-->
<!ELEMENT FORM - - %body.content -(FORM)>
<!ATTLIST FORM
action %URL #IMPLIED -- server-side form handler --
method (%HTTP-Method) GET -- see HTTP specification --
enctype %Content-Type; "application/x-www-form-urlencoded"
>
This is used to define an HTML form, and you can have more than one form in the same document. Both the start and end tags are required. For very simple forms, you can also use the ISINDEX element. Forms can contain a wide range of HTML markup including several kinds of form fields such as single and multi-line text fields, radio button groups, checkboxes, and menus.
action
This specifies a URL which is either used to post forms via email, e.g. action="mailto:foo@bar.com", or used to invoke a server-side forms handler via HTTP, e.g. action="http://www.acme.com/cgi-bin/register.pl"
method
When the action attribute specifies an HTTP server, the method attribute determines which HTTP method will be used to send the form's contents to the server. It can be either GET or POST, and defaults to GET.
enctype
This determines the mechanism used to encode the form's contents. It defaults to application/x-www-form-urlencoded.
Further details on handling forms are given in RFC 1867. 似乎也没有讲到我们要的信息,注意最后一句,Further details on handling forms are given in RFC 1867. 那么我们再找RFC 1867文档来看看,搜索后在http://www.faqs.org/rfcs/rfc1867.html找到了RFC 1867的介绍。翻看了一下,在Example中发现了这么一段
复制内容到剪贴板
代码:
6. Examples
Suppose the server supplies the following HTML:
<FORM ACTION="http://server.dom/cgi/handle"
ENCTYPE="multipart/form-data"
METHOD=POST>
What is your name? <INPUT TYPE=TEXT NAME=submitter>
What files are you sending? <INPUT TYPE=FILE NAME=pics>
</FORM>
and the user types "Joe Blow" in the name field, and selects a text
file "file1.txt" for the answer to 'What files are you sending?'
The client might send back the following data:
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--AaB03x--
If the user also indicated an image file "file2.gif" for the answer
to 'What files are you sending?', the client might client might send
back the following data:
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics"
Content-type: multipart/mixed, boundary=BbC04y
--BbC04y
Content-disposition: attachment; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--BbC04y
Content-disposition: attachment; filename="file2.gif"
Content-type: image/gif
Content-Transfer-Encoding: binary
...contents of file2.gif...
--BbC04y--
--AaB03x--是不是发现这里有许多东西在昨天的getInputStream()的输出里很相似?OK!就是它了,简单看了下整篇介绍,对文件上传有了大体的了解。
注意:
复制内容到剪贴板
代码:
如果仔细观看给的文档的时候,会发现Content-type: multipart/form-data, boundary=AaB03x这句我们没有看到过,这句是哪里来的呢?查看下request方法,发现有个getContentType()方法,在JSP中输出一看,发现是multipart/form-data; boundary=---------------------------7d723f29b07f4,是不是跟文档中的那个非常相似。
原来,在这里,定义了这个随机的字符串,---------------------------7d723f29b07f4,getInputStream()中取得的每个参数,都是以这个字符串做分割的,对比一下,发现getInputStream()中对应的字符串比ContentType中的这个字符串前端多了”--”,而结束的字符串又在后端多了”--”,这些信息很有用,对我们分析getInputStream()取得的流很有帮助!技术方面,主要就是这3点了,主要是通过自己的学习和搜索引擎得到的。现在开始分析如何实现文件上传的功能。
在分析之前,我们要考虑一点细节问题。比如,如果form中普通的input控件有2个名字相同,在getInputStream流中是什么样的呢?如果上传多个文件,在getInputStream流中是什么样的呢?如果form中普通的input控件和文件上传控件名字相同,在getInputStream流中是什么样的呢?这些可能不太符合规范,但是如果可以这样写,我们就要考虑这种情况出现的时候,该怎样应对。动手前想的越多,写出来的代码可能存在的bug可能会越少。
现在就尝试一下,写一个html页面,form中有2个相同name的input控件,有2个相同name的文件上传控件,有一个input控件和文件上传控件的名字相同。代码如下:
HelloUpFormat.html
复制内容到剪贴板
代码:
<html>
<head>
<title>Test DATE</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<form name="frm" method="post" action="HelloGet.jsp" ENCTYPE="multipart/form-data">
USERNAME : <input type="text" name="username" value="">
USERNAME2(same name as USERNAME) : <input type="text" name="username" value="">
<br>
TEST input 1<input type="text" name="input_file" value="">
TEST file 1(same name as TEST input 1)<input type="file" name="input_file">
<br>
file pt1<input type="file" name="filept">
same name as file pt1<input type="file" name="filept">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>提交到昨天写的HelloGet.jsp页面。注意选择上传文件的时候选择不同的文件,以便区分清楚。
提交后显示的数据如下:
复制内容到剪贴板
代码:
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="username"
SinNeR
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="username"
不可饶恕的罪人
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="input_file"
和上传文件控件名一样
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="input_file"; filename="C:\Documents and Settings\uki.TYO\デスクトップ\1.txt"
Content-Type: text/plain
aaaaaaaaaaaaaaaaaaaaa
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="filept"; filename="C:\Documents and Settings\uki.TYO\デスクトップ\2.txt"
Content-Type: text/plain
bbbbbbbbbbbbbbbbbbbbbbbbbbb
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="filept"; filename="C:\Documents and Settings\uki.TYO\デスクトップ\3.txt"
Content-Type: text/plain
cccccccccccccccccccccccccccccccc
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="submit"
submit
-----------------------------7d72451bb07f4--会发现相同的名称并没有影响参数以及文件的提交。那么,我们在实现功能的时候也要注意,保留用户的提交信息。不要丢失参数,同时,要正常的支持多文件上传的功能。
另外,如果文件上传控件没有选择文件的时候,得到的流是这样的。
复制内容到剪贴板
代码:
-----------------------------7d71343b07f4
Content-Disposition: form-data; name="input_file"; filename=""
Content-Type: application/octet-stream这点也要注意。
至此,jsp文件上传的功能和技术准备我们大体已经准备的差不多了。现在开始分析业务流程以及实现细节的问题了。
Jsp页面程序的大体流程为:
1. 取得要分析的流。
2. 分析取得的流,将普通的参数保存,留作页面取得用。将文件的流和文件名保存,在服务器端保存文件。
3. 提供方法,使用户可以方便的取得要用的参数(这个不是必须的)
这样来看,难点就在对取得的流的分析上。这,就需要自己认真的思考了。
考虑后,可以大体画出以下的流程图。

大家可以尝试着走一遍这个流程图看看能不能走得通。
上面的流程图似乎已经比较清晰,比较严谨。但是有一个问题,如果上传的文件中有Content-Disposition: form-data; name="input_file"这样的字符串,这个流程图就会错误的把它当成一个新的参数来进行分析,这样程序就出现了严重的错误!这是一个危险的bug。那么,我们就要修正这个流程,仔细观察会发现,Content-Disposition: form-data; name="input_file"这个设定参数名字的字符串至会出现在-----------------------------7d71343b07f4的下面,所以,每次读到-----------------------------7d71343b07f4的时候,立刻再读一行,来读取参数的name,这样就避免了上面的问题。流程修改如下:
这时候可能细心的人会想到,如果上传的文件中还有-----------------------------7d71343b07f4字符串,那么对流的分析岂不是也乱了?这个不用担心,因为-----------------------------7d71343b07f4是一个随机的字符串,用户的文件碰巧和这个字符串一样的概率是微之甚微的。
这样,我们就分析完大体的流成了。剩下的工作就是编码实现了。
编码之前,还要交待一些程序实现的细节问题。
1.对于参数的value,用什么对象保存来好。
如开始所看到的,对应同一个name的参数,可能有几个value,如果我们用String来存储的话,就会造成数据丢失。很容易就想到了数组,但是数组在初始化的时候,必须为其定义大小,而在分析之前,我们是不知道同一个name的value有多少个的。这里,我们用ArrayList对象来存储value。
ArrayList对象其实也是一个类似数组的容器,但是它在初始化的时候不需要指定大小。每次有新的元素,尽管加进去就可以。
和数组相似,ArrayList也存在索引(index)。以下是ArrayList的一些常用方法。
复制内容到剪贴板
代码:
//初期化
ArrayList al = new ArrayList();
//返回ArrayList的大小。
int size = al.size();
//向ArrayList中添加数据。
al.add(object);
//取得索引为i的元素,注意索引越界。
al.get(i);
//判断一个元素在ArrayList中的索引位置,不存在的时候返回-1
al.indexOf(object);
//常用的遍历ArrayList的方法。
for(int i=0;i<al.size();i++)
al.get(i);2. 参数怎么保存。
传递过来的参数中,名字不同,会有很多,而且不知道他们的大小。我们用什么来保存它呢?
注意到这种数据的形式是,有一个name,对应一个value对象。这个,跟java中的Hashtable非常相似。
所以我们选择用Hashtable来保存数据。Hashtable比较适合保存一个name对应一个value对象的数据。
复制内容到剪贴板
代码:
//初期化
Hashtable ht = new Hashtable();
//存放数据
ht.put(name,value);
//取出对应name的数据。
ht.get(name);3. 文件的简单操作。
新建一个文件,并写入文件的内容,一般的情况下会以流的形式写入文件。
关于文件操作的介绍也很多,这里暂时就不详细介绍了。
复制内容到剪贴板
代码:
//新建一个文件。
File file = new File( FILEPATH );
//新建一个文件输出流。
FileOutputStream baos = new FileOutputStream( file );
//新建一个buffer输出流
BufferedOutputStream bos = new BufferedOutputStream( baos );
//向流中写入数据
bos.write( buffs, 0, rtnPos );
baos.flush();有了以上的知识。
我们可以初步对代码进行实现了。注意,这是一个稍显复杂的过程,所以要一步一步,先写好大的逻辑,再写细节,这样可以保持清晰地结构,避免一下子就想写完,结果最后整的自己也搞不清逻辑了。同时写的过程中记得加入注释,这也是良好的习惯。对看清source有很大的帮助。
首先,我们拿出昨天的HelloGet.jsp,将我们刚才的流程图作为注释写入代码。
复制内容到剪贴板
代码:
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="javax.servlet.ServletInputStream" %>
<html>
<head>
<title>get method</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<%
request.setCharacterEncoding("GBK");
//定义name,value变量,以及文件标志位
int BUFSIZE = 1024 * 8;
int rtnPos = 0;
byte[] buffs = new byte[ BUFSIZE * 8 ];
ServletInputStream sis = request.getInputStream();
//读取1行数据
while( (rtnPos = sis.readLine( buffs, 0, buffs.length )) != -1 ){
String strBuff = new String( buffs, 0, rtnPos );
//判断取得的数据
//1. 如果是开始符
//如果name存在
//如果文件标志位为true
//进行文件存储操作。
//如果文件标志位为flase
//进行参数设定操作。
//重新初始化name,value以及文件标志位
//读取1行数据。
//如果字符中包含"Content-Disposition: form-data;",读取name
//如果字符中包含"filename",设定文件标志位为true,读取文件名字。
//2. 如果是结束符
//如果name存在
//如果文件标志位为true
//进行文件存储操作。
//如果文件标志位为flase
//进行参数设定操作。
//4. 如果不是1,2的情况。
//value相加。
//out.print(strBuff);
//out.print("<br>");
}
%>
</body>
</html>可以看出上面的条件判断是分级别的,我们先实现第一级判断。在这里注意到我们的程序没有从contentType中取得开始符得操作。这里我们需要添加进去。具体取的过程中用到的String对象的indexOf和substring方法,都是基础的基础的方法,大家自己查下吧。
复制内容到剪贴板
代码:
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="javax.servlet.ServletInputStream" %>
<html>
<head>
<title>get method</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<%
request.setCharacterEncoding("GBK");
//定义name,value变量,以及文件标志位
String name = null;
String value = null;
boolean fileFlag = false;
int BUFSIZE = 1024 * 8;
int rtnPos = 0;
byte[] buffs = new byte[ BUFSIZE * 8 ];
//取得开始符。
String contentType = req.getContentType();
int index = contentType.indexOf( "boundary=" );
String boundary = "--" + contentType.substring( index + 9 );
String endBoundary = boundary + "--";
//取得流。
ServletInputStream sis = request.getInputStream();
//读取1行数据
while( (rtnPos = sis.readLine( buffs, 0, buffs.length )) != -1 ){
String strBuff = new String( buffs, 0, rtnPos );
//判断取得的数据
//1. 如果是开始符
if( strBuff.startsWith( boundary ) ){
//如果name存在
//如果文件标志位为true
//进行文件存储操作。
//如果文件标志位为flase
//进行参数设定操作。
//重新初始化name,value以及文件标志位
//读取1行数据。
//如果字符中包含"Content-Disposition: form-data;",读取name
//如果字符中包含"filename",设定文件标志位为true,读取文件名字。
//2. 如果是结束符
}else if( strBuff.startsWith( endBoundary ) ){
//如果name存在
//如果文件标志位为true
//进行文件存储操作。
//如果文件标志位为flase
//进行参数设定操作。
//4. 如果不是1,2的情况。
}else{
//value相加。
//out.print(strBuff);
//out.print("<br>");
}
}
%>
</body>
</html>按照这样的步骤,一步一步完成下一步的逻辑,这个时候要确保自己的每句代码的正确性,因为jsp是无法预编译的,提前细心一些,就避免了调试过程中问题的出现。
为下一步添加代码。
复制内容到剪贴板
代码:
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="javax.servlet.ServletInputStream" %>
<html>
<head>
<title>get method</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<%
request.setCharacterEncoding("GBK");
//定义name,value变量,以及文件标志位
String name = null;
String value = null;
boolean fileFlag = false;
int BUFSIZE = 1024 * 8;
int rtnPos = 0;
byte[] buffs = new byte[ BUFSIZE * 8 ];
//取得开始符。
String contentType = req.getContentType();
int index = contentType.indexOf( "boundary=" );
String boundary = "--" + contentType.substring( index + 9 );
String endBoundary = boundary + "--";
//取得流。
ServletInputStream sis = request.getInputStream();
//读取1行数据
while( (rtnPos = sis.readLine( buffs, 0, buffs.length )) != -1 ){
String strBuff = new String( buffs, 0, rtnPos );
//判断取得的数据
//1. 如果是开始符
if( strBuff.startsWith( boundary ) ){
//如果name存在
if ( name != null && name.trim().length() > 0 ){
//如果文件标志位为true
//进行文件存储操作。
//如果文件标志位为flase
//进行参数设定操作。
}
//重新初始化name,value以及文件标志位
name = new String();
value = new String();
fileFlag = false;
//读取1行数据。
rtnPos = sis.readLine( buffs, 0, buffs.length );
//如果字符中包含"Content-Disposition: form-data;",读取name
//如果字符中包含"filename",设定文件标志位为true,读取文件名字。
//2. 如果是结束符
}else if( strBuff.startsWith( endBoundary ) ){
//如果name存在
if ( name != null && name.trim().length() > 0 ){
//如果文件标志位为true
//进行文件存储操作。
//如果文件标志位为flase
//进行参数设定操作。
}
//4. 如果不是1,2的情况。
}else{
//value相加。
//out.print(strBuff);
//out.print("<br>");
}
}
%>
</body>
</html>在这里注意一下if ( name != null && name.trim().length() > 0 )这句语句。Java开发中,空指针异常是经常发生的,对没有初始化的对象调用方法,那时候就会抛出空指针异常。那这句话中name.trim()会不会抛出空指针异常呢?回答是不会的,为什么?因为java中条件判断语句是从左向右执行的,如果已经能确定这个条件的结果,那么程序就不会执行下一个判断的。比如A && B 的与条件判断,如果A为false,那么A && B 一定是false,程序就不会去执行B的条件判断,直接返回false。在这里,如果name 为 null,那么前边的条件为false,程序就不会执行后面的语句。只有在name不为null的情况下,才会执行后面的语句。换句话说,name.trim().length()被执行的时候,name永远不会为null。自然不会抛出空指针异常。这是一个小技巧,要利用好。相反,如果写成if (name.trim().length() > 0 && name != null )就很容易出错了。
接着完成下边的注释对应的代码。这时候注意,value对应的为参数的时候,存储value应该为ArrayList数组,而value对应的为文件内容的时候,直接写入文件即可,同时,要给存放name的Hashtable初始化。逻辑开始复杂了,要慢慢的对应。
复制内容到剪贴板
代码:
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="javax.servlet.ServletInputStream" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<html>
<head>
<title>get method</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<%
request.setCharacterEncoding("GBK");
//定义name,value变量,以及文件标志位
String name = null;
String value = null;
//ArrayList value = null;//更改存储value的对象。
boolean fileFlag = false;
// TMP_DIR
String TMP_DIR = "C:\\";
File tmpFile = null;
//file name
String fName = null;
FileOutputStream baos = null;
BufferedOutputStream bos = null;
//定义存放参数的Hashtable。
Hashtable paramHt = new Hashtable();
int BUFSIZE = 1024 * 8;
int rtnPos = 0;
byte[] buffs = new byte[ BUFSIZE * 8 ];
//取得开始符。
String contentType = request.getContentType();
int index = contentType.indexOf( "boundary=" );
String boundary = "--" + contentType.substring( index + 9 );
String endBoundary = boundary + "--";
//取得流。
ServletInputStream sis = request.getInputStream();
//读取1行数据
while( (rtnPos = sis.readLine( buffs, 0, buffs.length )) != -1 ){
String strBuff = new String( buffs, 0, rtnPos );
//判断取得的数据
//1. 如果是开始符
if( strBuff.startsWith( boundary ) ){
//如果name存在
if ( name != null && name.trim().length() > 0 ){
//如果文件标志位为true
if (fileFlag ){
//进行文件存储操作。
bos.flush();
baos.close();
bos.close();
baos = null;
bos = null;
}else{
//如果文件标志位为flase
//进行参数设定操作。
Object obj = paramHt.get(name);
ArrayList al = null;
if ( obj == null ){ al = new ArrayList();
}else{
al = (ArrayList)obj;
}
al.add(value);
paramHt.put(name, al);
}
}
//重新初始化name,value以及文件标志位
name = new String();
value = new String();
fileFlag = false;
//读取1行数据。
rtnPos = sis.readLine( buffs, 0, buffs.length );
if (rtnPos != -1 ){
strBuff = new String( buffs, 0, rtnPos );
//如果字符中包含"Content-Disposition: form-data;",读取name
if (strBuff.toLowerCase().startsWith( "content-disposition: form-data; " )){
int nIndex = strBuff.toLowerCase().indexOf( "name=\"" );
int nLastIndex = strBuff.toLowerCase().indexOf( "\"", nIndex + 6 );
name = strBuff.substring( nIndex + 6, nLastIndex );
}
//如果字符中包含"filename",设定文件标志位为true,读取文件名字。
int fIndex = strBuff.toLowerCase().indexOf( "filename=\"" );
if (fIndex != -1 ){
fileFlag = true;
int fLastIndex = strBuff.toLowerCase().indexOf( "\"", fIndex + 10 );
fName = strBuff.substring( fIndex + 10 , fLastIndex );
fIndex = fName.lastIndexOf( "\\" );
if( fIndex == -1 ){
fIndex = fName.lastIndexOf( "/" );
if( fIndex != -1 ){
fName = fName.substring( fIndex + 1 );
}
}else{
fName = fName.substring( fIndex + 1 );
}
if (fName == null || fName.trim().length() == 0){
fileFlag = false;
sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
continue;
}
}
sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
}
//2. 如果是结束符
}else if( strBuff.startsWith( endBoundary ) ){
//如果name存在
if ( name != null && name.trim().length() > 0 ){
//如果文件标志位为true
if (fileFlag ){
//进行文件存储操作。
bos.flush();
baos.close();
bos.close();
baos = null;
bos = null;
}else{
//如果文件标志位为flase
//进行参数设定操作。
Object obj = paramHt.get(name);
ArrayList al = null;
if ( obj == null ){ al = new ArrayList();
}else{
al = (ArrayList)obj;
}
al.add(value);
paramHt.put(name, al);
}
}
//4. 如果不是1,2的情况。
}else{
//value相加。
if (fileFlag ){
if ( baos == null && bos == null ) {
tmpFile = new File( TMP_DIR + fName );
baos = new FileOutputStream( tmpFile );
bos = new BufferedOutputStream( baos );
}
bos.write( buffs, 0, rtnPos );
baos.flush();
}else{
value = value + strBuff;
}
}
}
%>
</body>
</html>浩浩荡荡的写完了,调试一下。修改了一些简单的bug后,程序居然跑起来了。运行一下开始讲的那个HTML。会发现文件上传成功。这里要注意的是,TMP_DIR设定的是服务器端你上传的文件的存放地址。
到那里去看一下,发现原来的文件内容,
复制内容到剪贴板
代码:
aaaaaaaaaaaaaaaaaaaaa变成了
复制内容到剪贴板
代码:
Content-Type: text/plain
aaaaaaaaaaaaaaaaaaaaa这是不正确的,所以,在文件读取的时候,要跳过2行的数据,不然存储的文件和原来的文件不同。修改后,读取的文件就正常了。
至此,文件上传的功能已经完成。而且传递的参数也被我们存储到一个Hashtable中了。如果开发者要用的话,可以取到。
但是现在就没有问题了么?还存在一个问题,就是当上传文件的控件没有指定文件的时候,会出现异常。这是漏掉的一个环节。发现这个时候服务器端取得的数据是如下形式的。
复制内容到剪贴板
代码:
-----------------------------7d71fe14b07f4
Content-Disposition: form-data; name="input_file"; filename=""
Content-Type: application/octet-stream 所以在文件名不存在的时候,直接跳过3行数据,然后让循环继续进行。
复制内容到剪贴板
代码:
if (fName == null || fName.trim().length() == 0){
fileFlag = false;
sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
continue;
}至此,文件上传告一段落了。但是还有几个小问题。
复制内容到剪贴板
代码:
1.文件上传的覆盖问题。现在的文件上传,如果上传相同的文件名,文件会覆盖。这,对于多用户的web系统来说,不是一个好现象。
这个问题可以避免,最简单的就是在文件名后边加上时间,YYYYMMDDHHmmss的形式,最安全的应该是毫秒级别的,那样问题的发生概率将几乎等于0。
2.文件上传后,一般情况下,程序都是要知道上传的文件的地址的。这个也容易实现,代码进行适当的修改就可以了。
3.文件上传的代码过于复杂,如果上传文件的时候,就在jsp中加入这么一段代码,会使程序看起来很乱。
这个也可以解决,将会把它抽出为一个class文件。
4.别的传入的参数,只能用setAttribute方法设定到request中,无法设定到parameter中。不知道有没有更好的方法。
5.整个程序没有捕捉异常。至此,文件上传的功能已经可以简单实现。下一次,我们就把这些代码抽出到1个class中并介绍接口。
之所以絮絮叨叨的写了这么多,只是希望新手能看到怎么样一点一点实现我们要求的功能。因为,前3讲之后,除了java的一些类的不熟悉,其他的问题大家的困难是一样的。希望这样写,能对用户分析问题,解决问题有点帮助。
[
本帖最后由 SinNeR 于 2007-3-30 18:48 编辑 ]