上傳工具Cos的源代碼學習

使用Cos時,可以使用兩個類來進行上傳工作:
1.  MultipartRequest
2.  MultipartParser
   一般情況下會使用MultipartRequest類,我感覺它比MultipartParser更方便一些(事實上MultipartRequest封裝了MultipartParser),在構造MultipartRequest實例時,構造了MultipartParser實例(構造parser的過程取得了上傳的數據流引用,但並未真正讀取)。
   然後通過MultipartParser的readNextPart()方法,從request流中讀取數據,區別出流中的參數域和文件域,如果是參數的話用ParamPart類封裝;如果是文件的話用FilePart封裝。
   此時如果設置了重命名策略的話,則在服務器端新建一個新命名的空文件,然後調用FilePart的writeTo(saveDir)方法將流數據寫到磁盤中。至此,文件上傳完成。

下面是使用MultipartRequest的上傳類示例:

/**//*
* DemoUploadServlet.java
*
* Example servlet to handle file uploads using MultipartRequest for
* decoding the incoming multipart/form-data stream
*/

import java.util.Enumeration;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

public class DemoRequestUploadServlet extends HttpServlet {
  private String dirName;
  
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    // read the uploadDir from the servlet parameters
    dirName = config.getInitParameter("uploadDir");
    if (dirName == null) {
      throw new ServletException("Please supply uploadDir parameter");
    }
  }
  
  public void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    PrintWriter out = response.getWriter();
    response.setContentType("text/plain");
    out.println("Demo Upload Servlet using MultipartRequest");
    out.println();
      
    try {
      // Use an advanced form of the constructor that specifies a character
      // encoding of the request (not of the file contents) and a file
      // rename policy.
      MultipartRequest multi =
        new MultipartRequest(request, dirName, 10*1024*1024,
                             "ISO-8859-1", new DefaultFileRenamePolicy());
        
      out.println("PARAMS:");
      Enumeration params = multi.getParameterNames();
      while (params.hasMoreElements()) {
        String name = (String)params.nextElement();
        String value = multi.getParameter(name);
        out.println(name + "=" + value);
      }
      out.println();
              
      out.println("FILES:");
      Enumeration files = multi.getFileNames();
      while (files.hasMoreElements()) {
        String name = (String)files.nextElement();
        String filename = multi.getFilesystemName(name);
        String originalFilename = multi.getOriginalFileName(name);
        String type = multi.getContentType(name);
        File f = multi.getFile(name);
        out.println("name: " + name);
        out.println("filename: " + filename);
        out.println("originalFilename: " + originalFilename);
        out.println("type: " + type);
        if (f != null) {
          out.println("f.toString(): " + f.toString());
          out.println("f.getName(): " + f.getName());
          out.println("f.exists(): " + f.exists());
          out.println("f.length(): " + f.length());
        }
        out.println();
      }
    }
    catch (IOException lEx) {
      this.getServletContext().log(lEx, "error reading or saving file");
    }
  }
}
下面是使用MultipartParser的代碼示例:
/**//*
* DemoParserUploadServlet.java
*
* Example servlet to handle file uploads using MultipartParser for
* decoding the incoming multipart/form-data stream
*/

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

import com.oreilly.servlet.multipart.*;

public class DemoParserUploadServlet extends HttpServlet {
  private File dir;
  
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    // Read the uploadDir from the servlet parameters
    String dirName = config.getInitParameter("uploadDir");
    if (dirName == null) {
      throw new ServletException("Please supply uploadDir parameter");
    }
    dir = new File(dirName);
    if (! dir.isDirectory()) {
      throw new ServletException("Supplied uploadDir " + dirName +
                                 " is invalid");
    }
  }
  
  public void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    PrintWriter out = response.getWriter();
    response.setContentType("text/plain");
    out.println("Demo Upload Servlet using MultipartParser");
    out.println();
      
    try {
      MultipartParser mp = new MultipartParser(request, 10*1024*1024); // 10MB
      Part part;
      while ((part = mp.readNextPart()) != null) {
        String name = part.getName();
        if (part.isParam()) {
          // it's a parameter part
          ParamPart paramPart = (ParamPart) part;
          String value = paramPart.getStringValue();
          out.println("param: name=" + name + "; value=" + value);
        }
        else if (part.isFile()) {
          // it's a file part
          FilePart filePart = (FilePart) part;
          String fileName = filePart.getFileName();
          if (fileName != null) {
            // the part actually contained a file
            long size = filePart.writeTo(dir);
            out.println("file: name=" + name + "; fileName=" + fileName +
              ", filePath=" + filePart.getFilePath() +
              ", contentType=" + filePart.getContentType() +
              ", size=" + size);
          }
          else {
            // the field did not contain a file
            out.println("file: name=" + name + "; EMPTY");
          }
          out.flush();
        }
      }
    }
    catch (IOException lEx) {
      this.getServletContext().log(lEx, "error reading or saving file");
    }
  }
}
近兩天來由於項目需要使用上傳組件,於是我仔細分析了Cos和FileUpload的源代碼,並對它們的性能進行了測試比較,使用2M、20M、45M、200M的上傳大小測試三種組件所花費的時間(單位爲ms)結果是:

