Потоковая передача файлов в Java

В настоящее время я разрабатываю приложение для трехмерной графики с использованием JOGL (привязка Java OpenGL). Короче говоря, у меня есть огромный двоичный файл ландшафта. Из-за его размера мне приходится транслировать фрагменты ландшафта во время выполнения. Таким образом, мы явно видим проблему произвольного доступа. Я уже закончил первую (и грязную :)) реализацию (возможно, многопоточную), где я использую дурацкий подход... Вот его инициализация:

dataInputStream = new DataInputStream(new BufferedInputStream(fileInputStream,4 * 1024);
dataInputStream.mark(dataInputStream.available());

И когда мне нужно прочитать (поток) специальный чанк (я уже знаю его "смещение" в файле), я делаю следующее (позор мне :)):

dataInputStream.reset();
dataInputStream.skipBytes(offset);
dataInputStream.read(whatever I need...);

Так как у меня было мало опыта, это было первое, о чем я мог подумать :) Итак, пока что я прочитал 3 полезные и довольно интересные статьи (предлагаю вам их прочитать, возможно, если вам интересна эта тема)

  1. байтовые буферы и некучевая память – Г-н Грегори, кажется, хорошо разбирается в Java NIO.

  2. Совет по Java: как быстро читать файлы [http://nadeausoftware.com/articles/2008/02/java_tip_how_read_files_quickly] — интересный тест.

  3. Статьи: Настройка производительности ввода-вывода Java [http://java.sun.com/developer/technicalArticles/Programming/PerfTuning/] — Простые рекомендации Sun, но, пожалуйста, прокрутите вниз и посмотрите " раздел «Произвольный доступ»; они показывают простую реализацию RandomAccessFile (RAF) с улучшением самобуферизации.

Г-н Грегори предоставляет несколько файлов *.java в конце своей статьи. Одним из них является бенчмаркинг между FileChannel + ByteBuffer + Mapping (FBM) и RAF. Он говорит, что заметил 4-кратное ускорение при использовании FBM по сравнению с RAF. Я запустил этот тест в следующих условиях:

  1. Смещение (например, место доступа) генерируется случайным образом (в области видимости файла, например, 0 - file.length());
  2. Размер файла 220 МБ;
  3. 1 000 000 обращений (75% чтения и 25% записи)

Результаты были ошеломляющими:

~ 28 сек для РАФ! ~ 0,2 сек. для FBM!

Однако его реализация RAF в этом бенчмарке не имеет самобуферизации (о ней рассказывается в 3-й статье), поэтому я предполагаю, что вызов метода "RandomAccessFile.seek" так сильно снижает производительность.

Хорошо, теперь, после всего того, что я узнал, есть 1 вопрос и 1 дилемма :)

Вопрос: когда мы сопоставляем файл с помощью «FileChannel.map», копирует ли Java все содержимое файла в MappedByteBuffer? Или просто подражает ему? Если он копирует, то использование FBM не подходит для моей ситуации, не так ли?

Дилемма: зависит от ваших ответов на вопрос...

  1. Если сопоставление копирует файл, то, похоже, у меня есть только 2 возможных решения: RAF + самобуферизация (из 3-й статьи) или использовать позицию в FileChannel< /strong> (не с отображением)... Какой из них будет лучше?

  2. Если сопоставление не копирует файл, то у меня есть 3 варианта: два предыдущих и сам FBM.

Изменить. Вот еще один вопрос. Некоторые из вас здесь говорят, что сопоставление не копирует файл в MappedByteBuffer. Хорошо, тогда почему я не могу сопоставить файл размером 1 ГБ, я получаю сообщение "не удалось сопоставить"...

П. С. Хотелось бы получить развернутый ответ с советами, т.к. не могу найти в инете целостной информации по этой теме.

Спасибо :)


person Alexander Shukaev    schedule 18.01.2011    source источник


Ответы (3)


Нет, данные не буферизуются. MappedByteBuffer ссылается на данные, используя указатель. Другими словами, данные не копируются, они просто сопоставляются с физической памятью. См. документацию по API, если вы еще не уже.

Файл с отображением памяти — это сегмент виртуальной памяти, которому назначена прямая побайтовая корреляция с некоторой частью файла или файлового ресурса. Этот ресурс обычно представляет собой файл, физически присутствующий на диске, но также может быть устройством, объектом общей памяти или другим ресурсом, на который операционная система может ссылаться через файловый дескриптор. Когда эта корреляция между файлом и областью памяти присутствует, приложения могут обрабатывать отображенную часть, как если бы она была основной памятью.

Источник: Википедия

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

