Java 檔案分塊上傳用戶端原始碼

來源:互聯網
上載者:User

MIME協議(中文版).doc

        本部落格介紹如何進行檔案的分塊上傳。本文側重介紹用戶端,伺服器端請參考部落格《Java 檔案分塊上傳伺服器端原始碼》。建議讀者朋友在閱讀本文代碼前先瞭解一下 MIME 協議。

        所謂分塊上傳並非把大檔案進行物理分塊,然後挨個上傳,而是依次讀取大檔案的一部分檔案流進行上傳。分塊,倒不如說分流比較切實。本文通過一個項目中的樣本,說明使用 Apache 的 HttpComponents/HttpClient 對大檔案進行分塊上傳的過程。樣本使用的版本是 HttpComponents Client 4.2.1。
        本文僅以一小 demo 功能性地解釋 HttpComponents/HttpClient 分塊上傳,沒有考慮 I/O 關閉、多線程等資源因素,讀者可以根據自己的項目酌情處理。
        本文核心思想及流程:以 100 MB 大小為例,大於 100 MB 的進行分塊上傳,否則整塊上傳。對於大於 100 MB 的檔案,又以 100 MB 為單位進行分割,保證每次以不大於 100 MB 的大小進行上傳。比如 304 MB 的一個檔案會分為 100 MB、100 MB、100 MB、4 MB 等四塊依次上傳。第一次讀取 0 位元組開始的 100 MB 個位元組,上傳;第二次讀取第 100 MB 位元組開始的 100 MB 個位元組,上傳;第三次讀取第 200 MB 位元組開始的 100 MB
個位元組,上傳;第四次讀取最後剩下的 4 MB 個位元組進行上傳。

        自訂的 ContentBody 源碼如下,其中定義了流的讀取和輸出:

