Как BufferedReader читает файлы с S3?

У меня есть очень большой файл (несколько ГБ) в AWS S3, и мне нужно только небольшое количество строк в файле, которые удовлетворяют определенному условию. Я не хочу загружать весь файл в память, а затем искать и печатать эти несколько строк - нагрузка на память для этого будет слишком высокой. Правильным способом было бы загружать в память только те строки, которые необходимы.

Согласно документации AWS для чтения из файла:

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 построчно? Или это где-то посередине?


person sbhatla    schedule 24.07.2018    source источник
comment
Из размещенной вами ссылки: Примечание Ваше сетевое соединение остается открытым до тех пор, пока вы не прочитаете все данные или не закроете входной поток. Рекомендуем как можно быстрее ознакомиться с содержанием стрима.   -  person Johannes Kuhn    schedule 24.07.2018
comment
Мой вопрос больше похож на то, будет ли весь файл загружен в память или только строки, которые я читаю, или буфер, который находится где-то посередине?   -  person sbhatla    schedule 26.07.2018
comment
Просто напишите небольшой пример приложения и попробуйте прочитать файл с S3, используя приведенный выше код. Если он будет сразу читать файл дыры в память, вы наверняка столкнетесь с OOM.   -  person dpr    schedule 03.08.2018


Ответы (4)


Один из ответов на ваш вопрос уже дан в документации, которую вы связали:

Ваше сетевое соединение остается открытым до тех пор, пока вы не прочитаете все данные или не закроете входной поток.

BufferedReader не знает, откуда берутся данные, которые он читает, потому что вы передаете ему другой Reader. BufferedReader создает буфер определенного размера (например, 4096 символов) и заполняет этот буфер чтением из базового Reader перед тем, как начать выдачу данных вызовов read() или read(char[] buf).

Между прочим, Reader, который вы передаете BufferedReader, использует другой буфер для преобразования потока на основе byte в считыватель на основе char. Это работает так же, как и с BufferedReader, поэтому внутренний буфер заполняется чтением из переданного InputStream, который является InputStream, возвращенным вашим S3-клиентом.

Что именно происходит в этом клиенте, если вы пытаетесь загрузить данные из потока, зависит от реализации. Одним из способов было бы оставить открытым одно сетевое соединение, и вы можете читать из него, как хотите, или сетевое соединение может быть закрыто после того, как блок данных был прочитан, и новый открывается, когда вы пытаетесь получить следующий.

Документация, процитированная выше, кажется, говорит, что у нас здесь прежняя ситуация, поэтому: Нет, вызовы readLine не ведут к одиночным сетевым вызовам.

И чтобы ответить на ваш другой вопрос: нет, BufferedReader, InputStreamReader и, скорее всего, InputStream, возвращаемые S3-клиентом, не загружают весь документ в память. Это противоречило бы всей цели использования потоков в первую очередь, и клиент S3 мог бы просто вместо этого вернуть byte[][] (чтобы обойти ограничение в 2 ^ 32 байта на byte-массив)

Изменить: есть исключение последнего абзаца. Если весь гигабайтный большой документ не имеет разрывов строк, вызов readLine фактически приведет к чтению всех данных в память (и, скорее всего, к ошибке OutOfMemoryError). Отвечая на ваш вопрос, я предположил «обычный» текстовый документ.

person Lothar    schedule 28.07.2018
comment
Это утверждение кажется мне неясным. Нет, вызовы readLine не ведут к одиночным сетевым вызовам. Вы предлагаете, чтобы сетевой вызов выполнялся для каждого вызова readLine api? - person Arshan Qureshi; 02.09.2019
comment
@ArshanQureshi Это конкретное утверждение является кратким изложением вышесказанного. Чтение readLine не приводит к сетевому вызову, потому что между вами и S3-бакетом есть байтовые буферы (по крайней мере, судя по документации), которые заполняются независимо от реальных данных, а вы берете только часть из этого буфера (до возникновения разрыва строки) при вызове readLine. - person Lothar; 02.09.2019

Если вы в основном не ищете конкретное слово/слова и вам известен диапазон байтов, вы также можете использовать заголовок Range в S3. Это должно быть особенно полезно, когда вы работаете с одним файлом размером в несколько ГБ. Указание диапазона не только помогает уменьшить объем памяти, но и работает быстрее, так как читается только указанная часть файла.

См. есть ли функция чтения диапазона S3, которая позволяет читать назначенный диапазон байтов из файла AWS-S3?

Надеюсь это поможет.

Серам

person Sreram    schedule 29.07.2018

Зависит от размера строк в вашем файле. readLine() будет продолжать строить строку, извлекающую данные из потока блоками размером с размер вашего буфера, пока вы не нажмете символ завершения строки. Таким образом, используемая память будет порядка длины вашей строки + длины буфера.

person Jesse    schedule 28.07.2018

К инфраструктуре AWS выполняется только один HTTP-запрос, а данные считываются в память небольшими блоками, размер которых может варьироваться и не находится под вашим непосредственным контролем.

Это уже очень эффективно использует память, если предположить, что каждая строка в файле имеет достаточно небольшой размер.

Один из способов дальнейшей оптимизации (для сетевых и вычислительных ресурсов), предполагая, что ваше «определенное условие» представляет собой простое совпадение строк, — использовать S3 Select: https://aws.amazon.com/s3/features/#s3-select

person Alex R    schedule 02.08.2018