Привет всем.
Собственно сам вопрос ниже (
вопрос), а здесь — предистория к нему.
Возникла задача — записывать и считывать объекты целиком как последовательность байтов (В С++ это у меня получалось на ура). Преимущество в том, что не приходится писать каждый член класса по отдельности. Скидываем целый объект. Реализуется это имплементированием интерфейса Serializable ( тот класс, который сбрасываем) и следующим кодом
FileOutputStream fileStream = new FileOutputStream("object.data");
ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);
objectStream.writeObject(this);
Но одна проблема: у меня после записи этого класса в файле идет несколько гигов двоичной инфы. А если объект увеличится в размерах? Он уже не поместится в пространство от начала файла и до начала двоичных данных.
Вопрос: как в Java переписать часть файла (в серединке), при этом, возможно, не только overwrite' ить, но еще и добавлять инфу в серединку?
Можно ли как-нибудь вырезать часть из серединки, а затем туда вставить блок? Предоставляет ли Java какие-нибудь средства? Предупреждая гневные возгласы, скажу, что поиском я воспользовался, прочитал и про то, что в C стандартных способов нет (хотя я и писал так, открыв файл на append — a+), и про memmove.
Этот вопрос немного посложнее.
Как мне обновить запись объекта в файле? Она может увеличиться, может уменьшиться.
Можно ли, допустим, полностью удалить предыдущую запись объекта, а затем вставить новую?
D>>Никак. Разве что сделать это руками как и в C.
Можно, конечно, сделать нативный метод, и в С-ной части кода расширить пространство в файле под объект (не слишком ли хитрый путь...)
Ибо я что-то не нашел в Java API, можно ли добавлять в произвольное место потока. Напротив,
Public FileOutputStream(String name,
boolean append)
throws FileNotFoundException
Creates an output file stream to write to the file with the specified name. If the second argument is true, then bytes will be written to the end of the file rather than the beginning.
А потом, расчистив место, уже делать overwrite. В этом случае уже не удастся передвинуть назад все нижеследующее, если объект уменьшится, ну да это и не важно.
— А можно ли узнать количество байт, на которое вырастет описание объекта? (можно, конечно, записать во временный файл
Как-то чересчур много гемора получается
D>>Вообще-то, в данном лучше было бы использовать не сериализацию, а базу данных.
L>Или переопределить readObject/writeObject и данные объекта сохранять отдельно где-нибудь рядом, а в сериализуемый стрим писать путь, по которому можно найти эти данные.
Изврат задачи в том, чтобы все вышеописанное хранилось в одном файле
Диктую условия не я...
Здравствуйте, iAlexander, Вы писали:
Это не ответ на вопрос, но, возможно, решение
Как правило, размеры объекта играют в некотором ограниченном диапазоне. Т.е. можно зарезервировать некоторое пространство в файле под объет и переписывать его, не изменяя размер файла. Если бинари в файле занимают 2 гига, то увеничение размера файла на сотню метров роли не играет....
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Здравствуйте, iAlexander, Вы писали:
D>>>Никак. Разве что сделать это руками как и в C.
A>Можно, конечно, сделать нативный метод, и в С-ной части кода расширить пространство в файле под объект (не слишком ли хитрый путь...)
Это в принципе невозможно на файловом IO — оно по своему смыслу потоковое и файл являет собой единый кусок.
По крайней мере в SystemV R4, Linux и Win32 я таких функций не видел, так что могу сделать предположение, что таких API не существует в природе. Буду признателен, если переубедишь меня.
A>Ибо я что-то не нашел в Java API, можно ли добавлять в произвольное место потока. Напротив,
A>A>Public FileOutputStream(String name,
A> boolean append)
A> throws FileNotFoundException
A>Creates an output file stream to write to the file with the specified name. If the second argument is true, then bytes will be written to the end of the file rather than the beginning.
A>А потом, расчистив место, уже делать overwrite. В этом случае уже не удастся передвинуть назад все нижеследующее, если объект уменьшится, ну да это и не важно.
Если ты ведешь разговор про файл произвольного доступа, по которому можно двигаться и читать/писать из/в произвольного(е) места(о),
тогда смотри java.io.RandomAccessFile. Довольно легко написать реализацию InputStream/OutputStream написав абстрактную реализацию методов read и write. Но проблему "вставки в файл" это не решает.
A> — А можно ли узнать количество байт, на которое вырастет описание объекта? (можно, конечно, записать во временный файл Как-то чересчур много гемора получается
Для этого можно записать в java.io.ByteArrayOutputStream:
Object obj=...; //here goes object iniitalization
ByteArrayOutputStream baos = new ByteOutputStream();
new ObjectOutputStream(baos).writeObject(obj);
int newLength = baos.size();
A>Изврат задачи в том, чтобы все вышеописанное хранилось в одном файле Диктую условия не я...
Конечно идеальный вариант это сгенерировать код. Взять что-нибудь типа EMF и написать к нему шаблоны, которые генерируют код сериализации состояния, метод определения размера этого состояния и все прочее. Может быть то же самое можно сделать с помощью XDoclet. Но это требуется знание EMF или же Xdoclet, а готовое решение для довольно нетипичной проблемы тебе вряд ли дадут.
Есть еще вариант — использовать базу данных, которая все свое состояние хранит в одном файле и не требует установки сервера. К примеру
HSQLDB. Точнее его
In-Process (Standalone) Mode.
Я, как человек весьма ленивый, остановился бы на каком-нибудь из вышеприведенных вариантов (хотя все зависит от условий задачи...)
Но есть решение, которое позволяет пользоваться сериализацией (оно не очень простое — по сути своя файловая система, где в файле содержится один объект, но и не очень сложное).
Нужно разбить файл на блоки размера 2^N, N целое, N>0.
Каждый блок содержит следующую сервисную информацию:
1) N бит — размер реально хранимых данных.
2) 1 бит — признак, занят или свободен данный блок.
3) 64-N бит — положение следующего блока в файле (положение байта в файле в общем случае характеризуется 64-битным числом, нам нужно определить положение каждого 2^N-го байта).
4) 1 бит — признак того, что блок является последним в записи
Итого 66 бит > 8 байт (можно еще поджать где-нибудь, но для выделенных значений N).
Предлагается для упрощения жизни использовать целое число байт для обозначния 1) и 2) вместе и еще целое число байт для обозначния 3) и 4).
Соотвественно имеем 1+N/8 (деление целочисленное) байт на размер данных в блоке и признак занят/не занят, 8-(N-1)/8 (деление целочисленное) байт на "номер" следующего блока. Тут же становится очевидно, что N>=4.
В каждом блоке остается 2^N-9 (при N кратных 8 — 2^N-10) байтов, которые можно использовать под запись информации.
Получаем следующий алгоритм работы:
При записи в файл:
1) Объект сериализуется в байтовый массив. Определяется размер.
2) Ищется первый свободный блок в файле. Если такого нет, то выделяется новый в конце файла.
3) В область, которая доступна для записи информации пишутся данные из массива.
4) Если закончена запись, тогда проставляется соотвествующий флаг.
5) Если нет, тогда ищется новый свободный блок. В записаный только что блок проставляется номер вновь найденного блока. Если такого нет, то выделяется новый в конце файла. Goto 3.
При чтении:
1) Ищется первый блок данного объекта.
2) Читаются данные в байтовый массив
3) Повторять, пока не прочитали последний блок.
4) Десериализовать данные из массива.
Оптимизация: в первом блоке можно также хранить фактическую длину объекта(4 байта), соответственно длинну массива можно подстравивать всего один раз.
При удалении:
1) Ищем первый блок объекта.
2) Помечаем как удаленный
3) Если блок не последний, ищем следующий блок, Goto 2.
Оптимизация:
Для убыстрения операции поиска свободных блоков, предлагается свободные блоки увязывать в список, а в голове файла хранить
Тогда при удалении на шаге 2 нужно найти свободные блоки "слева" (ближе к голове, чем данный) и "справа" (ближе к концу, чем данный).
1) Если "левый" — пустой => первый свободный блок в файле — тот, что удаляется сейчас. Если нет, то "левый".следующий = удаляемый
2) Если "правый" — не пустой, то удаляемый.следующий = "правый".
Короче связный список, он и в Африке связный список
Замечу, что способ не претендует на супероптимальность, так как я не знаю условия всей задачи.
Также возможно:
1) Использовать под хранение сервисной информации число байт, совпадающее с размером каког-либо из целочисленных типов (8,16,32,64).
Это увеличит процент сервисной информации, но упростит чтение.
2) В файле хранить таблицу, которая может по какому-либо идентификатору выдать первый блок в записи объекта (идентификатор уникальный и не зависит от среды исполнения. Это я к тому, что identityHashCode не подойдет). Хранить можно по тому же механизму, используя Hashtable, только уговориться, что первый блок в ее записи обязательно лежит в первом блоке файла (это тот, что с индексом 0).
5) Хранить список свободных блоков в самом файле (тоже где-нибудь в начале).
6) Можно также уменьшить процент сервисной информации, но это ударит по производительности и будет возможно не при всех.
Последние замечания:
При таком подходе часть файла не будет содержать реальной информации. Чем больше размер блока, тем большая часть будет теряться. Но чем меньше размер блока, тем большее число операций позиционирования надо будет сделать — так что упадет производительность.
Работать с таким файлом надо, разумеется, через RandomAccessFile
Здравствуйте, GuinPin, Вы писали:
GP>Здравствуйте, iAlexander, Вы писали:
GP>Это не ответ на вопрос, но, возможно, решение
GP>Как правило, размеры объекта играют в некотором ограниченном диапазоне. Т.е. можно зарезервировать некоторое пространство в файле под объет и переписывать его, не изменяя размер файла. Если бинари в файле занимают 2 гига, то увеничение размера файла на сотню метров роли не играет....
Думаю объект в даном случае и 1 мегабайт не потянет
скорее всего это структоры описатель следующих за ним данных, и "весит он наверное 600-800 кб.
Резервируешь пару десяткоф мегов (на вс. пож), и после объекта делаешь skip столько сколько осталось до конца области для "загловка". Вообще можно рендомным файлом (RandomAccessFile) абойтись, дописав заголовок в конец а после ниго размер объекта-заголовка в виде 2х-4х байт.
А если область данных (та что 2 гига) фиксированного размера, то вообще вуаля — сещвемся на размер области с данными и пишем/читаем объект. (иногда м. б. OptionalDataException, их тоже отлавливай, и смотри в чём лажа. Обычно это когда класс без serialVersiobID перекомпилишь и старую версию открыл, читая новый объект. И наоборот
).
... << RSDN@Home 1.1.4 beta 3 rev. 185>> @@J[getWorld.ApplyCheats(unfair.Cheats.IDDQD_AND_IDKFA]
nant
Это в принципе невозможно на файловом IO — оно по своему смыслу потоковое и файл являет собой единый кусок.
По крайней мере в SystemV R4, Linux и Win32 я таких функций не видел, так что могу сделать предположение, что таких API не существует в природе. Буду признателен, если переубедишь меня.
Да, я оказался неправ:
When a file is opened with the "a" or "a+" access type, all write operations occur at the end of the file. The file pointer can be repositioned using fseek or rewind, but is always moved back to the end of the file before any write operation is carried out. Thus, existing data cannot be overwritten.
Извините, оказался неправ.
Почему-то было впечатление, что это делал...
nant, огромное спасибо за разъяснения и такое подробное изложение. Очень интересная идея со своей файловой системой. Боюсь только, что она по трудоемкости будет стоять наравне, а может и выше, моей задачи. А на Java я недавно начал серьезно писать, так что с библиотеками познакомиться не было времени.
Наверное, я все-таки зарезервирую буфер под объект в мегабайт или несколько, как посоветовал
GuinPin и
Волк-Призрак. А объект описывает структуру идущих за ним нескольких гигов информации. Меняется он в случае, когда меняется эта информация: добавляется, удаляется, модифицируется. Поэтому объект должен идти в начале файла.
Большое спасибо всем за участие.