package com.defonds.rtupload.common.util.block;import java.io.File;import java.io.IOException;import java.io.OutputStream;import java.io.RandomAccessFile;import org.apache.http.entity.mime.content.AbstractContentBody;import com.defonds.rtupload.GlobalConstant;public class BlockStreamBody extends AbstractContentBody {//給MultipartEntity看的2個參數private long blockSize = 0;//本次分塊上傳的大小private String fileName = null;//上傳檔案名稱//writeTo需要的3個參數private int blockNumber = 0, blockIndex = 0;//blockNumber分塊數;blockIndex當前第幾塊private File targetFile = null;//要上傳的檔案private BlockStreamBody(String mimeType) {super(mimeType);// TODO Auto-generated constructor stub}/** * 自訂的ContentBody構造子 * @param blockNumber分塊數 * @param blockIndex當前第幾塊 * @param targetFile要上傳的檔案 */public BlockStreamBody(int blockNumber, int blockIndex, File targetFile) {this("application/octet-stream");this.blockNumber = blockNumber;//blockNumber初始化this.blockIndex = blockIndex;//blockIndex初始化this.targetFile = targetFile;//targetFile初始化this.fileName = targetFile.getName();//fileName初始化//blockSize初始化if (blockIndex < blockNumber) {//不是最後一塊,那就是固定大小了this.blockSize = GlobalConstant.CLOUD_API_LOGON_SIZE;} else {//最後一塊this.blockSize = targetFile.length() - GlobalConstant.CLOUD_API_LOGON_SIZE * (blockNumber - 1);}}@Overridepublic void writeTo(OutputStream out) throws IOException {byte b[] = new byte[1024];//暫存容器RandomAccessFile raf  = new RandomAccessFile(targetFile, "r");//負責讀取資料if (blockIndex == 1) {//第一塊int n = 0;long readLength = 0;//記錄已讀位元組數while (readLength <= blockSize - 1024) {//大部分位元組在這裡讀取n = raf.read(b, 0, 1024);readLength += 1024;out.write(b, 0, n);}if (readLength <= blockSize) {//餘下的不足 1024 個位元組在這裡讀取n = raf.read(b, 0, (int)(blockSize - readLength));out.write(b, 0, n);}} else if (blockIndex < blockNumber) {//既不是第一塊,也不是最後一塊raf.seek(GlobalConstant.CLOUD_API_LOGON_SIZE * (blockIndex - 1));//跳過前[塊數*固定大小 ]個位元組int n = 0;long readLength = 0;//記錄已讀位元組數while (readLength <= blockSize - 1024) {//大部分位元組在這裡讀取n = raf.read(b, 0, 1024);readLength += 1024;out.write(b, 0, n);}if (readLength <= blockSize) {//餘下的不足 1024 個位元組在這裡讀取n = raf.read(b, 0, (int)(blockSize - readLength));out.write(b, 0, n);}} else {//最後一塊raf.seek(GlobalConstant.CLOUD_API_LOGON_SIZE * (blockIndex - 1));//跳過前[塊數*固定大小 ]個位元組        int n = 0;        while ((n = raf.read(b, 0, 1024)) != -1) {        out.write(b, 0, n);        }}//TODO 最後不要忘掉關閉out/raf}@Overridepublic String getCharset() {// TODO Auto-generated method stubreturn null;}@Overridepublic String getTransferEncoding() {// TODO Auto-generated method stubreturn "binary";}@Overridepublic String getFilename() {// TODO Auto-generated method stubreturn fileName;}@Overridepublic long getContentLength() {// TODO Auto-generated method stubreturn blockSize;}}

        在自訂的 HttpComponents/HttpClient 工具類 HttpClient4Util 裡進行分塊上傳的封裝:

public static String restPost(String serverURL, File targetFile,Map<String, String> mediaInfoMap){String content ="";try {DefaultHttpClient httpClient = new DefaultHttpClient();HttpPost post = new HttpPost(serverURL +"?");httpClient.getParams().setParameter("http.socket.timeout",60*60*1000);MultipartEntity mpEntity = new MultipartEntity();List<String> keys = new ArrayList<String>(mediaInfoMap.keySet());Collections.sort(keys, String.CASE_INSENSITIVE_ORDER);for (Iterator<String> iterator = keys.iterator(); iterator.hasNext();) {String key = iterator.next();if (StringUtils.isNotBlank(mediaInfoMap.get(key))) {mpEntity.addPart(key, new StringBody(mediaInfoMap.get(key)));}}if(targetFile!=null&&targetFile.exists()){ContentBody contentBody = new FileBody(targetFile);mpEntity.addPart("file", contentBody);}post.setEntity(mpEntity);HttpResponse response = httpClient.execute(post);content = EntityUtils.toString(response.getEntity());httpClient.getConnectionManager().shutdown();} catch (Exception e) {e.printStackTrace();}System.out.println("=====RequestUrl==========================\n"+getRequestUrlStrRest(serverURL, mediaInfoMap).replaceAll("&fmt=json", ""));System.out.println("=====content==========================\n"+content);return content.trim();}

        其中 "file" 是分塊上傳伺服器對區塊檔案參數定義的名字。細心的讀者會發現,整塊檔案上傳直接使用 Apache 官方的 InputStreamBody,而分塊才使用自訂的 BlockStreamBody。

        最後調用 HttpClient4Util 進行上傳:

public static Map<String, String> uploadToDrive(Map<String, String> params, String domain) {File targetFile = new File(params.get("filePath"));long targetFileSize = targetFile.length();int mBlockNumber = 0;if (targetFileSize < GlobalConstant.CLOUD_API_LOGON_SIZE) {mBlockNumber = 1;} else {mBlockNumber = (int) (targetFileSize / GlobalConstant.CLOUD_API_LOGON_SIZE);long someExtra = targetFileSize% GlobalConstant.CLOUD_API_LOGON_SIZE;if (someExtra > 0) {mBlockNumber++;}}params.put("blockNumber", Integer.toString(mBlockNumber));if (domain != null) {LOG.debug("Drive---domain=" + domain);LOG.debug("drive---url=" + "http://" + domain + "/sync"+ GlobalConstant.CLOUD_API_PRE_UPLOAD_PATH);} else {LOG.debug("Drive---domain=null");}String responseBodyStr = HttpClient4Util.getRest("http://" + domain+ "/sync" + GlobalConstant.CLOUD_API_PRE_UPLOAD_PATH, params);ObjectMapper mapper = new ObjectMapper();DrivePreInfo result;try {result = mapper.readValue(responseBodyStr, ArcDrivePreInfo.class);} catch (IOException e) {LOG.error("Drive.preUploadToArcDrive error.", e);throw new RtuploadException(GlobalConstant.ERROR_CODE_13001);// TODO}// JSONObject jsonObject = JSONObject.fromObject(responseBodyStr);if (Integer.valueOf(result.getRc()) == 0) {int uuid = result.getUuid();String upsServerUrl = result.getUploadServerUrl().replace("https","http");if (uuid != -1) {upsServerUrl = upsServerUrl+ GlobalConstant.CLOUD_API_UPLOAD_PATH;params.put("uuid", String.valueOf(uuid));for (int i = 1; i <= mBlockNumber; i++) {params.put("blockIndex", "" + i);HttpClient4Util.restPostBlock(upsServerUrl, targetFile,params);//}}} else {throw new RtuploadException(GlobalConstant.ERROR_CODE_13001);// TODO}return null;}

        其中 params 這個 Map 裡封裝的是伺服器分塊上傳所需要的一些參數,而上傳塊數也在這裡進行確定。
        本文中的樣本經本人測試能夠上傳大檔案成功,諸如 *.mp4 的檔案上傳成功沒有出現任何問題。如果讀者朋友測試時遇到問題無法上傳成功,請在部落格後跟帖留言,大家共同交流下。本文樣本肯定還存在很多不足之處,如果讀者朋友發現還請留言指出,筆者先行謝過了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.