本文介绍如何将存储空间(Bucket)中的文件(Object)下载到本地文件。
注意事项
本文以华东1(杭州)外网Endpoint为例。如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见OSS地域和访问域名。
本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见Java配置访问凭证。
本文以OSS域名新建OSSClient为例。如果您希望通过自定义域名、STS等方式新建OSSClient,请参见常见场景配置示例。
权限说明
阿里云账号默认拥有全部权限。阿里云账号下的RAM用户或RAM角色默认没有任何权限,需要阿里云账号或账号管理员通过RAM Policy或Bucket Policy授予操作权限。
API | Action | 说明 |
GetObject |
| 下载Object。 |
| 下载Object时,如果通过versionId指定了Object的版本,则需要授予此操作的权限。 | |
| 下载Object时,如果Object的元数据包含X-Oss-Server-Side-Encryption: KMS,则需要此操作的权限。 |
下载单个文件到本地
以下代码用于将examplebucket中testfolder目录下的exampleobject.txt下载到本地D:\localpath路径下的examplefile.txt。
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.GetObjectRequest;
import java.io.File;
public class Demo {
public static void main(String[] args) throws Exception {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
//从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填写Bucket名称,例如examplebucket。
String bucketName = "examplebucket";
// 填写不包含Bucket名称在内的Object完整路径,例如testfolder/exampleobject.txt。
String objectName = "testfolder/exampleobject.txt";
// 填写Object下载到本地的完整路径。
String pathName = "D:\\localpath\\examplefile.txt";
// 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
String region = "cn-hangzhou";
// 创建OSSClient实例。
// 当OSSClient实例不再使用时,调用shutdown方法以释放资源。
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
OSS ossClient = OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(credentialsProvider)
.clientConfiguration(clientBuilderConfiguration)
.region(region)
.build();
try {
// 下载Object到本地文件,并保存到指定的本地路径中。如果指定的本地文件存在会覆盖,不存在则新建。
// 如果未指定本地路径,则下载后的文件默认保存到示例程序所属项目对应本地路径中。
ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(pathName));
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}
批量下载文件到本地
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.*;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class BatchDownloadObject {
public static void main(String[] args) throws Exception {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
//从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填写Bucket名称,例如examplebucket。
String bucketName = "examplebuckett";
// 填写要下载的文件夹前缀,例如"images/",如果要下载整个bucket则填写""
String folderPrefix = "";
// 填写本地下载目录
String localDownloadPath = "./batch_downloads/";
// 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
String region = "cn-hangzhou";
// 创建OSSClient实例。
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
OSS ossClient = OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(credentialsProvider)
.clientConfiguration(clientBuilderConfiguration)
.region(region)
.build();
try {
// 确保本地下载目录存在
createDirectoryIfNotExists(localDownloadPath);
// 批量下载文件
batchDownloadObjects(ossClient, bucketName, folderPrefix, localDownloadPath);
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
/**
* 批量下载OSS中的文件到本地
* @param ossClient OSS客户端
* @param bucketName 存储桶名称
* @param folderPrefix 文件夹前缀
* @param localDownloadPath 本地下载路径
*/
public static void batchDownloadObjects(OSS ossClient, String bucketName, String folderPrefix, String localDownloadPath) {
String nextMarker = null;
ObjectListing objectListing;
int downloadCount = 0;
long totalSize = 0;
int failedCount = 0;
System.out.println("开始批量下载文件...");
System.out.println("源路径: " + bucketName + "/" + folderPrefix);
System.out.println("本地路径: " + localDownloadPath);
System.out.println("----------------------------------------");
long startTime = System.currentTimeMillis();
do {
// 列举文件
ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName)
.withPrefix(folderPrefix)
.withMarker(nextMarker)
.withMaxKeys(1000); // 每次最多列举1000个文件
objectListing = ossClient.listObjects(listObjectsRequest);
List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
for (OSSObjectSummary s : sums) {
// 跳过文件夹(以/结尾的对象)
if (s.getKey().endsWith("/")) {
continue;
}
try {
// 构造本地文件路径
String localFilePath = constructLocalFilePath(localDownloadPath, s.getKey(), folderPrefix);
// 创建本地文件的父目录
File localFile = new File(localFilePath);
createDirectoryIfNotExists(localFile.getParent());
// 检查文件是否已存在,避免重复下载
if (localFile.exists() && localFile.length() == s.getSize()) {
System.out.println("文件已存在,跳过下载: " + s.getKey());
downloadCount++;
totalSize += s.getSize();
continue;
}
// 下载文件
System.out.println("正在下载: " + s.getKey() + " -> " + localFilePath);
// 使用临时文件避免下载过程中的文件损坏
File tempFile = new File(localFilePath + ".tmp");
try {
ossClient.getObject(new GetObjectRequest(bucketName, s.getKey()), tempFile);
// 下载完成后重命名为最终文件
if (tempFile.renameTo(localFile)) {
downloadCount++;
totalSize += s.getSize();
System.out.println("下载完成: " + s.getKey() + " (大小: " + formatFileSize(s.getSize()) + ")");
} else {
throw new IOException("无法重命名临时文件: " + tempFile.getPath() + " -> " + localFile.getPath());
}
} finally {
// 清理临时文件
if (tempFile.exists()) {
tempFile.delete();
}
}
} catch (Exception e) {
failedCount++;
System.err.println("下载文件失败: " + s.getKey() + ", 错误: " + e.getMessage());
// 记录详细错误信息用于调试
if (e instanceof OSSException) {
OSSException oe = (OSSException) e;
System.err.println("OSS错误码: " + oe.getErrorCode() + ", 请求ID: " + oe.getRequestId());
}
}
}
nextMarker = objectListing.getNextMarker();
} while (objectListing.isTruncated());
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println("----------------------------------------");
System.out.println("批量下载完成!");
System.out.println("总共下载文件数: " + downloadCount);
System.out.println("下载失败文件数: " + failedCount);
System.out.println("总下载大小: " + formatFileSize(totalSize));
System.out.println("总耗时: " + formatDuration(duration));
System.out.println("平均下载速度: " + formatSpeed(totalSize, duration));
}
/**
* 构造本地文件路径
* @param localDownloadPath 本地下载根目录
* @param objectKey OSS对象键
* @param folderPrefix 文件夹前缀
* @return 本地文件完整路径
*/
private static String constructLocalFilePath(String localDownloadPath, String objectKey, String folderPrefix) {
// 移除前缀,保持相对路径结构
String relativePath = objectKey;
if (folderPrefix != null && !folderPrefix.isEmpty() && objectKey.startsWith(folderPrefix)) {
relativePath = objectKey.substring(folderPrefix.length());
}
// 确保路径分隔符正确
String normalizedPath = localDownloadPath.endsWith(File.separator) ?
localDownloadPath : localDownloadPath + File.separator;
return normalizedPath + relativePath.replace("/", File.separator);
}
/**
* 创建目录(如果不存在)
* @param dirPath 目录路径
*/
private static void createDirectoryIfNotExists(String dirPath) {
if (dirPath == null || dirPath.isEmpty()) {
return;
}
try {
File dir = new File(dirPath);
if (!dir.exists()) {
boolean created = dir.mkdirs();
if (created) {
System.out.println("创建目录: " + dirPath);
} else if (!dir.exists()) {
throw new IOException("无法创建目录: " + dirPath);
}
}
} catch (Exception e) {
System.err.println("创建目录失败: " + dirPath + ", 错误: " + e.getMessage());
throw new RuntimeException("创建目录失败", e);
}
}
/**
* 格式化文件大小
* @param size 文件大小(字节)
* @return 格式化后的文件大小字符串
*/
private static String formatFileSize(long size) {
if (size < 1024) {
return size + " B";
} else if (size < 1024 * 1024) {
return String.format("%.2f KB", size / 1024.0);
} else if (size < 1024 * 1024 * 1024) {
return String.format("%.2f MB", size / (1024.0 * 1024.0));
} else {
return String.format("%.2f GB", size / (1024.0 * 1024.0 * 1024.0));
}
}
/**
* 格式化时间长度
* @param duration 时间长度(毫秒)
* @return 格式化后的时间字符串
*/
private static String formatDuration(long duration) {
long seconds = duration / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
if (hours > 0) {
return String.format("%d小时%d分钟%d秒", hours, minutes % 60, seconds % 60);
} else if (minutes > 0) {
return String.format("%d分钟%d秒", minutes, seconds % 60);
} else {
return String.format("%d秒", seconds);
}
}
/**
* 格式化下载速度
* @param totalBytes 总字节数
* @param durationMs 持续时间(毫秒)
* @return 格式化后的速度字符串
*/
private static String formatSpeed(long totalBytes, long durationMs) {
if (durationMs <= 0) {
return "N/A";
}
double bytesPerSecond = (double) totalBytes / (durationMs / 1000.0);
if (bytesPerSecond < 1024) {
return String.format("%.2f B/s", bytesPerSecond);
} else if (bytesPerSecond < 1024 * 1024) {
return String.format("%.2f KB/s", bytesPerSecond / 1024.0);
} else if (bytesPerSecond < 1024 * 1024 * 1024) {
return String.format("%.2f MB/s", bytesPerSecond / (1024.0 * 1024.0));
} else {
return String.format("%.2f GB/s", bytesPerSecond / (1024.0 * 1024.0 * 1024.0));
}
}
}
相关API
如果您的程序自定义要求较高,您可以直接发起REST API请求。直接发起REST API请求需要手动编写代码计算签名。更多信息,请参见GetObject。
相关文档
关于下载到本地文件的完整示例代码,请参见GitHub示例。
该文章对您有帮助吗?