我正在使用Amazon S3 Java SDK在(模拟的)子文件夹中获取文件列表。这段代码相当标准(AWSConfiguration是一个包含大量帐户特定值的类):因为它们都包含相同的前缀)。若要仅列出直接在/images/cars/default.png“文件夹”内部的对象,我具有以下功能(在名为S3Asset的类中)

String prefix = "/images/cars/";
int prefix_size = prefix.length();
AmazonS3 s3 = new AmazonS3Client(new AWSConfiguration());
ObjectListing objectListing = s3.listObjects(new ListObjectsRequest().
    withBucketName(AWSConfiguration.BUCKET).
    withPrefix(prefix));


前缀后面的任何/images/cars/ford/Default.png,以表明它在子文件夹中。这使我可以使用以下代码遍历对象(为清楚起见,我会修剪前缀):

public static boolean isInsideFolder(int root_size, String key) {
  return (key.substring(root_size).indexOf("/") == -1);
}


据我所知,这是最干净的方法做到这一点,但它有我不喜欢的一个特征。如果我正在寻找根目录文件夹,那么我要求所有子文件夹中所有文件的名称只是为了遍历它们,并了解实际的根目录文件夹中只有一个对象。我已经考虑过将某个键的值与该文件夹的完整路径相关联,这将允许我使用可预测的键而不是前缀来请求对象,但是这样做的主要缺点是必须在代码以及因此(直接通过管理控制台)上传到S3存储桶中的资产将没有此密钥。任何人都有更好的主意吗?

#1 楼

ListObjectsRequest javadoc中,有一种称为withDelimiter(String delimiter)的方法。在.withDelimiter("/")调用之后添加.withPrefix(prefix),您将仅收到与前缀处于同一文件夹级别的对象列表(避免在通过有线发送列表后过滤返回的ObjectListing)。

关于代码的一些注释:

1,我将提取出ListObjectsRequest实例的局部变量:

final ListObjectsRequest listObjectRequest = new ListObjectsRequest().
    withBucketName(AWSConfiguration.BUCKET).
    withPrefix(prefix);
final ObjectListing objectListing = s3.listObjects(listObjectRequest);


读取。

2,root_size应该是rootSize。 (关于Java编码约定。)

3,我将使用String.contains代替indexOf。它更有意义,更易于阅读,因为您不必使用-1幻数。

4,在最后一个代码段中,我将为key创建一个局部变量:

for (final S3ObjectSummary objectSummary: objectListing.getObjectSummaries()) {
    final String key = objectSummary.getKey();
    if (S3Asset.isImmediateDescendant(prefix, key)) {
        final String relativePath = getRelativePath(prefix, key);
        System.out.println(relativePath);
    }
}


5,此外,我将length调用移到辅助方法内:

public String getRelativePath(final String parent, final String child) {
    if (!child.startsWith(parent)) {
        throw new IllegalArgumentException("Invalid child '" + child 
            + "' for parent '" + parent + "'");
    }
    // a String.replace() also would be fine here
    final int parentLen = parent.length();
    return child.substring(parentLen);
}

public boolean isImmediateDescendant(final String parent, final String child) {
    if (!child.startsWith(parent)) {
        // maybe we just should return false
        throw new IllegalArgumentException("Invalid child '" + child 
            + "' for parent '" + parent + "'");
    }
    final int parentLen = parent.length();
    final String childWithoutParent = child.substring(parentLen);
    if (childWithoutParent.contains("/")) {
        return false;
    }
    return true;
}


注意输入检查。 (有效Java,第二版,项目38:检查参数的有效性)

length的多次调用可能看起来很冗长和缓慢,但过早的优化不是一件好事(请参阅有效Java,第二版,项目55:明智地优化)。如果检查java.lang.String的来源,则会发现以下内容: />

评论


\ $ \ begingroup \ $
如果您可以期望这个社区提供这种意见,那么我将100%支持将其提升为完整的网站!感谢您提供完整的答案。
\ $ \ endgroup \ $
–詹森·斯珀斯凯(Jason Sperske)
2011年12月14日在22:11

\ $ \ begingroup \ $
第4点非常接近此代码在我的实际应用中的实际外观,对于第5点,在我的S3Asset类中,我有2种方法(一种需要2个字符串并对父对象进行长度检查,另外一种我在问题中引用的内容)。这是一个过早的优化,但是在每次迭代中始终保持相同的值时调用字符串长度函数会让我感到难过。现在,我将如何处理节省的所有多余纳秒?
\ $ \ endgroup \ $
–詹森·斯珀斯凯(Jason Sperske)
2011年12月14日在22:19

\ $ \ begingroup \ $
检查更新,没有多余的纳秒:-)
\ $ \ endgroup \ $
–palacsint
2011-12-14 22:32

\ $ \ begingroup \ $
似乎缺少的API调用是“ withDelimiter”,该页面在aws.amazon.com/releasenotes/213上有明确说明,如果您更新答案,我会将其标记为正确。
\ $ \ endgroup \ $
–詹森·斯珀斯凯(Jason Sperske)
2011-12-17在0:05

\ $ \ begingroup \ $
很好的答案,我认为它也可以通过提及在ObjectListing级别上得到改进,根据API docs this docs.aws.amazon.com/AmazonS3/latest/API/ RESTBucketGET.html,ObjectListing有一个方法objectListing.isTruncated()来指示是否还有更多结果,objectListing.getNextMarker()和objectListing.setNextMarker()用于控制分页。建议在迭代过程中使用do-while(),它包含“ for(final S3ObjectSummary objectSummary:objectListing.getObjectSummaries())”循环。
\ $ \ endgroup \ $
–le0diaz
18年11月1日在19:29

#2 楼

这是一个老问题,但是我刚刚发现,使用新版本的amazonaws“ 1.9.22”,您可以调用“ getCommonPrefixes”以获得所有可用前缀的列表。因此,对于“ prefix / aaa / 111”之类的结构,它将返回“ prefix / aaa”级别的项。


/>
更新:

和一个Java版本(原始版本使用Scala)

val listObjectRequest = new ListObjectsRequest().
  withBucketName("bucket").
  withPrefix("prefix").
  withDelimiter("/")

s3Client.listObjects(listObjectRequest).getCommonPrefixes


评论


\ $ \ begingroup \ $
欢迎来到CR!很好,但是此站点上的好答案与OP的代码直接相关。也许您可以编辑并添加一些上下文来说明此代码段适合的位置?
\ $ \ endgroup \ $
– Mathieu Guindon♦
2015年3月3日17:09

\ $ \ begingroup \ $
感谢@cyrillk!这个社区继续使我感到惊喜:)原始答案已经成为我许多项目的重要组成部分,我将重新访问视频,看看您的答案是否可以使意图更清晰:)
\ $ \ endgroup \ $
–詹森·斯珀斯凯(Jason Sperske)
15年3月3日在18:02

#3 楼

当我使用withDelimiter("/")时,我什么也没得到回报。事实证明,为了使用定界符,我还需要以/结尾我的前缀。换句话说,withPrefix("prefix/abc")将获得prefix/abc下的所有对象,但要使定界符起作用,请改用withPrefix("prefix/abc/")