Скопируйте двоичные данные из URL в файл на Java без промежуточного копирования - PullRequest
4 голосов
/ 17 мая 2009

Я обновляю старый код, чтобы получить некоторые двоичные данные из URL-адреса, а не из базы данных (данные будут перемещены из базы данных и будут доступны по HTTP вместо этого). API базы данных, казалось, предоставлял данные в виде необработанного байтового массива напрямую, и рассматриваемый код записывал этот массив в файл, используя BufferedOutputStream.

Я совсем не знаком с Java, но небольшое прибегание к поиску подсказало мне следующий код:

URL u = new URL("my-url-string");
URLConnection uc = u.openConnection();
uc.connect();
InputStream in = uc.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
final int BUF_SIZE = 1 << 8;
byte[] buffer = new byte[BUF_SIZE];
int bytesRead = -1;
while((bytesRead = in.read(buffer)) > -1) {
    out.write(buffer, 0, bytesRead);
}
in.close();
fileBytes = out.toByteArray();

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

Я предполагаю, что это потому, что в этой версии кода одновременно хранится несколько копий данных, тогда как в исходном коде этого не было.

Есть ли простой способ получить двоичные данные из URL-адреса и сохранить их в файле, не неся при этом затрат на несколько копий в памяти?

Ответы [ 4 ]

12 голосов
/ 17 мая 2009

Вместо записи данных в байтовый массив и последующего вывода их в файл, вы можете напрямую записать их в файл, заменив следующее:

ByteArrayOutputStream out = new ByteArrayOutputStream();

С:

FileOutputStream out = new FileOutputStream("filename");

Если вы это сделаете, вам не нужно звонить out.toByteArray() в конце. Просто убедитесь, что вы закрыли объект FileOutputStream, когда закончите, например:

out.close();

Подробнее см. Документацию FileOutputStream .

1 голос
/ 17 мая 2009

Если вам нужен Content-Length и ваш веб-сервер соответствует стандарту, то он должен предоставить вам заголовок «Content-Length».

URLConnection # getContentLength () должен предоставить вам эту информацию заранее, чтобы вы могли создать свой файл. (Имейте в виду, что если ваш HTTP-сервер неправильно настроен или находится под контролем злой сущности, этот заголовок может не соответствовать количеству полученных байтов. В таком случае, почему бы вам не выполнить потоковую передачу сначала во временный файл, а затем скопировать этот файл?)

В дополнение к этому: ByteArrayInputStream - ужасный распределитель памяти. Он всегда удваивает размер буфера, поэтому, если вы читаете файл размером 32 МБ + 1 байт, вы получаете буфер размером 64 МБ. Возможно, было бы лучше реализовать собственный, более умный поток байтов-массивов, как этот:

http://source.pentaho.org/pentaho-reporting/engines/classic/trunk/core/source/org/pentaho/reporting/engine/classic/core/util/MemoryByteArrayOutputStream.java

1 голос
/ 17 мая 2009

Я не знаю, что вы имеете в виду под "большими" данными, но попробуйте использовать параметр JVM

java -Xmx 256m ...

, который устанавливает максимальный размер кучи 256 МБ (или любое другое значение).

0 голосов
/ 17 мая 2009

Создание подкласса ByteArrayOutputStream дает вам доступ к буферу и количеству байтов в нем.

Но, конечно, если все, что вы хотите сделать, это сохранить данные в файл, вам лучше использовать FileOutputStream.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...