person someguy    schedule 18.01.2011
comment
Если вы говорите, что MappedByteBuffer — это указатель на HD, то как он достигает таких хороших результатов в бенчмарках? Единственная возможная функция ускорения в IO, которую я лично знаю, - это ДОСТУП К ДИСКУ КАК МИНИМАЛЬНО, и единственное решение здесь - буферизация. Опять же, если вы достаточно грамотны в этом вопросе, пожалуйста, будьте более подробными. - person Alexander Shukaev; 19.01.2011
comment
@ Haroogan Я цитирую из этой статьи: разница почти полностью связана с переключением контекста ядра. - person someguy; 19.01.2011
comment
Вы должно быть шутите, отсылая меня к javadoc, не так ли? Потому что я не прошу никакой конкретной информации. У меня до сих пор нет прямых ответов или правильных идей и комментариев по поводу возможных решений. - person Alexander Shukaev; 19.01.2011
comment
@ Haroogan Прежде всего, открой глаза. Моего ответа достаточно, учитывая, что все, что вы хотели знать, это копирует ли отображение файл. В самой первой строке javadoc говорится, что данные отображаются в памяти. Вы должны были спросить меня, что это значит, вместо того, чтобы называть мой ответ шуткой. Во всяком случае, остальная часть моего ответа разъясняет, что это такое. Кроме того, я дал дополнительное предложение по оптимизации. - person someguy; 19.01.2011
comment
Похоже, вы просто не понимаете. Попробую еще раз с примитивным вопросом. Просто скажи да или нет. Если я сопоставляю файл размером 1 ГБ, тогда емкость MappedByteBuffer = 1 ГБ, так действительно ли MappedByteBuffer занимает 1 ГБ ОЗУ или просто эмулирует его? - person Alexander Shukaev; 19.01.2011
comment
Мой собственный опыт подсказывает мне, что он пытается занять 1 ГБ оперативной памяти, поскольку я не могу отобразить файл размером 1 ГБ с: out of memory: map failed exception! Если я ошибаюсь, поправьте меня, просто перестаньте ссылаться на бесполезные документы, все эти слова в javadocs не подкреплены достаточной информацией. Более того, javadocs — это всего лишь краткая справка, чтобы рассказать вам о правильном использовании класса в Java, но это не руководство, которое объясняет вам, что происходит за кулисами! Слово MAPPING в javadocs ничего не говорит мне о его реальном механизме. Я надеюсь, что вы получили это сейчас. - person Alexander Shukaev; 19.01.2011
comment
@ Haroogan Я понимаю, просто я хотел убедиться, что вы поняли концепции. На ваш вопрос сложно ответить да или нет, поэтому я дам вам ссылку на эту статью: en .wikipedia.org/wiki/Memory-mapped_file. Это подробно, а также объясняет, почему вы получили исключение нехватки памяти. - person someguy; 19.01.2011

Для файла размером 220 МБ я бы отобразил всю память в виртуальную память. Причина, по которой FBM работает так быстро, заключается в том, что он фактически не считывает данные в память, а просто делает их доступными.

Примечание: при запуске теста нужно сравнивать подобное на подобное, т.е. когда файл находится в кеше ОС, он будет намного быстрее, независимо от того, как вы это сделаете. Вам нужно повторить тест несколько раз, чтобы получить воспроизводимый результат.

person Peter Lawrey    schedule 18.01.2011
comment
Что вы имеете в виду под доступным? Может быть только 2 варианта: файл полностью копируется в MappedByteBuffer (максимальный размер 2 ГБ для 32-битных систем) или MappedByteBuffer просто эмулирует этот файл, используя фоновую буферизацию, прогнозирующую логику или что-то еще... Так как я пытался отобразить 1 ГБ файл, и это не удалось, я должен сделать вывод, что его сопоставление, кажется, копирует весь файл в MappedByteBuffer... или я все еще ошибаюсь? Пожалуйста, будьте более подробными в своих ответах. - person Alexander Shukaev; 18.01.2011
comment
При сопоставлении ОС сопоставляет файл с виртуальной памятью. Страницы (обычно 4 КБ) файла переносятся в память, когда вы их читаете/записываете, и медленно сбрасываются на диск. (Или когда вы принудительно сбрасываете) Невозможно прочитать файл размером 220 МБ в память за 0,2 секунды. Я не уверен, почему не удалось отобразить файл размером 1 ГБ, если вы не используете 32-разрядную JVM. - person Peter Lawrey; 19.01.2011
comment
Да, я использую 32-битную JVM, поэтому я не понимаю, почему не удается сопоставление файлов размером 1 ГБ ... есть идеи? В настоящее время меня интересует только чтение, поэтому мне не нужен сброс и т. д. Вы только что сказали, что ОС загружает страницы размером 4 КБ в виртуальную память, но вы видите, что я уже говорил об этом, т.е. е. MappedByteBuffer просто эмулирует этот файл, используя логику медленной фоновой буферизации, которую я не могу контролировать. Верно? - person Alexander Shukaev; 19.01.2011
comment
32-разрядная JVM в 32-разрядной ОС может использовать только от 1,2 до 1,5 ГБ виртуальной памяти. 32-разрядная JVM в 64-разрядной ОС может получить доступ к большему количеству. В Solaris он может получить доступ к 3,5 ГБ. Самая большая 64-битная JVM, которую я когда-либо видел, имеет доступ к 768 ГБ. Ни один из них не соответствует теоретическому пределу, но вы можете видеть, что 64-битная JVM — правильный инструмент для работы. Вы можете контролировать, где вы получаете доступ к файлу и в каком порядке, объем файла, который вы можете хранить в памяти, ограничен вашим оборудованием, скорость чтения файла также ограничена вашим оборудованием. - person Peter Lawrey; 19.01.2011
comment
Java использует базовое сопоставление ОС. Там написано, как это делается, потому что это зависит от используемой вами ОС. Если вы хотите знать, как ваша ОС делает это, вам нужно прочитать документацию для вашей ОС. - person Peter Lawrey; 19.01.2011

Вы замечали, что если вы запустите программу, затем закроете ее, а затем запустите снова, она запустится намного быстрее, чем во второй раз? Это происходит потому, что ОС кэширует части файлов, к которым обращались при первом запуске, и ей не нужно обращаться к диску для них. Отображение памяти в файле, по сути, позволяет программе получить доступ к этим буферам, тем самым сводя к минимуму количество копий, сделанных при его чтении. Обратите внимание, что отображение файла в память не приводит к его считыванию в память целиком; биты и фрагменты, которые вы читаете, считываются с диска по запросу. Если ОС определяет нехватку памяти, она может решить освободить некоторые части отображаемого файла из памяти и оставить их на диске.

Изменить: вам нужен FileInputStream.getChannel().map(), затем адаптируйте его к InputStream, затем подключите к DataInputStream.

person Tassos Bassoukos    schedule 19.01.2011