Java 文件分块上传客户端源代码

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。放弃等于又一次可以选择的机会。

Java 文件分块上传客户端源代码

相关文章:

你感兴趣的文章:

标签云: