如果需要上传较大的对象,建议分成多个数据块(part)来分别上传,最后调用合并分片将上传的数据块合并为一个对象。
tos:PutObject
权限,具体操作,请参见权限配置指南。tos:AbortMultipartUpload
权限,具体操作,请参见权限配置指南。使用 TOS Java SDK 进行分片上传包含以下三个步骤。
createMultipartUpload
接口返回 TOS 创建的全局唯一uploadId
。uploadPart
接口上传分片数据。说明
uploadId
标识),分片编号(partNumber
)标识了该分片在整个对象中的相对位置。若通过同一分片编号多次上传数据,TOS 会覆盖已有的数据,并以最后一次上传的数据为准。uploadPart
接口返回分片数据的 MD5 值,可通过 ETag 字段获取。合并分片时,您需指定当前分片上传任务中已上传的所有分片信息(分片编号、ETag值)。completeMultipartUpload
接口将所有分片合并成一个完整的对象。以下代码通过三种上传方式展示如何通过 Java SDK 将本地文件分片上传到目标桶 bucket-example 中的 example_dir 目录下的 example_object.txt 文件。
import com.volcengine.tos.TOSV2; import com.volcengine.tos.TOSV2ClientBuilder; import com.volcengine.tos.TosClientException; import com.volcengine.tos.TosServerException; import com.volcengine.tos.comm.io.TosRepeatableBoundedFileInputStream; import com.volcengine.tos.model.object.*; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; public class MultipartUploadWithFileInputStreamFullExample { public static void main(String[] args) { String endpoint = "your endpoint"; String region = "your region"; String accessKey = System.getenv("TOS_ACCESS_KEY"); String secretKey = System.getenv("TOS_SECRET_KEY"); String bucketName = "bucket-example"; // 对象名,模拟 example_dir 下的 example_object.txt 文件 String objectKey = "example_dir/example_object.txt"; // 本地文件路径,请保证文件存在,暂不支持文件夹功能。 String filePath = "example_dir/example_file.txt"; TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey); try{ // 1. 初始化分片上传 String uploadId = null; CreateMultipartUploadInput create = new CreateMultipartUploadInput().setBucket(bucketName).setKey(objectKey); // // 如果需要设置对象的元数据,需要在初始化分片上传的时候设置 // // 以下所有设置参数均为可选,参数值仅供参考,请根据业务实际需要进行设置。 // ObjectMetaRequestOptions options = new ObjectMetaRequestOptions(); // // 设置对象访问权限,此处为私有权限 // options.setAclType(ACLType.ACL_PRIVATE); // // 设置对象存储类型 // options.setStorageClass(StorageClassType.STORAGE_CLASS_STANDARD); // // SDK 默认会根据 objectKey 后缀识别 Content-Type,也可以自定义设置 // options.setContentType("text/plain"); // // 自定义对象的元数据,对于自定义的元数据,SDK 会自动对 key 添加 // // "X-Tos-Meta-" 的前缀,因此用户无需自行添加。 // Map<String, String> custom = new HashMap<>(); // custom.put("name", "volc_user"); // // 在 TOS 服务端存储的元数据为:"X-Tos-Meta-name: volc_user" // options.setCustomMetadata(custom); // create.setOptions(options); CreateMultipartUploadOutput createOutput = tos.createMultipartUpload(create); System.out.println("createMultipartUpload succeed, uploadId is " + createOutput.getUploadID()); // 从 createMultipartUpload 结果中获取 uploadId,用于后续的分片上传和合并分片。 uploadId = createOutput.getUploadID(); // 2. 上传分片。 // 假设分片大小统一为 5MB。 long partSize = 5 * 1024 * 1024; // 已上传分片列表,每次上传分片后记录。 List<UploadedPartV2> uploadedParts = new ArrayList<>(); // 以下代码展示读取同一个文件到 FileInputStream,按照每 5MB 大小从头到尾读取文件的一部分进行上传。 // fileSize 为文件总大小 long fileSize = new File(filePath).length(); // offset 为读取文件的位置。如果 offset < fileSize,说明已读到文件末尾,不再读取上传(此时文件已上传完成) long offset = 0; for (int i = 1; offset < fileSize; ++i) { try(FileInputStream content = new FileInputStream(filePath);){ // 每次只上传文件的一部分,需要跳过前面已上传的部分,即为 offset 长度。 content.skip(offset); InputStream wrappedContent = new TosRepeatableBoundedFileInputStream(content, partSize); // 注意 partNumber 从 1 开始计数。 int partNumber = i; if (fileSize-offset < partSize) { // 如果 skip 过后剩余的数据长度小于 partSize,即剩余数据长度没有达到 partSize // 说明已到达最后一个分片,需要修改分片大小 partSize = fileSize-offset; } UploadPartV2Input input = new UploadPartV2Input().setBucket(bucketName) .setKey(objectKey).setUploadID(uploadId).setPartNumber(partNumber) .setContentLength(partSize).setContent(wrappedContent); UploadPartV2Output output = tos.uploadPart(input); // 存储已上传分片信息,必须设置 partNumber 和 etag uploadedParts.add(new UploadedPartV2().setPartNumber(i).setEtag(output.getEtag())); System.out.printf("uploadPart succeed, partNumber is %d, etag is %s, crc64 value is %s\n", output.getPartNumber(), output.getEtag(), output.getHashCrc64ecma()); // 每上传一次分片,需要更新 offset 的值 offset += partSize; } catch (IOException e) { System.out.println("uploadPart read file failed"); e.printStackTrace(); } } // 3. 合并已上传的分片 // 需要设置桶名、对象名、uploadId和已上传的分片列表 CompleteMultipartUploadV2Input complete = new CompleteMultipartUploadV2Input().setBucket(bucketName) .setKey(objectKey).setUploadID(uploadId); complete.setUploadedParts(uploadedParts); // 从 2.6.0 版本开始,SDK 支持设置 completeAll 参数 // 如果设置 completeAll 为 true,则 TOS 会列举当前 uploadId 已上传的所有 part, // 并根据 partNumber 的序号排序执行 completeMultipartUpload 操作。 // 如果设置 completeAll 为 true,则不允许调用 setUploadedParts 设置 uploadedParts,否则报错。 // complete.setUploadedParts(null); // complete.setCompleteAll(true); CompleteMultipartUploadV2Output completedOutput = tos.completeMultipartUpload(complete); System.out.printf("completeMultipartUpload succeed, etag is %s, crc64 value is %s, location is %s.\n", completedOutput.getEtag(), completedOutput.getHashCrc64ecma(), completedOutput.getLocation()); } catch (TosClientException e) { // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送 System.out.println("uploadPart failed"); System.out.println("Message: " + e.getMessage()); if (e.getCause() != null) { e.getCause().printStackTrace(); } } catch (TosServerException e) { // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息 System.out.println("uploadPart failed"); System.out.println("StatusCode: " + e.getStatusCode()); System.out.println("Code: " + e.getCode()); System.out.println("Message: " + e.getMessage()); System.out.println("RequestID: " + e.getRequestID()); } catch (Throwable t) { // 作为兜底捕获其他异常,一般不会执行到这里 System.out.println("uploadPart failed"); System.out.println("unexpected exception, message: " + t.getMessage()); } } }
uploadPartFromFile
接口进行分片上传。如果待上传数据是本地文件,推荐用此接口。import com.volcengine.tos.TOSV2; import com.volcengine.tos.TOSV2ClientBuilder; import com.volcengine.tos.TosClientException; import com.volcengine.tos.TosServerException; import com.volcengine.tos.model.object.*; import java.io.File; import java.util.ArrayList; import java.util.List; public class MultipartUploadWithFileFullExample { public static void main(String[] args) { String endpoint = "your endpoint"; String region = "your region"; String accessKey = System.getenv("TOS_ACCESS_KEY"); String secretKey = System.getenv("TOS_SECRET_KEY"); String bucketName = "bucket-example"; // 对象名,模拟 example_dir 下的 example_object.txt 文件 String objectKey = "example_dir/example_object.txt"; // 本地文件路径,请保证文件存在,暂不支持文件夹功能。 String filePath = "example_dir/example_file.txt"; TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey); String uploadId = null; // 1. 初始化分片上传 try{ CreateMultipartUploadInput create = new CreateMultipartUploadInput().setBucket(bucketName).setKey(objectKey); // // 如果需要设置对象的元数据,需要在初始化分片上传的时候设置 // // 以下所有设置参数均为可选,参数值仅供参考,请根据业务实际需要进行设置。 // ObjectMetaRequestOptions options = new ObjectMetaRequestOptions(); // // 设置对象访问权限,此处为私有权限 // options.setAclType(ACLType.ACL_PRIVATE); // // 设置对象存储类型 // options.setStorageClass(StorageClassType.STORAGE_CLASS_STANDARD); // // SDK 默认会根据 objectKey 后缀识别 Content-Type,也可以自定义设置 // options.setContentType("text/plain"); // // 自定义对象的元数据,对于自定义的元数据,SDK 会自动对 key 添加 // // "X-Tos-Meta-" 的前缀,因此用户无需自行添加。 // Map<String, String> custom = new HashMap<>(); // custom.put("name", "volc_user"); // // 在 TOS 服务端存储的元数据为:"X-Tos-Meta-name: volc_user" // options.setCustomMetadata(custom); // create.setOptions(options); CreateMultipartUploadOutput createOutput = tos.createMultipartUpload(create); System.out.println("createMultipartUpload succeed, uploadId is " + createOutput.getUploadID()); // 从 createMultipartUpload 结果中获取 uploadId,用于后续的分片上传和合并分片。 uploadId = createOutput.getUploadID(); // 2. 上传分片。 // 假设分片大小统一为 5MB。 long partSize = 5 * 1024 * 1024; // 已上传分片列表,每次上传分片后记录。 List<UploadedPartV2> uploadedParts = new ArrayList<>(); // 以下代码展示如何使用 uploadPartFromFile 接口上传分片,按照每 5MB 大小从头到尾读取文件的一部分进行上传。 // fileSize 为文件总大小 long fileSize = new File(filePath).length(); // offset 为读取文件的位置。如果 offset < fileSize,说明已读到文件末尾,不再读取上传(此时文件已上传完成) long offset = 0; for (int i = 1; offset < fileSize; ++i) { // 注意 partNumber 从 1 开始计数。 int partNumber = i; if (fileSize-offset < partSize) { // 如果 skip 过后剩余的数据长度小于 partSize,即剩余数据长度没有达到 partSize // 说明已到达最后一个分片,需要修改分片大小 partSize = fileSize-offset; } UploadPartFromFileInput input = new UploadPartFromFileInput().setBucket(bucketName) .setKey(objectKey).setUploadID(uploadId).setPartNumber(partNumber) .setFilePath(filePath).setPartSize(partSize).setOffset(offset); UploadPartFromFileOutput output = tos.uploadPartFromFile(input); // 存储已上传分片信息,必须设置 partNumber 和 etag uploadedParts.add(new UploadedPartV2().setPartNumber(partNumber).setEtag(output.getEtag())); System.out.printf("uploadPart succeed, partNumber is %d, etag is %s, crc64 value is %s\n", output.getPartNumber(), output.getEtag(), output.getHashCrc64ecma()); // 每上传一次分片,需要更新 offset 的值 offset += partSize; } // 3. 合并已上传的分片 // 需要设置桶名、对象名、uploadId和已上传的分片列表 CompleteMultipartUploadV2Input complete = new CompleteMultipartUploadV2Input().setBucket(bucketName) .setKey(objectKey).setUploadID(uploadId); complete.setUploadedParts(uploadedParts); // 从 2.6.0 版本开始,SDK 支持设置 completeAll 参数 // 如果设置 completeAll 为 true,则 TOS 会列举当前 uploadId 已上传的所有 part, // 并根据 partNumber 的序号排序执行 completeMultipartUpload 操作。 // 如果设置 completeAll 为 true,则不允许调用 setUploadedParts 设置 uploadedParts,否则报错。 // complete.setUploadedParts(null); // complete.setCompleteAll(true); CompleteMultipartUploadV2Output completedOutput = tos.completeMultipartUpload(complete); System.out.printf("completeMultipartUpload succeed, etag is %s, crc64 value is %s, location is %s.\n", completedOutput.getEtag(), completedOutput.getHashCrc64ecma(), completedOutput.getLocation()); } catch (TosClientException e) { // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送 System.out.println("multipart upload failed"); System.out.println("Message: " + e.getMessage()); if (e.getCause() != null) { e.getCause().printStackTrace(); } } catch (TosServerException e) { // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息 System.out.println("multipart upload failed"); System.out.println("StatusCode: " + e.getStatusCode()); System.out.println("Code: " + e.getCode()); System.out.println("Message: " + e.getMessage()); System.out.println("RequestID: " + e.getRequestID()); } catch (Throwable t) { // 作为兜底捕获其他异常,一般不会执行到这里 System.out.println("multipart upload failed"); System.out.println("unexpected exception, message: " + t.getMessage()); } } }
import com.volcengine.tos.TOSV2; import com.volcengine.tos.TOSV2ClientBuilder; import com.volcengine.tos.TosClientException; import com.volcengine.tos.TosServerException; import com.volcengine.tos.model.object.*; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.*; public class MultipartUploadWithByteArrayInputStreamFullExample { public static void main(String[] args) { String endpoint = "your endpoint"; String region = "your region"; String accessKey = System.getenv("TOS_ACCESS_KEY"); String secretKey = System.getenv("TOS_SECRET_KEY"); String bucketName = "bucket-example"; // 对象名,模拟 example_dir 下的 example_object.txt 文件 String objectKey = "example_dir/example_object.txt"; TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey); String uploadId = null; // 1. 初始化分片上传 try{ CreateMultipartUploadInput create = new CreateMultipartUploadInput().setBucket(bucketName).setKey(objectKey); // // 如果需要设置对象的元数据,需要在初始化分片上传的时候设置 // // 以下所有设置参数均为可选,参数值仅供参考,请根据业务实际需要进行设置。 // ObjectMetaRequestOptions options = new ObjectMetaRequestOptions(); // // 设置对象访问权限,此处为私有权限 // options.setAclType(ACLType.ACL_PRIVATE); // // 设置对象存储类型 // options.setStorageClass(StorageClassType.STORAGE_CLASS_STANDARD); // // SDK 默认会根据 objectKey 后缀识别 Content-Type,也可以自定义设置 // options.setContentType("text/plain"); // // 自定义对象的元数据,对于自定义的元数据,SDK 会自动对 key 添加 // // "X-Tos-Meta-" 的前缀,因此用户无需自行添加。 // Map<String, String> custom = new HashMap<>(); // custom.put("name", "volc_user"); // // 在 TOS 服务端存储的元数据为:"X-Tos-Meta-name: volc_user" // options.setCustomMetadata(custom); // options.setServerSideEncryption("AES256"); // create.setOptions(options); CreateMultipartUploadOutput createOutput = tos.createMultipartUpload(create); System.out.println("createMultipartUpload succeed, uploadId is " + createOutput.getUploadID()); // 从 createMultipartUpload 结果中获取 uploadId,用于后续的分片上传和合并分片。 uploadId = createOutput.getUploadID(); // 2. 上传分片。 // 假设分片大小统一为 5MB。 long partSize = 5 * 1024 * 1024; // 已上传分片列表,每次上传分片后记录。 List<UploadedPartV2> uploadedParts = new ArrayList<>(); // 以下代码展示如何使用 uploadPart 接口上传内存中的 byte 数组。 for (int i = 1; i <= 3; ++i) { // 注意 partNumber 从 1 开始计数。 int partNumber = i; // byte 数组数据,对于小 size 的数据可以使用。 // 大 size 的数据用 byte 数组内存开销较大,而且 int 长度最长只能支持到约 4GB,不建议使用。 byte[] data = new byte[(int)partSize]; Arrays.fill(data, (byte)'A'); InputStream content = new ByteArrayInputStream(data); UploadPartV2Input input = new UploadPartV2Input().setBucket(bucketName) .setKey(objectKey).setUploadID(uploadId).setPartNumber(partNumber) .setContentLength(partSize).setContent(content); UploadPartV2Output output = tos.uploadPart(input); // 存储已上传分片信息,必须设置 partNumber 和 etag uploadedParts.add(new UploadedPartV2().setPartNumber(partNumber).setEtag(output.getEtag())); System.out.printf("uploadPart succeed, partNumber is %d, etag is %s, crc64 value is %s\n", output.getPartNumber(), output.getEtag(), output.getHashCrc64ecma()); } // 3. 合并已上传的分片 // 需要设置桶名、对象名、uploadId和已上传的分片列表 CompleteMultipartUploadV2Input complete = new CompleteMultipartUploadV2Input().setBucket(bucketName) .setKey(objectKey).setUploadID(uploadId); complete.setUploadedParts(uploadedParts); // 从 2.6.0 版本开始,SDK 支持设置 completeAll 参数 // 如果设置 completeAll 为 true,则 TOS 会列举当前 uploadId 已上传的所有 part, // 并根据 partNumber 的序号排序执行 completeMultipartUpload 操作。 // 如果设置 completeAll 为 true,则不允许调用 setUploadedParts 设置 uploadedParts,否则报错。 // complete.setUploadedParts(null); // complete.setCompleteAll(true); CompleteMultipartUploadV2Output completedOutput = tos.completeMultipartUpload(complete); System.out.printf("completeMultipartUpload succeed, etag is %s, crc64 value is %s, location is %s.\n", completedOutput.getEtag(), completedOutput.getHashCrc64ecma(), completedOutput.getLocation()); } catch (TosClientException e) { // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送 System.out.println("multipart upload failed"); System.out.println("Message: " + e.getMessage()); if (e.getCause() != null) { e.getCause().printStackTrace(); } } catch (TosServerException e) { // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息 System.out.println("multipart upload failed"); System.out.println("StatusCode: " + e.getStatusCode()); System.out.println("Code: " + e.getCode()); System.out.println("Message: " + e.getMessage()); System.out.println("RequestID: " + e.getRequestID()); } catch (Throwable t) { // 作为兜底捕获其他异常,一般不会执行到这里 System.out.println("multipart upload failed"); System.out.println("unexpected exception, message: " + t.getMessage()); } } }
您可以通过 abortMultipartUpload
接口来取消分片上传任务。当一个分片任务被取消后, TOS 会将已上传的分片数据删除,同时您无法再对此分片任务进行任何操作。
以下代码展示如何取消桶 bucket-example
中的 example_dir
目录下的 example_object.txt
对象的分片上传任务。
import com.volcengine.tos.TOSV2; import com.volcengine.tos.TOSV2ClientBuilder; import com.volcengine.tos.TosClientException; import com.volcengine.tos.TosServerException; import com.volcengine.tos.model.object.AbortMultipartUploadInput; public class AbortMultipartUploadExample { public static void main(String[] args) { String endpoint = "your endpoint"; String region = "your region"; String accessKey = System.getenv("TOS_ACCESS_KEY"); String secretKey = System.getenv("TOS_SECRET_KEY"); String bucketName = "bucket-example"; // 指定的需要取消的分片上传任务的uploadId, // 需保证该 uploadId 已通过初始化分片上传接口 createMultipartUpload 调用返回。 // 否则,对于不存在的 uploadId 会返回 404 not found。 String uploadId = "the specific uploadId"; // 与 uploadId 对应的对象 key,模拟 example_dir 下的 example_object.txt 文件 String objectKey = "example_dir/example_object.txt"; TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey); try{ AbortMultipartUploadInput input = new AbortMultipartUploadInput().setBucket(bucketName) .setKey(objectKey).setUploadID(uploadId); tos.abortMultipartUpload(input); } catch (TosClientException e) { // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送 System.out.println("abortMultipartUpload failed"); System.out.println("Message: " + e.getMessage()); if (e.getCause() != null) { e.getCause().printStackTrace(); } } catch (TosServerException e) { // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息 System.out.println("abortMultipartUpload failed"); System.out.println("StatusCode: " + e.getStatusCode()); System.out.println("Code: " + e.getCode()); System.out.println("Message: " + e.getMessage()); System.out.println("RequestID: " + e.getRequestID()); } catch (Throwable t) { // 作为兜底捕获其他异常,一般不会执行到这里 System.out.println("abortMultipartUpload failed"); System.out.println("unexpected exception, message: " + t.getMessage()); } } }
您可以通过 SDK 的 listParts
接口列举某个 uploadId
下已经上传的分片列表。
以下代码展示如何列举桶 bucket-example
中的 example_dir
目录下的 example_object.txt
对象所有已上传的分片列表。
import com.volcengine.tos.TOSV2; import com.volcengine.tos.TOSV2ClientBuilder; import com.volcengine.tos.TosClientException; import com.volcengine.tos.TosServerException; import com.volcengine.tos.model.object.ListPartsInput; import com.volcengine.tos.model.object.ListPartsOutput; import com.volcengine.tos.model.object.UploadedPartV2; public class ListPartsExample { public static void main(String[] args) { String endpoint = "your endpoint"; String region = "your region"; String accessKey = System.getenv("TOS_ACCESS_KEY"); String secretKey = System.getenv("TOS_SECRET_KEY"); String bucketName = "bucket-example"; // 指定的需要列举的分片上传任务的uploadID, // 需保证该 uploadId 已通过初始化分片上传接口 createMultipartUpload 调用返回, // 否则,对于不存在的 uploadId 会返回 404 not found。 String uploadId = "the specific uploadId"; // 与 uploadId 对应的对象 key,模拟 example_dir 下的 example_object.txt 文件 String objectKey = "example_dir/example_object.txt"; TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey); try{ // maxParts 设置返回的记录最大条数 int maxParts = 1000; // isTruncated 为 true 代表后续还有数据,为 false 代表已经列举完全部数据 boolean isTruncated = true; int partNumberMarker = 0; int total = 0; while(isTruncated) { ListPartsInput input = new ListPartsInput().setBucket(bucketName).setKey(objectKey) // 必须设置 bucket, key, uploadId .setUploadID(uploadId).setPartNumberMarker(partNumberMarker).setMaxParts(maxParts); ListPartsOutput output = tos.listParts(input); System.out.printf("listParts succeed, is truncated? %b, next partNumber marker is %d \n", output.isTruncated(), output.getNextPartNumberMarker()); if (output.getUploadedParts() != null) { for (int i = 0; i < output.getUploadedParts().size(); i++) { UploadedPartV2 upload = output.getUploadedParts().get(i); System.out.printf("Uploaded part, partNumber is %d, etag is %s, lastModified is %s, " + "size is %d.\n", upload.getPartNumber(), upload.getEtag(), upload.getLastModified(), upload.getSize()); } total += output.getUploadedParts().size(); } isTruncated = output.isTruncated(); partNumberMarker = output.getNextPartNumberMarker(); } } catch (TosClientException e) { // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送 System.out.println("listParts failed"); System.out.println("Message: " + e.getMessage()); if (e.getCause() != null) { e.getCause().printStackTrace(); } } catch (TosServerException e) { // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息 System.out.println("listParts failed"); System.out.println("StatusCode: " + e.getStatusCode()); System.out.println("Code: " + e.getCode()); System.out.println("Message: " + e.getMessage()); System.out.println("RequestID: " + e.getRequestID()); } catch (Throwable t) { // 作为兜底捕获其他异常,一般不会执行到这里 System.out.println("listParts failed"); System.out.println("unexpected exception, message: " + t.getMessage()); } } }