发新话题
打印

[原创] JSP上传原理分析 BNC JSP上传组件

[原创] JSP上传原理分析 BNC JSP上传组件

BNC is short for : Badboy.Net.Cn

对这方面有兴趣的朋友可以认真看看

[版权:badboy.net.cn]

因为是初学者,所以在我需要JSP为我提供上传功能的时候,我应该去了解一下实现细节,当我了解足够的时候,去使用别人的组件,或者给别人提供组件。

----------------------------------文章快要开始的分割线------------------------------------------------------------------------------------------------------------------------

我首先是了解一下能用request.getInputStream()得到的数据是什么样子的:
1、新建一个HTML页面,含以下表单,用于选择和提交要上传的文件:
复制内容到剪贴板
代码:
<form method="post" action="upload.jsp" ENCTYPE="multipart/form-data">
  <input type="file" name="file1" />
  <input type="submit" value="submit" />
</form>
2、
新建一个JSP页面upload.jsp:
复制内容到剪贴板
代码:
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ page import="java.io.*"%>
<%
try
{
File file = new File("e:/upfile.txt");
InputStream is = request.getInputStream();
FileOutputStream fos = new FileOutputStream(file);
int rn;
byte[] buf = new byte[1024];
while((rn=is.read(buf,0,1024))!=-1)
   fos.write(buf,0,rn);

is.close();
fos.close();
}
catch(Exception e)
{

}
%>
这样,打开HTML页面选择文件并提交后,如无意外E:/下会多出一个upfile.txt文件,它的内容为:
清单1
复制内容到剪贴板
代码:
1  -----------------------------7d65d38307d2
2  Content-Disposition: form-data; name="body3"; filename="C:\Documents and Settings\badboy\桌面\复件 (3) 新建 文本文档.txt"
3  Content-Type: text/plain
4
5  dasfdf
6  sdf
7  asdf
8  -----------------------------7d65d38307d2--
可以了解到:
1、1个文件被2个“-----------------------------7d65d38307d2”夹着
2、第2行到第4行是文件描述
3、文件内容由第5行开始,直到出现“-----------------------------7d65d38307d2”
4、第一行和最后一行的字符串很像,都是“-----------------------------7d65d38307d2”,最后一行多了“--”
因为考虑到上传的可能不是文本文件,但文件描述和分割线却是文本,所以全部用字符串读写方法分析应该行不通,受一本JSP教程的例子启发,决定用RandomAccessFile
RandomAccessFile 可以在文件内的任何位置随意跳转,并且可以读写字符串和字节,因而可以在以字符串方式分析到文件内容的起始位置和结束位置后很方便的跳转到适当位置再以字节方式读取和生成文件。

以下是一个例子,新建一个JSP文件,内容为
复制内容到剪贴板
代码:
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ page import="java.io.*"%>
<%
try
{
File fl1 = new File("e:/upfile.txt");  //这个是刚才那个 文件a
File fl2 = new File("e:/upfile2.txt");  //这个将是内容跟上传的那个文件一模一样的 文件b
RandomAccessFile raf1 = new RandomAccessFile(fl1,"r");  //只读 文件a 的 RandomAccessFile对象 用来在upfile.txt里读出数据用来分析和生成文件
RandomAccessFile raf2 = new RandomAccessFile(fl2,"rw");  //这个是用于生成目的文件的RandomAccessFile对象
String StringStart = null;  //upfile.txt的第一行(“-----------------------------7d65d38307d2”) 是分割符
String tmpStr = null;  //要来临时存放一些字符串
long startPos = 0 ;  //实际文件内容的起始位置
long endPos = 0;  //实际文件内容的结束位置

StringStart = raf1.readLine();  //读取第一行 即分割符
raf1.readLine();
raf1.readLine();
raf1.readLine();
  //连续4次调用readLine()方法,使文件指针跳到了 清单1 的第4行的开始位置,即实际文件的起始位置
startPos = raf1.getFilePointer();  //获得实际文件内容的起始位置

  //以下这个while循环在找到下一个类似“-----------------------------7d65d38307d2”的时候分割符退出,并且会计算实际文件内容的结束位置
while((tmpStr=raf1.readLine())!=null)
{
if(tmpStr.indexOf(StringStart)!=-1)
{
  out.print("found.");
  endPos = raf1.getFilePointer() - tmpStr.getBytes().length - 2;  //获得实际文件内容的结束位置
  break;
}
}

raf1.seek(startPos);  //将指针重新定位到实际文件内容的起始位置  现在已经知道实在文件内容在哪里开始哪里结束了

  //以下这个while循环将读取实际文件内容的起始位置到结束位置间的内容到upfile2.txt中 理论上upfile2.txt的内容是上传的文件的内容一模一样的
while(startPos<endPos)
{
raf2.write(raf1.readByte());
startPos = raf1.getFilePointer();
}
raf1.close();
raf2.close();
}
catch(Exception e)
{
}
%>
上传一个文件的解决方法大概就是这样子,应该很好理解的,不过有几个问题:
1、怎样处理多个文件?
2、性能

关地第1个问题:
只要重复查找分割符并作一些类似的处理可以解决,以后有例子;
关于第2个问题:
我发现程序处理的时间大多是用于文件分析而不是文件的传输过程(我在本机测试 传输过程用的时候可以忽略),而用于文件分析的时间我想大多是用在文件内部跳转的操作上,后来我用一个字节buffer读代替一个一个字节读和写速度有很大提高,但是处理5个共20M左右的MP3文件耗时比较多,这还是在本机上测试的。
以下是我做的一个JavaBean,我想大概不太符合某些规范,不过的确可以实现多文件上传:
下载地址:http://www.badboy.net.cn/badboy/bnc_fpr.rar
方法说明:http://www.badboy.net.cn/badboy/MethodSummary.html

实际上它只负责文件的分析.
以下是一个应用例子,一个包括了表单的JSP页面:
复制内容到剪贴板
代码:
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ page import="java.io.*"%>
<%@ page import="cn.net.badboy.FileParser"%>
<jsp:useBean id="fpr" scope="page" class="cn.net.badboy.FileParser" />
<%
String action=request.getParameter("action");
if(action==null)
action="";
if(action.equals("save"))
{
String FilePath = request.getRealPath("/") + "uploadfiles";  //设置上传文件的保存目录 这里是jsp页面所在目录中的uploadfiles目录 如果没有这个目录 请手动为它新建一个
InputStream is = request.getInputStream();
File f = new File("e:/jt.bnc");  //临时文件 即包含分割符、文件描述和文件内容的那个文件
if(f.exists())
{
  f.delete();
  f = new File("e:/jt.bnc");
}
FileOutputStream fos = new FileOutputStream(f);
int rn;
byte[] buf = new byte[1024];
while((rn=is.read(buf,0,1024))>0)
{
  fos.write(buf,0,rn);
}

is.close();
fos.close();
  //到这里临时文件生成了 余下的工作不再是从客户那里传输文件而是服务器方面的文件分析工作
               
               
                 

fpr.parseFile(f,FilePath,"mp3,gif,jpg");  //方法parseFile分析一个临时文件 第一个参数指明临时文件对象 第二个参数指明生成的文件的保存目录 第三个参数指明充许的文件类型
out.println("Errors message :<br>"+fpr.getErrorMessage()+"<br>");  //方法getErrorMessage返回文件分析过程中的错误 例如有扩展名不合法的文件等
String[] FileNames = fpr.getFileNames();  //方法getFileNames返回一个存放了成功上传的文件的文件名的字符串数组
out.println("file uploaded:<br>");
for(int i=0;i<FileNames.length;i++)
  out.println(FileNames[i]+"<br>");
}
else
{
%>
<form method="post" action="?action=save"  ENCTYPE="multipart/form-data">
<input type="file" name="body1" />
<input type="file" name="body2" />
<input type="file" name="body3" />
<input type="file" name="body4" />
<input type="submit" value="submit" />
</form>
<br />
<%
}
%>
以下是bean cn.net.badboy.FileParser全部代码,有兴趣的朋友可以看看:
复制内容到剪贴板
代码:
package cn.net.badboy;
import java.io.*;

/*
don't remove this message .
this bean from www.badboy.net.cn .
please ! don't remove this message .
all copyright reserved by www.badboy.net.cn .
ar ... don't remove this message .
*/

public class FileParser
{
private String[] FileNameArray = new String[20] ;     //this array  stores files' name that have been create .
private int FileCount = 0 ;          //tells how many file are there
private String errorMessage = "" ;

//***********************************private method definition begin*******************************************************
//method setErrorMessage(String msg)
private void setErrorMessage(String errorMessage)
{
  this.errorMessage += "\n" + errorMessage ;
}

private boolean createFile(RandomAccessFile SourceFile,long startPos,long endPos,String FileDirectory,String FileName)
{
  File file = null;
  RandomAccessFile targetFile = null;
  try{
   file = new File(FileDirectory,FileName);
   if(file.exists())      //if the file exists,overwrite it.
   {
    file.delete();
    file = new File(FileDirectory,FileName);
   }
   targetFile = new RandomAccessFile(file,"rw");
   
   int rn;
   int bufferSize = 1024 ;
   byte[] buf = new byte[bufferSize];
   while(startPos<endPos)
   {
    if(startPos+bufferSize>=endPos)
    {
     targetFile.write(SourceFile.readByte());
     startPos = SourceFile.getFilePointer();     
    }
    else
    {
     rn = SourceFile.read(buf,0,bufferSize);
     targetFile.write(buf,0,rn);
     startPos = SourceFile.getFilePointer();
    }
   }
  }
  catch(Exception e)
  {
   e.printStackTrace();
   return false ;
  }
  finally{
   try{
     if(targetFile!=null)
     {
      targetFile.close();
     }
    }
   catch(IOException e)
   {
    e.printStackTrace();
    return false;
   }
  }
  return true ;
}

private String getFileName(String NameInJerk,String allowableFiles) throws Exception
{
  NameInJerk = new String(NameInJerk.getBytes("ISO-8859-1"),"UTF-8") ;     //encoding to local charset
  int startPos = NameInJerk.lastIndexOf("\\");
  
  if(startPos==-1)
   return null ;
  String FileName = NameInJerk.substring(startPos+1,NameInJerk.length()-1);
  String[] TmpArr = FileName.split("\\.");
  String FileTypeName = TmpArr[TmpArr.length - 1];
  if(allowableFiles.indexOf(FileTypeName)==-1)
  {
   setErrorMessage("file " +  FileName + " is not a supported type of file to upload , upload failed .");
   return null ;
  }
  return FileName;
}




private static void debug(String message)
{
  System.out.println(message);
  
  //or log sth
}
//************************end private method definition*******************************************



//*************************here begin the public method definition ******************************



//method getErrorMessage()
//return the error message
public String getErrorMessage()
{
  return errorMessage ;
}

//public METHOD parseFile
//parseFile接受参数TmpFileName(临时文件)解析临时文件,在FileDirectory(生成文件目录)生成约干文件 return true if success , or not false returned instead.
public boolean parseFile(File TmpFile,String FileDirectory,String allowableFiles)
{
  String CompartString = null ;   //分割字符串 通常为临时文件第一段内容
  String TmpString     = null ;   //临时字符串
  String FileName = null ;  //保存文件名
  long startPos = 0 ;   //每个文件内容的开始位置
  long endPos   = 0 ;   //每个文件内容的结束位置
  RandomAccessFile SourceFile = null ;
  
  try{
   debug("starting.......");
   SourceFile = new RandomAccessFile(TmpFile,"r") ;  //创建随机读取文件对象
   CompartString = SourceFile.readLine();   //获得分割字符串
   SourceFile.seek(0);   //将操作位置重定位到文件开头
   
   looper1:
   while(true)
   {
    while((TmpString=SourceFile.readLine())!=null){
     if(TmpString.indexOf(CompartString)!=-1){
      for(int i=1;i<=3;i++)
      {
       TmpString = SourceFile.readLine();
       if(TmpString==null)
        break looper1;
       if(i==1)
        FileName  = getFileName(TmpString,allowableFiles);   //第二行包含文件名等信息 可以获得文件名
      }
     if(FileName!=null)
      break;
     }
    }
   
    debug("parsing...");
    startPos = SourceFile.getFilePointer();    //获得文件内容的起始位置
   
   
    while((TmpString=SourceFile.readLine())!=null)
    {
     if(TmpString.indexOf(CompartString)!=-1)
     {
      endPos = SourceFile.getFilePointer() - TmpString.getBytes().length - 2 ;   //得到文件内容的结束位置
      break;
     }
    }
    SourceFile.seek(startPos);   //将操作位置重定位到startPos处 开始生成一个文件
    createFile(SourceFile,startPos,endPos,FileDirectory,FileName);   //invoke生成文件方法生成文件
    startPos = endPos + 2 ;
    debug("done...");
   
    // add a file name to FileNameArray
    FileNameArray[FileCount++] = FileName ;
    FileName = null ;
   }
   
  }
  catch(Exception e)
  {
   e.printStackTrace();
   return false ;
  }
  finally{
   try{
    if(SourceFile!=null)
    {
     SourceFile.close();
     TmpFile.delete();   //delete tmpfile    maybe is should be deleted by who create it .
    }
   
   }
   catch(IOException e)
   {
    e.printStackTrace();
    return false;
   }
  }
  return true;
}

//public METHOD getFileNames()
//return an array storing some file name which was created . if it doesn't contains anything,the returned arrary length will be 0 .
public String[] getFileNames()
{
  String[] FileNameArray = new String[FileCount];
  try{
    for(int i=0;i<FileCount;i++)
    {
     FileNameArray[i] = this.FileNameArray[i];
    }
  }
  catch(Exception e)
  {
    e.printStackTrace();
    return FileNameArray;
  }
  return FileNameArray ;
}

public int getFileCount()
{
  return FileCount;
}


//this operation may modify the file has been created . return false if no such file found or some error caused.
//arguments should contains file directory .
public boolean setFileName(String oldFileName,String newFileName)
{
  try
  {
   File oldFile = new File(oldFileName);
   File newFile = new File(newFileName);
   oldFile.renameTo(newFile);
   oldFileName = oldFile.getName();
   newFileName = newFile.getName();
   for(int i=0;i<FileCount;i++)
   {
     if(FileNameArray[i].equals(oldFileName))
      FileNameArray[i] = newFileName;
   }
   
  }
  catch(Exception e)
  {
   e.printStackTrace();
   return false ;
  }
  return true ;
}
}
----------------------------------文章快要结束的分割线------------------------------------------------------------------------------------------------------------------------
我想性能的问题是出在指针的seek来seek去
希望同学们把自己的想法和大家分亨

TOP

发新话题
版块跳转 
Powered by Discuz! 6.1.0 © 2001-2007 Comsenz Inc.
---------------------------------------------------------------------------------------
本论坛所有文章为会员所发布,会员拥有该内容的所有权力及责任,转载时请注明出处!
站务管理员:我心飞翔 QQ:12012805(请直奔主题), 联系电话:15914350818
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触和有损学校声誉的言论!
粤ICP备06054096号 茂名市公安局网警支队备案: 4409813060501

Processed in 0.121734 second(s), 8 queries, Gzip enabled.