下载到本地文件(Java SDK)

本文介绍如何将存储空间(Bucket)中的文件(Object)下载到本地文件。

注意事项

  • 本文以华东1(杭州)外网Endpoint为例。如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的RegionEndpoint的对应关系,请参见OSS地域和访问域名

  • 本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见Java配置访问凭证

  • 本文以OSS域名新建OSSClient为例。如果您希望通过自定义域名、STS等方式新建OSSClient,请参见常见场景配置示例

权限说明

阿里云账号默认拥有全部权限。阿里云账号下的RAM用户或RAM角色默认没有任何权限,需要阿里云账号或账号管理员通过RAM PolicyBucket Policy授予操作权限。

API

Action

说明

GetObject

oss:GetObject

下载Object。

oss:GetObjectVersion

下载Object时,如果通过versionId指定了Object的版本,则需要授予此操作的权限。

kms:Decrypt

下载Object时,如果Object的元数据包含X-Oss-Server-Side-Encryption: KMS,则需要此操作的权限。

下载单个文件到本地

以下代码用于将examplebuckettestfolder目录下的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示例