BufferedReader 如何从 S3 读取文件?

Posted

技术标签:

【中文标题】BufferedReader 如何从 S3 读取文件?【英文标题】:How does BufferedReader read files from S3? 【发布时间】:2019-01-01 12:04:39 【问题描述】:

我在 AWS S3 中有一个非常大的文件(几 GB),我只需要文件中满足特定条件的少量行。我不想将整个文件加载到内存中,然后搜索并打印那几行 - 这样做的内存负载太高了。正确的方法是只在内存中加载那些需要的行。

根据 AWS 文档to read from file:

fullObject = s3Client.getObject(new GetObjectRequest(bucketName, key));
 displayTextInputStream(fullObject.getObjectContent());

private static void displayTextInputStream(InputStream input) throws IOException 
    // Read the text input stream one line at a time and display each line.
    BufferedReader reader = new BufferedReader(new InputStreamReader(input));
    String line = null;
    while ((line = reader.readLine()) != null) 
        System.out.println(line);
    
    System.out.println();

这里我们使用BufferedReader。我不清楚下面发生了什么。

我们是否在每次读取新行时都对 S3 进行网络调用,并且只将当前行保留在缓冲区中?还是将整个文件加载到内存中,然后由 BufferedReader 逐行读取?还是介于两者之间?

【问题讨论】:

来自link you posted:注意您的网络连接保持打开状态,直到您读取所有数据或关闭输入流。我们建议您尽快阅读直播内容。 我的问题更多的是——将整个文件加载到内存中,还是只加载我正在阅读的行,或者介于两者之间的缓冲区? 只需编写一个小示例应用程序并尝试使用上述代码从S3读取文件。如果它会立即将孔文件读入内存,您肯定会遇到OOM。 【参考方案1】:

仅对 AWS 基础设施进行一次 HTTP 调用,数据以小块的形式读入内存,其大小可能会有所不同,并且不受您的直接控制。

假设文件中的每一行都是相当小的大小,这已经非常节省内存。

假设您的“某些条件”是简单的字符串匹配,进一步优化(针对网络和计算资源)的一种方法是使用 S3 Select:https://aws.amazon.com/s3/features/#s3-select

【讨论】:

【参考方案2】:

您链接的文档中已经给出了您问题的答案之一:

在您读取所有数据或关闭输入流之前,您的网络连接保持打开状态。

BufferedReader 不知道它读取的数据来自哪里,因为您将另一个 Reader 传递给它。 BufferedReader 创建一个特定大小(例如 4096 个字符)的缓冲区,并在开始分发 read()read(char[] buf) 的调用数据之前通过从底层 Reader 读取来填充此缓冲区。

您传递给BufferedReaderReader - 顺便说一下 - 使用另一个缓冲区为自己执行从基于byte 的流到基于char 的读取器的转换。它的工作方式与BufferedReader 相同,因此内部缓冲区通过读取传递的InputStream 来填充,InputStream 是您的 S3 客户端返回的InputStream

如果您尝试从流中加载数据,此客户端中究竟会发生什么取决于实现。一种方法是保持打开一个网络连接,您可以根据需要从中读取,或者可以在读取大量数据后关闭网络连接,并在您尝试获取下一个数据时打开一个新连接。

上面引用的文档似乎说我们在这里遇到了前一种情况,所以:不,readLine 的调用不会导致单个网络调用。

并回答您的另一个问题:不,BufferedReaderInputStreamReader 和很可能由 S3 客户端返回的 InputStream 没有将整个文档加载到内存中。这将与首先使用流的整个目的相矛盾,并且 S3 客户端可以简单地返回 byte[][](以达到每个 byte-array 的 2^32 字节的限制)

编辑:最后一段有一个例外。如果整个千兆字节的大文档没有换行符,调用readLine 实际上会导致将整个数据读入内存(并且很可能会导致 OutOfMemoryError)。在回答您的问题时,我假设了一个“常规”文本文档。

【讨论】:

这句话对我来说似乎不清楚 - “不,readLine 的调用不会导致单个网络调用。”。您是否建议每次调用 readLine api 进行网络调用? @ArshanQureshi 该特定声明是上述内容的摘要。 readLine 的读取不会导致网络调用,因为在您和 S3 存储桶之间存在基于字节的缓冲区(至少根据文档看起来是这样),该缓冲区独立于实际数据填充,您只需要调用 readLine 时该缓冲区的一部分(直到出现换行符)。【参考方案3】:

如果您基本上不是在搜索特定的单词/单词,并且您知道字节范围,您还可以在 S3 中使用 Range 标头。当您处理几个 GB 大小的单个文件时,这应该特别有用。指定范围不仅有助于减少内存,而且速度更快,因为只读取文件的指定部分。

见Is there "S3 range read function" that allows to read assigned byte range from AWS-S3 file?

希望这会有所帮助。

Sreram

【讨论】:

【参考方案4】:

取决于文件中行的大小。 readLine() 将继续构建从流中获取数据的字符串,块大小为缓冲区大小,直到您遇到行终止字符。所以使用的内存将是你的行长度+缓冲区长度的顺序。

【讨论】:

以上是关于BufferedReader 如何从 S3 读取文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何从文件中逐字读取?

Java,为啥从 MappedByteBuffer 读取比从 BufferedReader 读取慢

如何将 s3 数据从一个 EMR 集群读取到另一个 EMR 集群?

通过火花数据框读取 S3 文件时,胶水书签不起作用

如何从 Spark 正确读取 S3 中的 .csv 文件? - 无法读取文件的页脚

Spark - 从 S3 读取分区数据 - 分区是如何发生的?