Как работает RandomAccessFile.seek ()?

Согласно API, это факты:

  • Попросту говоря, метод seek(long bytePosition) перемещает указатель в позицию, указанную параметром bytePosition.
  • Когда bytePosition больше, чем длина файла, длина файла не изменяется, если байт не записан на (новом) конце.
  • Если данные присутствуют в пропущенной длине, такие данные остаются нетронутыми.

Однако меня интересует ситуация: когда есть файл без данных (0 байт), и я выполняю следующий код:

file.seek(100000-1);
file.write(0);

Все 100 000 байт заполняются 0 практически мгновенно. Я могу синхронизировать более 200 ГБ, скажем, за 10 мс.

Но когда я пытаюсь записать 100000 байт другими методами, такими как BufferedOutputStream, тот же процесс занимает почти бесконечно больше времени.

В чем причина такой разницы во времени? Есть ли более эффективный способ создать файл размером n байт и заполнить его 0?

РЕДАКТИРОВАТЬ: Если данные на самом деле не записываются, как файл заполняется данными? Пример этого кода:

RandomAccessFile out=new RandomAccessFile("D:/out","rw");
out.seek(100000-1);
out.write(0);
out.close();

Это результат:

Вывод

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


person SirVirgin    schedule 23.02.2017    source источник
comment
Я предполагаю, что размер файла указан, но фактические блоки не записываются на диск. Как долго длится флеш / закрытие? (см. здесь stackoverflow.com/a/257849/540873)   -  person Thomas Jungblut    schedule 23.02.2017
comment
Я предположил то же самое, но я открыл файл и проверил его. когда я не записывал последний байт, он был пустым, а размер результирующего файла составлял 0 байт. когда я записывал последний байт, каждый байт до последнего был заполнен нулем, а размер файла был как входной. Весь процесс с включенной операцией close () - это время, указанное в вопросе (вот почему я удивлен!)   -  person SirVirgin    schedule 23.02.2017
comment
что вы не поняли, когда читали исходный код этого метода? Читал ли источник, прежде чем попросить кого-нибудь его прочитать, и вы работаете на себя, не так ли?   -  person    schedule 23.02.2017
comment
@JarrodRoberson - это собственный метод.   -  person Gray    schedule 23.02.2017
comment
То, как ОС справляется с этим, очень зависит от того, какая ОС @RangaRajan. А что касается того, как записываются данные, это объясняется в ответах ниже. Все нули обрабатываются по-разному, если файл разреженный.   -  person Gray    schedule 23.02.2017
comment
@JarrodRoberson Если бы я указал мне на исходный код, я был бы очень признателен, сэр.   -  person SirVirgin    schedule 23.02.2017
comment
@Gray, спасибо, я слежу за разреженными файлами (из опубликованных ответов)   -  person SirVirgin    schedule 23.02.2017


Ответы (2)


Когда вы записываете 100 000 байтов в BufferedOutputStream, ваша программа явно обращается к каждому байту файла и записывает ноль.

Когда вы используете RandomAccessFile.seek() в локальном файле, вы косвенно используете системный вызов C fseek(). Как это будет сделано, зависит от операционной системы.

В большинстве современных операционных систем поддерживаются разреженные файлы. Это означает, что если вы запрашиваете пустой файл размером 100 000 байт, 100 000 байт дискового пространства фактически не используются. Когда вы записываете в байт 100 001, ОС по-прежнему не использует 100 001 байт на диске. Он выделяет небольшой объем места для блока, содержащего «настоящие» данные, и отдельно отслеживает пустое пространство.

Когда вы читаете разреженный файл, например, fseek()ing до байта 50 000, а затем читаете, ОС может сказать: "Хорошо, я не выделил дисковое пространство для байта 50 000, потому что я заметил, что байты от 0 до 100 000 пусты. Поэтому я могу вернуть 0 для этого байта. ". Это невидимо для вызывающего абонента.

Это имеет двойную цель - экономию дискового пространства и повышение скорости. Вы заметили улучшение скорости.

В более общем смысле fseek() переходит непосредственно к позиции в файле, поэтому это O (1), а не O (n). Если вы сравните файл с массивом, это будет похоже на x = arr[n] вместо for(i = 0; i<=n; i++) { x = arr[i]; }

Этого описания и того, что в Википедии, вероятно, достаточно, чтобы понять, почему при поиске байтов 100 000, а затем записи происходит быстрее, чем запись 100 000 нулей. Однако вы можете прочитать исходный код ядра Linux, чтобы увидеть, как реализованы разреженные файлы, вы можете прочитать исходный код RandomAccessFile в JDK и исходный код JRE, чтобы увидеть, как они взаимодействуют. Однако это, вероятно, больше деталей, чем вам нужно.

person slim    schedule 23.02.2017
comment
Этот ответ также обрабатывает ваше редактирование @RangaRajan. Данные - это 0 с, которые обрабатываются разреженным файлом. - person Gray; 23.02.2017
comment
Спасибо за ответы - person SirVirgin; 23.02.2017
comment
@slim Будет ли разреженный файл перезаписывать удаленные данные? (данные удалены, но еще не перезаписаны) - person SirVirgin; 23.02.2017
comment
Прочитав это описание, я понял, что это не так, но я поэкспериментировал и обнаружил, что не могу восстановить файл с помощью Piriform's Recuva. (Windows 10) - person SirVirgin; 23.02.2017
comment
Если у вас есть другой вопрос, задайте его как новый вопрос, но вам нужно будет объяснить, что вы имеете в виду под перезаписью удаленных данных и данных, удаленных, но еще не перезаписанных. Также он может принадлежать другому сайту Stack Exchange, в зависимости от того, о какой ОС вы спрашиваете. - person slim; 23.02.2017

Ваша операционная система и файловая система поддерживают разреженные файлы, и когда это так, реализуется seek для использования этой функции.

На самом деле это не связано с Java, это просто особенность функций fseek и fwrite из библиотеки C, которые, скорее всего, являются бэкэндом реализации File в JRE, которую вы используете.

дополнительная информация: https://en.wikipedia.org/wiki/Sparse_file

Есть ли более эффективный способ создать файл из n байтов и заполнить его нулями?

В операционных системах, которые его поддерживают, вы можете обрезать файл до нужного размера вместо вызова write . Однако, похоже, это недоступно в API Java.

person Display Name    schedule 23.02.2017
comment
не могли бы вы уточнить? Плюс КАК реализован метод seek ()? Есть ли способ увидеть, как реализованы встроенные библиотечные методы? - person SirVirgin; 23.02.2017
comment
@RangaRajan, это подходит для публикации в качестве другого вопроса. - person Display Name; 23.02.2017
comment
Спасибо за ответы - person SirVirgin; 23.02.2017
comment
@Gray Будет ли разреженный файл перезаписывать удаленные данные? (данные удалены, но еще не перезаписаны) - person SirVirgin; 23.02.2017
comment
Редкий файл должен быть разреженным. Это должно быть создано поиском. Если вы напишете в файл 0 или любой другой байт, то он не является разреженным. поиск файла ничего не удаляет. Он просто перемещает указатель файла вокруг @RangaRajan. - person Gray; 25.02.2017