使用Java SDK从Google云端存储下载对象字节范围

时间:2019-06-17 18:27:26

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

我正尝试使用他们的Google Cloud StorageJava SDK下载一个字节范围。

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

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)直到到达所需的起始字节,然后从该点下载一个范围,但这似乎效率不高(尽管我不确切知道实现中发生了什么。)< / p>

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

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

4 个答案:

答案 0 :(得分: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显示了如何对其他依赖项管理器进行操作,并提供了一些其他信息。

答案 1 :(得分:1)

解决方案是仅调用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;
            }
        }

答案 2 :(得分:0)

事实证明,您无法在当前 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。

答案 3 :(得分:-1)

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

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]
  }