Object的分块上传
分块上传的场景
除了通过putObject()方法上传文件到BOS以外,BOS还提供了另外一种上传模式:分块上传(Multipart Upload)。用户可以在如下的应用场景内(但不仅限于此),使用分块上传模式,如:
- 需要支持断点上传。
- 上传超过5GB大小的文件。
- 网络条件较差,和BOS的服务器之间的连接经常断开。
- 需要流式地上传文件。
- 上传文件之前,无法确定上传文件的大小。
Multipart Upload分块上传流程
假设有一个文件,本地路径为 /path/to/file.zip
,由于文件比较大,使用分块上传其传输到BOS中。
基本流程:
- 初始化Multipart Upload。
- 上传分块。
- 完成分块上传。
初始化Multipart Upload
使用 initiateMultipartUpload
方法来初始化一个分块上传事件:
示例代码:
1// 开始Multipart Upload
2InitiateMultipartUploadRequest initiateMultipartUploadRequest =
3 new InitiateMultipartUploadRequest(<BucketName>, <ObjectKey>);
4InitiateMultipartUploadResponse initiateMultipartUploadResponse =
5 client.initiateMultipartUpload(initiateMultipartUploadRequest);
6
7// 打印UploadId
8System.out.println("UploadId: " + initiateMultipartUploadResponse.getUploadId());
说明:
initiateMultipartUpload
的返回结果中含有UploadId
,它是区分分块上传事件的唯一标识,在后面的操作中,我们将用到它。
上传分块
将文件分块上传。
示例代码:
1// 设置每块为 5MB
2final long partSize = 1024 * 1024 * 5L;
3
4File partFile = new File("/path/to/file.zip");
5
6// 计算分块数目
7int partCount = (int) (partFile.length() / partSize);
8if (partFile.length() % partSize != 0) {
9 partCount++;
10}
11// 新建一个List保存每个分块上传后的ETag和PartNumber
12List<PartETag> partETags = new ArrayList<PartETag>();
13// 获取文件流
14FileInputStream fis = new FileInputStream(partFile);
15for (int i = 0; i < partCount; i++) {
16
17 // 计算每个分块的大小
18 long skipBytes = partSize * i;
19 long size = partSize < partFile.length() - skipBytes ?
20 partSize : partFile.length() - skipBytes;
21
22 byte[] buf = new byte[(int)size];
23 int offset = 0;
24 while (true) {
25 int byteRead = fis.read(buf, offset, (int)size);
26 offset += byteRead;
27 if (byteRead < 0 || offset >= size) {
28 break;
29 }
30 }
31 ByteArrayInputStream bufStream = new ByteArrayInputStream(buf);
32
33 // 创建UploadPartRequest,上传分块
34 UploadPartRequest uploadPartRequest = new UploadPartRequest();
35 uploadPartRequest.setBucketName(bucketName);
36 uploadPartRequest.setKey(objectkey);
37 uploadPartRequest.setUploadId(initiateMultipartUploadResponse.getUploadId());
38 uploadPartRequest.setInputStream(bufStream);
39 uploadPartRequest.setPartSize(size);
40 uploadPartRequest.setPartNumber(i + 1);
41 // 上传进度回调
42 uploadPartRequest.setProgressCallback(new BosProgressCallback<UploadPartRequest>() {
43 @Override
44 public void onProgress(UploadPartRequest request, long currentSize, long totalSize) {
45 Log.e(currentSize + "", totalSize + "");
46 }
47 });
48
49 UploadPartResponse uploadPartResponse = client.uploadPart(uploadPartRequest);
50
51 // 将返回的PartETag保存到List中。
52 partETags.add(uploadPartResponse.getPartETag());
53
54 System.out.println(uploadPartResponse.getPartETag());
55 }
56 // 关闭文件
57 fis.close();
注意:上面代码的核心是调用
UploadPart
方法来上传每一个分块,但是要注意以下几点:
- UploadPart 方法要求Part大小是1MB的整数倍或大于5MB。但是Upload Part接口并不会立即校验上传Part的大小;只有当Complete Multipart Upload的时候才会校验。
- 为了保证数据在网络传输过程中不出现错误,建议您在
UploadPart
后,使用每个分块BOS返回的Content-MD5值分别验证已上传分块数据的正确性。当所有分块数据合成一个Object后,不再含MD5值。- Part号码的范围是1~10000。如果超出这个范围,BOS将返回InvalidArgument的错误码。
- 每次上传Part时都要把流定位到此次上传块开头所对应的位置。
- 每次上传Part之后,BOS的返回结果会包含一个
PartETag
对象,它是上传块的ETag与块编号(PartNumber)的组合,在后续完成分块上传的步骤中会用到它,因此需要将其保存起来。一般来讲这些PartETag
对象将被保存到List中。- 进度回调接口的使用可以参考“获取上传进度”一章。
完成分块上传
示例代码
1CompleteMultipartUploadRequest completeMultipartUploadRequest =
2 new CompleteMultipartUploadRequest(<BucketName>, <ObjectKey>,
3 initiateMultipartUploadResponse.getUploadId(), partETags);
4
5// 完成分块上传
6CompleteMultipartUploadResponse completeMultipartUploadResponse =
7 client.completeMultipartUpload(completeMultipartUploadRequest);
8
9// 打印Object的ETag
10System.out.println(completeMultipartUploadResponse.getETag());
说明:上面代码中的
partETags
是第二步中保存的partETag的列表,BOS收到用户提交的Part列表后,会逐一验证每个数据Part的有效性。当所有的数据Part验证通过后,BOS将把这些数据part组合成一个完整的Object。
完整示例
1import java.io.File;
2import java.io.FileInputStream;
3import java.io.IOException;
4import java.util.ArrayList;
5import java.util.List;
6import org.json.JSONException;
7import android.app.Activity;
8import android.os.Bundle;
9import com.baidubce.BceClientException;
10import com.baidubce.BceServiceException;
11import com.baidubce.auth.DefaultBceCredentials;
12import com.baidubce.demo.R;
13import com.baidubce.services.bos.BosClient;
14import com.baidubce.services.bos.BosClientConfiguration;
15import com.baidubce.services.bos.model.CompleteMultipartUploadRequest;
16import com.baidubce.services.bos.model.CompleteMultipartUploadResponse;
17import com.baidubce.services.bos.model.InitiateMultipartUploadRequest;
18import com.baidubce.services.bos.model.InitiateMultipartUploadResponse;
19import com.baidubce.services.bos.model.PartETag;
20import com.baidubce.services.bos.model.UploadPartRequest;
21import com.baidubce.services.bos.model.UploadPartResponse;
22
23public class ExampleActivity extends Activity {
24
25private String bucketName = <BucketName>;
26private String objectKey = <ObjectKey>;
27
28@Override
29protected void onCreate(Bundle savedInstanceState) {
30 super.onCreate(savedInstanceState);
31 setContentView(R.layout.activity_main);
32 new Thread(new Runnable() {
33 @Override
34 public void run() {
35 try {
36 BosClientConfiguration config = new BosClientConfiguration();
37 config.setCredentials(new DefaultBceCredentials(<AccessKeyID>, <SecretAccessKey>));
38 config.setEndpoint(<EndPoint>);
39 BosClient client = new BosClient(config);
40
41 // 开始Multipart Upload
42 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(<BucketName>, <ObjectKey>);
43 InitiateMultipartUploadResponse initiateMultipartUploadResponse =
44 client.initiateMultipartUpload(initiateMultipartUploadRequest);
45
46 // 打印UploadId
47 System.out.println("UploadId: " + initiateMultipartUploadResponse.getUploadId());
48
49 // 设置每块为 5MB
50 final long partSize = 1024 * 1024 * 5L;
51
52 File partFile = new File("/path/to/file.zip");
53
54 // 计算分块数目
55 int partCount = (int) (partFile.length() / partSize);
56 if (partFile.length() % partSize != 0) {
57 partCount++;
58 }
59
60 // 新建一个List保存每个分块上传后的ETag和PartNumber
61 List<PartETag> partETags = new ArrayList<PartETag>();
62
63 // 获取文件流
64 FileInputStream fis = new FileInputStream(partFile);
65 for (int i = 0; i < partCount; i++) {
66
67 // 计算每个分块的大小
68 long skipBytes = partSize * i;
69 long size = partSize < partFile.length() - skipBytes ?
70 partSize : partFile.length() - skipBytes;
71
72 byte[] buf = new byte[(int)size];
73 int offset = 0;
74 while (true) {
75 int byteRead = fis.read(buf, offset, (int)size);
76 offset += byteRead;
77 if (byteRead < 0 || offset >= size) {
78 break;
79 }
80 }
81 ByteArrayInputStream bufStream = new ByteArrayInputStream(buf);
82
83 // 创建UploadPartRequest,上传分块
84 UploadPartRequest uploadPartRequest = new UploadPartRequest();
85 uploadPartRequest.setBucketName(<BucketName>);
86 uploadPartRequest.setKey(<ObjectKey>);
87 uploadPartRequest.setUploadId(initiateMultipartUploadResponse.getUploadId());
88 uploadPartRequest.setInputStream(fis);
89 uploadPartRequest.setPartSize(size);
90 uploadPartRequest.setPartNumber(i + 1);
91 UploadPartResponse uploadPartResponse = client.uploadPart(uploadPartRequest);
92
93 // 将返回的PartETag保存到List中。
94 partETags.add(uploadPartResponse.getPartETag());
95
96 System.out.println(uploadPartResponse.getPartETag());
97 }
98 // 关闭文件
99 fis.close();
100
101 CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(<BucketName>, <ObjectKey>, initiateMultipartUploadResponse.getUploadId(), partETags);
102
103 // 完成分块上传
104 CompleteMultipartUploadResponse completeMultipartUploadResponse =
105 client.completeMultipartUpload(completeMultipartUploadRequest);
106
107 // 打印Object的ETag
108 System.out.println(completeMultipartUploadResponse.getETag());
109
110 } catch (BceServiceException e) {
111 System.out.println("Error ErrorCode: " + e.getErrorCode());
112 System.out.println("Error RequestId: " + e.getRequestId());
113 System.out.println("Error StatusCode: " + e.getStatusCode());
114 System.out.println("Error Message: " + e.getMessage());
115 System.out.println("Error ErrorType: " + e.getErrorType());
116 } catch (BceClientException e) {
117 System.out.println("Error Message: " + e.getMessage());
118 } catch (IOException e) {
119 // TODO Auto-generated catch block
120 e.printStackTrace();
121 } catch (JSONException e) {
122 // TODO Auto-generated catch block
123 e.printStackTrace();
124 }
125 }
126 }).start();
127}}
取消分块上传
用户可以使用abortMultipartUpload方法取消分块上传。
示例代码:
1AbortMultipartUploadRequest abortMultipartUploadRequest =
2 new AbortMultipartUploadRequest(<BucketName>, <ObjectKey>, <UploadId>);
3
4// 取消分块上传
5client.abortMultipartUpload(abortMultipartUploadRequest);
获取未完成的分块上传
用户可以使用 listMultipartUploads
方法获取Bucket内未完成的分块上传事件。
基本流程
- 创建listMultipartUploadsRequest类的实例,传入
<BucketName>
参数。 - 创建BOSClient类的实例,执行BOSClient.listMultipartUploads( )方法。
- listMultipartUploads()返回所有已上传part的信息。
示例代码
1ListMultipartUploadsRequest listMultipartUploadsRequest =
2 new ListMultipartUploadsRequest(<BucketName>);
3
4// 获取Bucket内所有上传事件
5ListMultipartUploadsResponse list = client.listMultipartUploads(listMultipartUploadsRequest);
6
7// 遍历所有上传事件
8for (MultipartUploadSummary multipartUpload : list.getMultipartUploads()) {
9 System.out.println("Key: " + multipartUpload.getKey() + " UploadId: " + multipartUpload.getUploadId());
10}
注意:
- 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中IsTruncated的值为True,同时返回NextKeyMarker作为下次读取的起点。
- 若想获取更多分块上传事件,可以使用KeyMarker参数分次读取。
完整示例
1import android.app.Activity;
2import android.os.Bundle;
3import com.baidubce.BceClientException;
4import com.baidubce.BceServiceException;
5import com.baidubce.auth.DefaultBceCredentials;
6import com.baidubce.demo.R;
7import com.baidubce.services.bos.BosClient;
8import com.baidubce.services.bos.BosClientConfiguration;
9import com.baidubce.services.bos.model.ListMultipartUploadsRequest;
10import com.baidubce.services.bos.model.ListMultipartUploadsResponse;
11import com.baidubce.services.bos.model.MultipartUploadSummary;
12
13public class ExampleActivity extends Activity {
14
15private String bucketName = <BucketName>;
16
17@Override
18protected void onCreate(Bundle savedInstanceState) {
19 super.onCreate(savedInstanceState);
20 setContentView(R.layout.activity_main);
21 new Thread(new Runnable() {
22 @Override
23 public void run() {
24 try {
25 BosClientConfiguration config = new BosClientConfiguration();
26 config.setCredentials(new DefaultBceCredentials(<AccessKeyID>, <SecretAccessKey>));
27 config.setEndpoint(<EndPoint>);
28 BosClient client = new BosClient(config);
29
30 ListMultipartUploadsRequest listMultipartUploadsRequest =
31 new ListMultipartUploadsRequest(<BucketName>);
32
33 // 获取Bucket内所有上传事件
34 ListMultipartUploadsResponse listing = client.listMultipartUploads(listMultipartUploadsRequest);
35
36 // 遍历所有上传事件
37 for (MultipartUploadSummary multipartUpload : listing.getMultipartUploads()) {
38 System.out.println("Key: " + multipartUpload.getKey() + " UploadId: " + multipartUpload.getUploadId());
39 }
40
41 } catch (BceServiceException e) {
42 System.out.println("Error ErrorCode: " + e.getErrorCode());
43 System.out.println("Error RequestId: " + e.getRequestId());
44 System.out.println("Error StatusCode: " + e.getStatusCode());
45 System.out.println("Error ErrorType: " + e.getErrorType());
46 System.out.println("Error Message: " + e.getMessage());
47 } catch (BceClientException e) {
48 System.out.println("Error Message: " + e.getMessage());
49 }
50 }
51 }).start();
52}}
获取所有已上传的分块信息
用户可以使用 listParts
方法获取某个上传事件中所有已上传的块。
基本流程
- 创建ListPartsRequest类的实例,传入
<BucketName>
,<ObjectKey>
,<UploadId>
参数。 - 创建BOSClient类的实例,执行BOSClient.listParts( )方法。
- listParts()返回所有已上传part的信息。
示例代码
1ListPartsRequest listPartsRequest = new ListPartsRequest(<BucketName>, <ObjectKey>, <UploadId>);
2
3// 获取上传的所有Part信息
4ListPartsResponse partListing = client.listParts(listPartsRequest);
5
6// 遍历所有Part
7for (PartSummary part : partListing.getParts()) {
8 System.out.println("PartNumber: " + part.getPartNumber() + " ETag: " + part.getETag());
9}
注意:
- 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中IsTruncated的值为True,同时返回NextPartNumberMarker作为下次读取的起点。
- 若想获取更多已上传的分块信息,可以使用PartNumberMarker参数分次读取。
完整示例
1import android.app.Activity;
2import android.os.Bundle;
3import com.baidubce.BceClientException;
4import com.baidubce.BceServiceException;
5import com.baidubce.auth.DefaultBceCredentials;
6import com.baidubce.demo.R;
7import com.baidubce.services.bos.BosClient;
8import com.baidubce.services.bos.BosClientConfiguration;
9import com.baidubce.services.bos.model.ListPartsRequest;
10import com.baidubce.services.bos.model.ListPartsResponse;
11import com.baidubce.services.bos.model.PartSummary;
12
13public class ExampleActivity extends Activity {
14
15private String bucketName = <BucketName>;
16private String objectKey = <ObjectKey>;
17private String uploadId = <UploadId>;
18
19@Override
20protected void onCreate(Bundle savedInstanceState) {
21 super.onCreate(savedInstanceState);
22 setContentView(R.layout.activity_main);
23 new Thread(new Runnable() {
24 @Override
25 public void run() {
26 try {
27 BosClientConfiguration config = new BosClientConfiguration();
28 config.setCredentials(new DefaultBceCredentials(<AccessKeyID>, <SecretAccessKey>));
29 config.setEndpoint(<EndPoint>);
30 BosClient client = new BosClient(config);
31
32 ListPartsRequest listPartsRequest = new ListPartsRequest(<BucketName>, <ObjectKey>, <UploadId>);
33 listPartsRequest.setMaxParts(100);
34 listPartsRequest.setPartNumberMarker(50);
35
36 // 获取上传的所有Part信息
37 ListPartsResponse partListing = client.listParts(listPartsRequest);
38
39 // 遍历所有Part
40 for (PartSummary part : partListing.getParts()) {
41 System.out.println("PartNumber: " + part.getPartNumber() + " ETag: " + part.getETag());
42 }
43
44 } catch (BceServiceException e) {
45 System.out.println("Error ErrorCode: " + e.getErrorCode());
46 System.out.println("Error RequestId: " + e.getRequestId());
47 System.out.println("Error StatusCode: " + e.getStatusCode());
48 System.out.println("Error ErrorType: " + e.getErrorType());
49 System.out.println("Error Message: " + e.getMessage());
50 } catch (BceClientException e) {
51 System.out.println("Error Message: " + e.getMessage());
52 }
53 }
54 }).start();
55}}
封装分块上传
在Android SDK中,Bos为用户提供了putSuperObjectFromFile接口,它对分块上传涉及到的initiateMultipartUpload、UploadPart、completeMultipartUpload三个方法进行封装,用户只需调用该接口即可完成分块上传,同时,支持进度同步回调。
-
简单示例:
Java1File file = new File("/path/to/file.zip"); 2PutSuperObjectRequest request = new PutSuperObjectRequest(bucketName, objectKey, file); 3bosClient.putSuperObjectFromFile(request);
-
示例:设置分块大小为2MB,线程数为4,并且每1024个byte同步进度回调
Java1BosClientConfiguration config=new BosClientConfiguration(); 2config.setUploadSegmentPart(1024); 3File file = new File("/path/to/file.zip"); 4PutSuperObjectRequest request = new PutSuperObjectRequest(bucketName, objectKey, 5 file, 1024 * 1024 * 2L, 4); 6request.setProgressCallback(new BosProgressCallback<PutSuperObjectRequest>() { 7 @Override 8 public void onProgress(PutSuperObjectRequest request, long currentSize, long totalSize) { 9 Log.e(currentSize + "", totalSize + ""); 10 } 11}); 12bosClient.putSuperObjectFromFile(request);
注意:
- 默认情况下,分块大小是5MB,线程数是5,进度回调周期是每2048个byte。
- 若一个大文件耗时很长,用户想结束分块上传,可调用PutSuperObjectRequest中的cancel()方法。