2M


第1次
第2次
第3次
第4次
平均

Cos
297
203
234
245
245

FileUpload
281
312
281
312
297

SmartUpload
531
594
485
532
536






20M


第1次
第2次
第3次
第4次
平均

Cos
2562
2109
2719
2172
2391

FileUpload
4062
4140
5360
3922
4371

SmartUpload
3453
3094
3078
3547
3293






45M


第1次
第2次
第3次
第4次
平均

Cos
4860
4844
5125
5171
5000

FileUpload
9000
8391
10375
10078
9461

SmartUpload
8265
9187
8672
8856
8745






200M


第1次
第2次
第3次
第4次
平均

Cos
55813
52282
54796
51187
53520

FileUpload
76343
68531
80954
79031
76215

SmartUpload
內存堆棧溢出
  
  
  
  



從上述的表格對比中可以看出Cos始終保持着良好的性能。在上傳量較小(容量<2M,這是最常出現的情況)時,Cos比FileUpload性能並沒有好很多,但SmartUpload就已經開始顯出弱勢。

   隨着容量的增大,FileUpload和SmartUpload的性能下降非常快,直到200M容量時,SmartUpload已經不堪重負崩潰了,而Cos此時的花費時間比FileUpload少了20多秒,不能不說在本次的評測中,Cos的性能位居第一。

通過對三種流行的上傳組件進行對比,我認爲選用Cos是比較好的。在實際的項目中,可以把上傳的文件放到文件夾,文件路徑存於數據庫中以便於管理。

   如果需要把文件上傳到數據庫也很簡單,從Cos中已經得到了上傳文件(java.io.File),其後的操作很平常所做的一樣:  通過File得到inputStream,存到數據庫的blob或Clob字段即可。

   對於使用Struts的項目,我覺得還是使用FileUpload比較好,因爲Struts天生集成了FileUpload的功能,使用FileUpload會帶來很多的便利。而如果想要開發獨立的上傳組件,則用Cos是最好的選擇,可以在Cos的基礎上封裝一層,暴露給業務程序員的只是一些簡單易用的API,而且可以給這些API加上自定義的javaDoc,這對於實際的開發和將來的擴展都是非常方便的。
   下面對FileUpload的上傳機制作一些分析,基本上,上傳一個文件的過程在FileUpload中可以分爲三個部分:
   1.由客戶端把要上傳的文件生成request數據流,與服務器端建立連接
   2.在服務器端接收request流,將流緩存到內存或磁盤中(具體緩存到什麼地方,將由DiskFileUpload的setSizeThreshold(int cacheMax)方法來決定,當文件大小<cacheMax時,文件將被緩存到內存,否則將被緩存到磁盤的臨時文件)
   3.由服務器端的內存或是臨時文件中把文件輸出到指定的目錄(這個目錄纔是指定的文件上傳目錄).
  

   上述的第一步由瀏覽器完成,不用過多理會,重點是第二和第三步。
   第二步時,由DiskFileUpload的parseRequest(...)方法(其實這個方法是繼承於FileUploadBase類,真正起解析request流作用的類是FileUploadBase)解析request流。在parseRequest(...)方法中,新建了一個MultipartStream實例,由此實例的readBodyData()方法將上傳文件的流讀到FileItem實例中,FileItem實例根據設置好的cacheMax大小,引用一個內存中的數據流或是一個磁盤上的數據流,注意此時文件已經上傳到了服務器,但仍然沒有傳到設定的上傳目錄。  
   第三步時,調用FileItem實例的write(File file)方法,將已經存在於內存或是磁盤上的上傳文件流拷貝到設定好的上傳目錄,至此上傳仍未結束,因爲磁盤中很可能保存了上傳文件的臨時文件(當設定的cacheMax<文件大小時),如何刪除這些臨時文件?有兩種方法:1.顯示調用FileItem實例的delete()方法。2.不調用任何方法,當FileItem被垃圾回收時,由finalize()方法刪除臨時文件。
發佈了5 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章