如何使用Java逐行从Google云端存储中读取巨大的CSV文件?

时间:2019-03-18 15:55:28

标签: google-cloud-platform google-cloud-storage nio

我是Google Cloud Platform的新手。我正在尝试逐行读取Google Cloud Storage(通过服务帐户密钥访问的非公共存储桶)中存在的CSV文件,该文件大约为1GB。

我找不到任何选项来逐行读取Google Cloud Storage(GCS)中存在的文件。我只看到按块大小/字节大小读取选项。由于我正在尝试读取CSV,因此我不希望使用按块大小读取,因为它可能会在读取时拆分记录。

到目前为止已尝试的解决方案: 尝试将GCS中存在的CSV文件中的内容复制到临时本地文件中,并使用以下代码读取临时文件。下面的代码按预期工作,但我不想将大文件复制到本地实例。相反,我想从GCS逐行读取。

    StorageOptions options = 
    StorageOptions.newBuilder().setProjectId(GCP_PROJECT_ID)
            .setCredentials(gcsConfig.getCredentials()).build();
    Storage storage = options.getService();
    Blob blob = storage.get(BUCKET_NAME, FILE_NAME);
    ReadChannel readChannel = blob.reader();
    FileOutputStream fileOuputStream = new FileOutputStream(TEMP_FILE_NAME);
    fileOuputStream.getChannel().transferFrom(readChannel, 0, Long.MAX_VALUE);
    fileOuputStream.close();

请提出方法。

3 个答案:

答案 0 :(得分:2)

最简单的方法之一可能是使用google-cloud-nio软件包,它是您已经在使用的google-cloud-java库的一部分:https://github.com/googleapis/google-cloud-java/tree/v0.30.0/google-cloud-contrib/google-cloud-nio

它将Google Cloud Storage集成到Java的NIO中,因此一旦启动并运行,就可以像引用文件或URI一样引用GCS资源。例如:

Path path = Paths.get(URI.create("gs://bucket/lolcat.csv"));
try (Stream<String> lines = Files.lines(path)) {
   lines.forEach(s -> System.out.println(s));
} catch (IOException ex) {
   // do something or re-throw...
}

答案 1 :(得分:2)

布兰登·雅伯(Brandon Yarbrough)是对的,在回答中补充:

如果您使用gcloud登录并使用自己的凭据登录,那么Brandon的代码将起作用:google-cloud-nio将使用您的登录信息来访问文件(即使它们不是公开的,也可以使用)。

如果您希望通过软件完成全部操作,则可以使用以下代码从本地文件读取凭据,然后从Google Cloud访问文件:

    String myCredentials = "/path/to/my/key.json";
    CloudStorageFileSystem fs =
        CloudStorageFileSystem.forBucket(
            "bucket",
            CloudStorageConfiguration.DEFAULT,
            StorageOptions.newBuilder()
                .setCredentials(ServiceAccountCredentials.fromStream(
                    new FileInputStream(myCredentials)))
                .build());
    Path path = fs.getPath("/lolcat.csv");
    List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);

edit:您不想一次读取所有行,因此不要使用realAllLines,但是一旦拥有Path,就可以使用上面讨论的任何其他技术来读取仅需要文件的一部分:您可以一次读取一行或获取一个Channel对象。

答案 2 :(得分:1)

由于我正在执行批处理,因此我在ItemReader的init()方法中使用以下代码,该代码以@PostConstruct进行批注。在我的ItemReader的read()中,我正在构建一个列表。列表的大小与块大小相同。这样,我可以基于我的chunkSize读取行,而不是一次读取所有行。

StorageOptions options = 
StorageOptions.newBuilder().setProjectId(GCP_PROJECT_ID)
        .setCredentials(gcsConfig.getCredentials()).build();
Storage storage = options.getService();
Blob blob = storage.get(BUCKET_NAME, FILE_NAME);
ReadChannel readChannel = blob.reader();
BufferedReader br = new BufferedReader(Channels.newReader(readChannel, "UTF-8"));