JSP文件上传一、先做选择上传文件页面chose.jsp,比较简单。
代码如下:关键代码是:<form name="Myform" action="upfile.jsp" method="post" enctype="multipart/form-data">选择文件后的上传由upfile.jsp网页来处理;整个表单内容以post方式传递;表单中包含的所有内容,包括文件都会以enctype="multipart/form-data"二进制形式传给服务器。
注意JSP网页里不同的编码形式声明:charset=gb2312表示网页中包含的内容(比如文件)以简体中文编码;pageEncoding="UTF-8"表示JSP网页源代码本身是UTF-8编码。
因为表单form的内容会以二进制形式传到服务器,就必须用charset=gb2312指定这个二进制是以中文简体来编码的,这样传上去以后真正处理上传和存储的upfile.jsp页才能看懂识别中文文件名!选择文件页面比较简单,关键是编码的申明,必须说清楚,不然到时候中文文件名就会乱码。
二、upfile.jsp页用来接收和处理以二进制形式传给服务器的数据流。
1、二进制数据流的基本信息。
还是用request对象帮我们取从客户端传过来的各种信息,先取二进制数据流的整体长度,就是传过来文件的大小了,单位是字节。
int formDataLength = request.getContentLength();我们可以设置能接收的最大文件大小,比如下面,就是10M。
int MAX_SIZE = 10*1024*1024;再取传过来的二进制数据流类型,不知道里面是什么鬼?String contentType = request.getContentType();不要紧,我们打出来看看就知道了,out.print编程时非常有用,随时打出来瞅瞅。
out.print(contentType);会看到下面这个:主要就是要后面的这个东西一个表单form可能选了多个文件,全部在一个二进制数据流里面一起传过来,那么文件数据之间都用这个间隔boundary隔开的。
通过它才可以在二进制数据流中找到一个文件的开始和结束位置,从而将文件数据正确取出并保存到服务器里。
一个字节的位置错误都会造成文件保存出错!所以文件保存的最关键就是要找准位置,错一个字节都不行。
2、接收传过来的二进制数据流。
用InputStream对象来接收整个传过来的二进制数据:InputStream in=request.getInputStream();内存中新建二进制数组,跟整个传过来的二进制数据流一样大小:byte dataBytes[] = new byte[formDataLength];将上传的全部二进制数据全部读到(保存到)dataBytes字节数组里:in.read(dataBytes,0,formDataLength);in.close();//记得关掉内存中的in对象,回收资源将上传上来的全部内容由dataBytes字节数组转换为可见的字符串:String file = new String(dataBytes);3、探析二进制数据流。
继续用我们很厉害的out.print来看看,转换为字符串后的二进制数据流里面到底是些什么东西。
out.print(file.substring(0,1000));取file字符串的前1000个字符,看看里面的基本结构就可以了。
非常重要的函数substring(begin,end),就是取begin到end之间的子字符串,begin和end分别是子串起、止位置的下标。
以间隔开始;接着就是文件的基本(包括文件名,类型等);最后看不懂的就是上传文件的二进制内容。
文件内容也将会以间隔结束。
因为不同文件编码很复杂,这里我们直接页面展示肯定是乱码(只有txt文本文件才是直接utf-8,gb2312这样编码,其他文件都会自己另外重编,所以用网页直接打开都会是这样的乱码。
)知道间隔是什么样的,知道文件名在哪里,也知道哪些是文件内容。
就可以对二进制数据流进行正确截取,实现文件上传和存储了。
4、在二进制数据流中取出文件名、扩展名、间隔字符串。
int fNbg = file.indexOf("filename=\"")+10;找到在file字符串的起始位置!indexof函数就是找子字符串在大字符串中的其实位置的。
\是转义字符,\”表示引号,+10是要找到”的位置。
从f到”刚好10个字符。
int fNEd = file.indexOf("Content-Type:")-3;,反引号”在前面3个位置。
文件名的起始位置,结束位置都找到了,就可以取出来了。
String fileName= file.substring(fNbg,fNEd);Substring函数就是截取file字符串指定起、止位置的子字符串。
out.print(fileName);打出来看看,文件名对不对。
继续,取出间隔boundary。
int budyBg = file.indexOf("-");int budyEd = file.indexOf("\n")-1;String boundary = file.substring(budyBg,budyEd);在file文件中最开始就是boundary,结束标记是回车换行\n。
这就是文件数据流前后的间隔,开始是这个,结束也是这个。
同样,可以取出文件的扩展名:int extBg = fileName.indexOf(".")+1;int extEd = fileName.length();String extName = fileName.substring(extBg,extEd);out.print(extName);5、在二进制数据流中取出真正的文件内容。
每个文件的二进制数据流前后都是间隔boundary,但是数据流最开始部分是文件信息,不能直接存到文件里,真正的文件内容开始是在后面还要三句话(三个回车换行\n)之后。
int pos;从开始找文件内容起始位置:pos = file.indexOf("filename=\"");往后的第一个回车换行\n:pos = file.indexOf("\n",pos) + 1;往后的第二个回车换行\n:pos = file.indexOf("\n",pos) + 1;往后的第三个回车换行\n:pos = file.indexOf("\n",pos) + 1;找到了文件内容的起始位置,就是这里,它前面有联系两个\n。
文件内容的结束位置,当然就是后面一个间隔的开始位置:int boundaryLocation = file.indexOf(boundary,pos) - 4;注意,这些位置坐标都是在file字符串的绝对位置,从0开始。
每次都从前一次的pos开始再往后找。
文件内容的真正开始位置,要转成二进制字节计数:int startPos = ((file.substring(0,pos)).getBytes()).length;文件内容的真正结束位置,要转成二进制字节计数:int endPos = ((file.substring(0,boundaryLocation)).getBytes()).length;先确定服务器上的路径和文件名:String jspcurpath=application.getRealPath(request.getRequestURI()); String curdir=new File(jspcurpath).getParent();String path=curdir + "\\upfile";File d=new File(path);if(!(d.exists())){//如果指定目录不存在d.mkdir();//则建立upfile目录}String savefileName = path + "\\"+ fileName;File checkFile = new File(savefileName);if(checkFile.exists()){checkFile.delete();}再利用FileOutputStream对象开始截取二进制数据,写入指定文件。
FileOutputStream fileOut = new FileOutputStream(savefileName);fileOut.write(dataBytes,startPos,(endPos - startPos));最后,记得关掉FileOutputStream对象。
fileOut.close();文件上传的关键就在于二进制数据流中各种内容起、止位置的确定,一定要好好体会。
字节数组转换为字符串再定位截取内容,会产生一些问题,并不精确,所以最好就是直接在二进制字节数组里面定位截取。
不用上面的方法,我们另做一个upfile.jsp文件。
自己写一个函数,在字节数组中定位一个子串的位置下标。
<%!//该函数功能为:从下标offset位置开始,在字节数组Src中找到第一个Des子串的起始位置,返回其在Src数组中的下标public static int ByteIndexOf(byte Src[],byteDes[],int offset){int i=offset; //Src数组从下标offset开始对比int j=0; //Des数组从下标0开始对比int subBg = -1; //-1表示尚没有找到子串int DesLength = Des.length;//Src数组的长度int SrcLength = Src.length;//Des数组的长度while(j<DesLength && i<SrcLength) //i在Src数组中没有找完,j在Des数组中也没有找完{if(Src[i]==Des[j]){ //如果两个数组的当前字节一样i++;j++; //i在Src数组中往后走一个,j在Des数组中往后走一个,返回循环继续对比}else{ //如果两个数组的当前字节不一样if(j>0) //如果Des数组的当前下标j大于零,表示Src数组已经找到与Des数组前几个字节相同的子串,但是没有全部匹配,要重新开始j=0; //则j回到Des数组的第0个字节;而i继续停在Src数组当前位置,返回循环与Des数组的第0个字节重新开始比else{ //如果Des数组的当前下标j仍是0,表示Src数组一直没有找到与Des数组匹配的第一个字节i++; //则i继续在Src数组中往后走,返回循环继续与Des数组的第0个字节比对j=0; //确保Des数组当前下标j回到0}}}if(j==DesLength && i<SrcLength) //循环结束,如果j 下标达到Des数组长度,表示找到了子串了subBg=i-DesLength; //子串在Src数组中的其实位置下标就是当前位置i减去DesLengthelse //循环结束,如果j下标没有达到Des数组长度,表示未找到子串subBg=-1; //值为-1,表示未找到指定子串return(subBg); //返回子串的其实位置,如果是-1则表示没有指定的子串}%>函数写好后,主程序如下:<%int MAX_SIZE = 10*1024*1024;int formDataLength = request.getContentLength();try{if(formDataLength<=MAX_SIZE){if(formDataLength>200){InputStream in=request.getInputStream();//将上传的数据流全部保存在byte数组中byte dataBytes[] = new byte[formDataLength];int byteRead = 0;int totalBytesRead = 0;//以1024字节为单位,循环读出InputStream数据流,存到dataBytes[]字节数组里while(totalBytesRead < formDataLength){byteRead =in.read(dataBytes,totalBytesRead,1024);totalBytesRead += byteRead;}in.close();//读出来后,就关掉InputStream数据流对象in//在二进制字节数组里面直接定位各种信息内容的位置,以及截取byte FenGe[] ={13,10}; //这个字节数组放的就是回车换行,13是回车字符的AscII码,10是换行字符的AscII码int Budbg = 0; //数据流一开始就是间隔,间隔开始位置就是下标0int BudEd = ByteIndexOf(dataBytes,FenGe,0); //间隔的结束是一个回车换行,结束位置用函数ByteIndexOf获取int Budlg = BudEd - Budbg; //间隔的长度byte Budery[]=new byte[Budlg];//间隔字节数组,用来放间隔的字节二进制数据System.arraycopy(dataBytes,0,Budery,0,Budlg);//用arraycopy从dataBytes的0位置开始中截取出长度为Budlg的间隔出来//放到Budery从0开始的位置,字节数组Budery现在就放的是文件间隔字符串了String flbg = "filename=\""; //文件名的开始标记byte FNbgB[] = flbg.getBytes(); //文件名的开始标记转换为二进制字节数组int fnameBg = ByteIndexOf(dataBytes,FNbgB,0)+10;//用函数ByteIndexOf帮助定位文件名开始位置下标byte FNedB[] = {13,10}; //文件名的结束标记也是回车换行int fnameEd =ByteIndexOf(dataBytes,FNedB,fnameBg)-1; //用函数ByteIndexOf帮助定位文件名结束位置下标,-1是因为末尾还有一个双引号int FnLen = fnameEd - fnameBg;byte File_Name[] = new byte[FnLen];//将文件名放入这个字节数组System.arraycopy(dataBytes,fnameBg,File_Name,0,FnLen) ;//用arraycopy从dataBytes中截取出文件名String filename = new String(File_Name); //文件名是字符串,将字节数组转为字符串,得到真正文件名byte FileFenGe[] ={13,10,13,10};//文件头信息是以连续两个回车换行结束的int Filebg =ByteIndexOf(dataBytes,FileFenGe,BudEd+2)+4; //用函数ByteIndexOf继续往后获取这连续两个回车换行的位置//真正的文件内容在这连续两个回车换行之后//文件流内容的结束是以间隔字节数组Budery为标记的int FileEd =ByteIndexOf(dataBytes,Budery,Filebg)-2;//用函数ByteIndexOf继续往后获取文件结束间隔的位置,-2是因为前面又有一个回车换行int Filelen = FileEd - Filebg; //真正文件内容的长度Stringjspcurpath=application.getRealPath(request.getRequest URI());String curdir=new File(jspcurpath).getParent();String path=curdir + "\\upfile";File d=new File(path);if(!(d.exists())){//如果指定目录不存在d.mkdir();//则建立upfile目录}String savefileName = path + "\\"+ filename;//out.print(fileName);File checkFile = new File(savefileName);if(checkFile.exists()){checkFile.delete();}FileOutputStream fileOut = newFileOutputStream(savefileName);fileOut.write(dataBytes,Filebg,Filelen);fileOut.close();out.print("文件上传成功!");}else{out.print("文件不存在,无法上传!");}}else{out.print("文件超过10M限额,无法上传!"); }}catch(IOException e){out.print("upload error.");e.printStackTrace();}%>需要源代码的请百度华夏电商,来专业博客下载。