Загрузка уже сегментированных файлов с помощью NIO — Java 11

#java #file #httpclient #nio #segment

#java #файл #httpclient #nio #сегмент

Вопрос:

Я хочу загрузить огромный файл, который уже разделен на сотни сегментов, в один файл.

Итак, у меня есть все URL-адреса всех сегментов в массиве, я делю его вручную на 4 раздела, запускаю 4 потока одновременно, а затем объединяю их в конце в один огромный файл. Часть, в которой, я думаю, я мог бы улучшить свой код, находится в части, когда я загружаю разделы. Цель состоит в том, чтобы открыть файлы один раз и добавить в них все сегменты. Я открыт и для совершенно других решений.

 static void downloadFileSection(int begin, int end, int marker, String[] segmentURLs, String segID) {
        try(var file = FileChannel.open(Path.of(segID   "_"   marker   ".seg"), WRITE, CREATE)){
            var byteArrayHandler = BodyHandlers.ofByteArray();

            IntStream.range(begin, end)
                      .forEach(index -> {

                          System.out.println("Downloading segment: "   index);

                          try {
                              //This is the part that I think is wrong. I shouldn't be creating a ByteBuffer every segment download.
                              //Main.sendNormalRequest is a method that sends a request using java 11-s HttpClient.send method.
                              file.write(ByteBuffer.wrap(Main.sendNormalRequest(segmentURLs[index], byteArrayHandler).body()));
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                      });
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
 

Комментарии:

1. Неясно, в чем заключается ваш вопрос. Вы хотите минимизировать использование памяти или хотите распараллелить процесс?

2. Я хочу знать, есть ли лучший способ загрузить много небольших файлов в один, в основном добавив все байты. Говоря об использовании памяти, я заметил, что анализатор памяти eclipse упоминает, что HttpClient хранит ссылки на байт-буферы.

3. «Самым быстрым» методом было бы выполнить несколько параллельных вызовов небольших файлов (например, поставить их в очередь через ThreadPoolExecutor ), а затем записать части в целевой файл через один RandomAccessFile , скопировав входной поток из HTTP-соединения в RAF-OutputStream. Это также должно иметь наименьший объем памяти, потому что вы не загружаете файлы полностью в ОЗУ.

4. HttpClient использует разные стратегии в зависимости от того, проходит ли запрос через HTTPS или обычный HTTP. В общем случае он может пересылать фрагменты буфера вызывающему коду, чтобы оптимизировать использование памяти, что может выглядеть так, как будто он содержит ссылку на байтовый буфер, в то время как он может просто ждать, пока следующие байты заполнят следующий фрагмент. С HTTPS тогда есть временные байтовые буферы, которые используются между сокетом и механизмом SSL: они обычно перерабатываются, и HttpClient сохранит ссылку на них, поскольку они никогда не переходят к вызывающему, но их должно быть всего несколько.

Ответ №1:

Вы можете использовать BodyHandlers.ofPublisher() для получения Publisher<List<ByteBuffer>> . Затем, если статус 200 OK, предоставьте и подпишите свою собственную Flow.Subscriber<List<ByteBuffer>> реализацию и запишите байты в файл по мере их поступления в onNext(...) метод. Если эти байты представляют текст, вам, возможно, придется позаботиться о кодировке / перекодировании символов, особенно если вам нужно записать файл в кодировке, отличной от той, что отправляется сервером (обычно это будет UTF-8).