java實現大文件上傳

文件上傳是最古老的互聯網操做之一,20多年來幾乎沒有怎麼變化,仍是操做麻煩、缺少交互、用戶體驗差。php

1、前端代碼

英國程序員Remy Sharp總結了這些新的接口 ,本文在他的基礎之上,討論在前端採用HTML5的API,對文件上傳進行漸進式加強:html

    * iframe上傳
   * ajax上傳
   * 進度條
   * 文件預覽
   * 拖放上傳 

1.1 傳統形式

  文件上傳的傳統形式,是使用表單元素file,參考 http://www.ruanyifeng.com/blog/2012/08/file_upload.html :前端

  <form id="upload-form" action="upload.php" method="post" enctype="multipart/form-data" >
    <input type="file" id="upload" name="upload" /> <br />
    <input type="submit" value="Upload" />
  </form>

全部瀏覽器都支持上面的代碼,點擊上傳按鈕後,網頁"鎖死",用戶只能等待上傳結束,而後瀏覽器刷新,跳到表單的action屬性指定的網址。html5

1.2 iframe上傳

  用戶點擊submit時,動態插入一個iframe元素git

   var form = $("#upload-form");
  form.on('submit',function() {
    // 此處動態插入iframe元素
  });

  var seed = Math.floor(Math.random() * 1000);程序員

  var id = "uploader-frame-" + seed;github

  var callback = "uploader-cb-" + seed;ajax

  var iframe = $('<iframe id="'+id+'" name="'+id+'" >');後端

  var url = form.attr('action');數組

  form.attr('target', id).append(iframe).attr('action', url + '?iframe=' + callback);

1.3 ajax上傳

  HTML5提出了XMLHttpRequest對象的第二版,今後ajax可以上傳文件了。這是真正的"異步上傳",是未來的主流。

  form.on('submit',function() {
    // 此處進行ajax上傳
  });
 
 // 檢查是否支持FormData
  if(window.FormData) { 
    var formData = new FormData();
    // 創建一個upload表單項,值爲上傳的文件
    formData.append('upload', document.getElementById('upload').files[0]);
    var xhr = new XMLHttpRequest();
    xhr.open('POST', $(this).attr('action'));
    // 定義上傳完成後的回調函數
    xhr.onload = function () {
      if (xhr.status === 200) {
        console.log('上傳成功');
      } else {
        console.log('出錯了');
      }
    };
    xhr.send(formData);
  } 

1.4 進度條

  XMLHttpRequest第二版還定義了一個progress事件,能夠用來製做進度條。

//在頁面中放置一個HTML元素progress
<progress id="uploadprogress" min="0" max="100" value="0">0</progress>

//定義進度progress事件的回調函數
 xhr.upload.onprogress = function (event) {
    if (event.lengthComputable) {
      var complete = (event.loaded / event.total * 100 | 0);
      var progress = document.getElementById('uploadprogress');
      progress.value = progress.innerHTML = complete;
    }
  }

2、後端

  Spring 框架中使用相似CommonsMultipartFile對象處理表二進制文件信息,細心地會發如今利用框架下封裝的Multiform接口進行文件上傳時,會先把文件傳輸至tomcat一個指定的work目錄之下,而後再傳輸到指定的路徑。小文件上傳這個時間延遲基本上能夠忽略,可是在大文件上傳時,這個上傳的速度就很讓人頭疼,上傳過程當中的進度信息沒法訪問。

  所以咱們有必要從瀏覽器請求字節流中解析Multiform協議,實現不依靠框架內置對象,取得用戶請求的全部數據,同時,用戶上傳的大小不受限制,並且在傳輸過程當中,咱們能夠實時獲取傳輸進度。

參考https://www.cnblogs.com/darkprince/p/5114936.html

2.1 普通Post請求協議及MultiPart協議

  由於一次傳輸的大文件MultiPart數據包,字節數可能會很大(1G甚至以上),爲了獲取實時進度信息,以及內存開銷控制,咱們須要將接收過程分紅多段處理,即將數據包分段循環接收(例:每次循環只接收64K數據,期間便可更新當前的進度信息)。本次咱們採用Spring框架來實現「大文件傳輸」功能,要點設計結構圖以下:

 

 

2.2 源碼解析

 

Filter對象:

 

  用於負責接收MultiPart原始數據的Filter,用以在Spring內置對象以前接收用戶請求。須要在Web.xml中進行配置,Web啓動後,該Filter即啓動,當用戶請求到來時須要判斷該MultiPart數據信息是否合法,接收並進行解析。

 

ServletInputStream/BufferedInputStream對象:

 

  使用以上兩對象,可對本次請求進行按字節流接收。在此可建立比較小的接收緩衝區,依靠BufferedInputStream的read進行分段循環接收。 

 

getBoundarySectFromBuf()函數:

 

  自定義函數,咱們須要該函數從分段緩衝區中分析可能包含的多個Form表單信息,或者部分表單信息,或者二進制文件片斷信息。對於表單信息分析後填充表單數據結構,對於二進制文件信息須要寫文件。該函數須要完成邊接收邊解析邊寫文件的重要工做。

 

ProgressInfo對象:

 

  進度信息類,描述了一次上傳請求的進度信息。該對象會用來被客戶端輪詢請求,以得到當前傳輸大文件過程當中的進度信息。

 

FormPart對象及listFormPart集合:

 

  FormPart對於單個Form表單的描述。listFormPart爲本次請求的所有表單描述集合。即供後續代碼調用的所有表單項內容。

 

Controller層getProgInfo()處理函數:

 

  該函數將接受來自瀏覽器的「得到進度信息請求」,並從當前ServletContext公共內存區中找到與Progesss ID對應的進度信息對象ProgressInfo,以XML的形式返回給瀏覽器。該函數會被客戶端輪詢請求。

代碼在 https://github.com/chilichen/UploadBigFile,其中有三個方法須要本身寫出來:

 

   //一、在byte數組上查詢是否含有子數組,若存在則返回數組的起始位置
     public int indexOf(byte[] buf, byte[] key, int scanStart){
             if(buf == key[0] || key == null){return -1;}
             if(scanStart < 0){return 0;}
             for(int i = 0; scanStart; i < buf.length - key.length; i++){
                  if(buf[i] == key[0]){
                          int m = 0;
                          for(int j = 0; scanStart; j< key.length; j++){  
                               if(buf[i + j] == key[j]){ m++;}else{ break;}    
                         if(m == key.length){ return i;}  
                    }
             }
          return -1;
    }

     //二、在byte數組轉換成String
      public String byteToString(byte[] bytes){
          if(bytes == null){ return null ;}
          String s = "";
          ByteBuffer bb = null;
          CharBuffer cb = null;
          try{
try{ bb = ByteBuffer.wrap(bytes); Charset charset = StandardCharsets.UTF_8; CharsetDecoder decoder = charset.newDecoder(); cb = decoder.decode(bb); s = cb.toString(); }catch( Exception e){} }finally{ if(cb != null){
            cb.clear();
            }
if(bb!= null){
            bb.clear();
            }
      }  
return s.trim(); } //三、經過byte[]獲取當前行 public LineReturn getLine(){ int lineStart = indexOf(buf, "\r\n".getBytes(), start); LineReturn line = new LineReturn(); line.setEndindex(lineStart + 2); byte[] lineByte = Arrays.copyOfRange(buf, start, line.getEndIndex()); line.setLine(bytesToString(lineByte)); return line; }
相關文章
相關標籤/搜索