【问题标题】:Downloading an object byte range from Google Cloud Storage using Java SDK使用 Java SDK 从 Google Cloud Storage 下载对象字节范围
【发布时间】:2021-08-05 16:12:52
【问题描述】:

我正在尝试使用他们的Java SDKGoogle Cloud Storage 下载一个字节范围。

我可以像这样下载整个文件。

Storage mStorage; // initialized and working

Blob blob = mStorage.get(pBucketName, pSource);

try (ReadChannel reader = mStorage.reader(blob.getBlobId())) {
    // read bytes from read channel
}

如果我愿意,我可以ReadChannel#seek(long) 直到达到所需的起始字节,然后从该点下载一个范围,但这似乎效率低下(尽管我不知道具体在实现中发生了什么。)

理想情况下,我想将Range: bytes=start-end 标头指定为shown in the Google Cloud Storage REST API,但我不知道如何在Java 中设置标头。

如何在 Java SDK Storage 的 get 调用中指定字节范围,或者指定标头,以便高效下载所需的字节范围?

【问题讨论】:

  • 使用 NIO 接口你可以在你的文件中获取一个 SeekableChannel,然后调用 position 方法来获取你想要读取的位置。这是他们的 Java SDK 的一部分。
  • 由于这里没有人可以接受的答案,我添加一个问题:github.com/googleapis/google-cloud-java/issues/7625
  • 更新:我跟踪 SDK 代码并发布我在答案中找到的内容。

标签: java google-cloud-storage google-cloud-sdk


【解决方案1】:

我了解您正在尝试使用 Google Cloud 的特定接口,但您可能不知道另一种方式:Google Cloud 可以插入 Java 的 NIO 接口。您可以将Path 获取到存储桶上的文件并正常使用它:将SeekableChannel 获取到您的文件中,然后调用position(long) 方法来获取您要读取的位置。

这是我测试的示例代码:

import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

(...)

    public static void readFromMiddle(String path, long offset, ByteBuffer buf) throws IOException {
        // Convert from a string to a path, using available NIO providers
        // so paths like gs://bucket/file are recognized (provided you included the google-cloud-nio
        // dependency).
        Path p = Paths.get(URI.create(path));
        SeekableByteChannel chan = Files.newByteChannel(p, StandardOpenOption.READ);
        chan.position(offset);
        chan.read(buf);
    }


您会发现这是普通的 Java 代码,除了我们制作 Path 的不同寻常的方式之外,没有什么特别之处。这就是蔚来的魅力所在。要使此代码能够理解“gs://” URL,您需要添加 google-cloud-nio 依赖项。对于 Maven,它是这样的:

    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-nio</artifactId>
      <version>0.107.0-alpha</version>
    </dependency>

仅此而已。

The documentation page 展示了如何为其他依赖管理器执行此操作并提供了一些附加信息。

【讨论】:

    【解决方案2】:

    解决方案是调用ReadChannel#seek(offset)

    例如:

            try (ReadChannel reader = blob.reader()) {
                // offset and readLength is obtained from HTTP Range Header
                reader.seek(offset);
                ByteBuffer bytes = ByteBuffer.allocate(1 * 1024 * 1024);
                int len = 0;
                while ((len = reader.read(bytes)) > 0 && readLength > 0) {
                    outputStream.write(bytes.array(), 0, (int) Math.min(len, readLength));
                    bytes.clear();
                    readLength -= len;
                }
            }
    

    【讨论】:

      【解决方案3】:

      事实证明,您无法在当前 Java SDK 实现中对范围标头进行细粒度控制。

      ReadChannel#seek(offset)可以设置起始位置,但不能设置结束位置。

      在 Java SDK 内部,它会将范围标头设置为Rrange:$offset-$(offset+bufferSize)

      https://github.com/googleapis/java-storage/blob/master/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java#L732

      一种解决方法是自己包装ReadChannel,并在它到达预期的结束位置时关闭连接。代码片段:

      
      class GSBlobInputStream extends InputStream {
      
          private final ReadChannel channel;
          private long start = 0;
          private long end = -1;
          private InputStream delegate;
      
          public GSBlobInputStream(ReadChannel channel) {
              this.channel = channel;
          }
      
          public GSBlobInputStream(ReadChannel channel, long start, long end) {
              this.channel = channel;
              this.start = start;
              this.end = end;
          }
      
          @Override
          public int read() throws IOException {
              init();
              return delegate.read();
          }
      
          @Override
          public int read(byte[] b) throws IOException {
              init();
              return delegate.read(b);
          }
      
          @Override
          public int read(byte[] b, int off, int len) throws IOException {
              init();
              return delegate.read(b, off, len);
          }
      
          /**
           * Closes this input stream and releases any system resources associated with the stream.
           *
           * @throws IOException if an I/O error occurs.
           */
          @Override
          public void close() throws IOException {
              if (delegate != null) {
                  delegate.close();
              }
          }
      
          private void init() throws IOException {
              if (delegate != null) {
                  return;
              }
      
              channel.seek(start);
      
              delegate = Channels.newInputStream(channel);
              if (end != -1) {
                  delegate = ByteStreams.limit(delegate, end - start + 1);
              }
          }
      
      
      }
      

      请注意,在这种方法中,在最坏的情况下,您将有 15MiB - 1 字节的开销,因为默认缓冲区大小为 15MiB。

      【讨论】:

        【解决方案4】:

        这是一个读取对象内容的好例子。 在这个链接中有更多的代码解决方案:

        Stream file from Google Cloud Storage

          /**
           * Example of reading a blob's content through a reader.
           */
          // [TARGET reader(String, String, BlobSourceOption...)]
          // [VARIABLE "my_unique_bucket"]
          // [VARIABLE "my_blob_name"]
          public void readerFromStrings(String bucketName, String blobName) throws IOException {
            // [START readerFromStrings]
            try (ReadChannel reader = storage.reader(bucketName, blobName)) {
              ByteBuffer bytes = ByteBuffer.allocate(64 * 1024);
              while (reader.read(bytes) > 0) {
                bytes.flip();
                // do something with bytes
                bytes.clear();
              }
            }
            // [END readerFromStrings]
          }
        

        【讨论】:

        • 是的,我知道如何下载整个对象(如我的问题所示。)我不知道的是如何请求对象的特定部分 通过在请求中添加一个带有字节范围的请求头。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-19
        • 2016-01-15
        • 2017-11-08
        • 1970-01-01
        • 2013-05-24
        相关资源
        最近更新 更多