Java: присвоение идентификаторов ссылки на объект для пользовательской сериализации

По различным причинам у меня есть пользовательская сериализация, где я вывожу некоторые довольно простые объекты к файлу данных. Существует, возможно, 5-10 классов и графы объектов, что результат является нециклическим и довольно простым (каждый сериализованный объект имеет 1 или 2 ссылки на другого, которые сериализируются). Например:

class Foo
{
    final private long id;
    public Foo(long id, /* other stuff */) { ... }
}

class Bar
{
    final private long id;
    final private Foo foo;
    public Bar(long id, Foo foo, /* other stuff */) { ... }
}

class Baz
{
    final private long id;
    final private List<Bar> barList;
    public Baz(long id, List<Bar> barList, /* other stuff */) { ... }
}

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

То, что является озадачивающим меня, - то, как присвоить идентификатор. Я думал об этом, и кажется, что существует три случая для присвоения идентификатора:

  • динамично-созданные-объекты - идентификатор присвоен из счетчика, который увеличивает
  • при чтении объектов из диска - идентификатор присвоен от числа, сохраненного в дисковом файле
  • одноэлементные объекты - объект создается до любого динамично-созданного-объекта, для представления одноэлементного объекта, который всегда присутствует.

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


разъяснение: так же, как некоторая тангенциальная информация формат файла, на который я смотрю, является приблизительно следующим (заминающий несколько деталей, которые не должны быть релевантными). Это оптимизировано для обработки довольно большого объема плотных двоичных данных (десятки/сотни МБ) со способностью вкрапить структурированные данные в нем. Плотные двоичные данные составляют 99,9% размера файла.

Файл состоит из серии исправленных ошибкой блоков, которые служат контейнерами. Каждый блок может считаться содержащий массив байтов, который состоит из серии пакетов. Возможно считать пакеты по одному по очереди (например, возможно сказать, где конец каждого пакета, и следующий запускается сразу впоследствии).

Таким образом, файл может считаться серией пакетов, сохраненных сверх слоя с коррекцией ошибок. Подавляющее большинство этих пакетов является непрозрачными двоичными данными, который не имеет никакого отношения к этому вопросу. Малочисленное меньшинство этих пакетов, однако, является объектами, содержащими сериализированные структурированные данные, формируя своего рода "архипелаг", состоящий из данных "острова", которые могут быть связаны отношениями ссылки на объект.

Таким образом, у меня мог бы быть файл, где пакет 2971 содержит сериализированного Foo, и пакет 12083 содержит сериализированную Панель, которая относится к Foo в пакете 2971. (с пакетами 0-2970 и 2972-12082 являющийся непрозрачными пакетами данных)

Все эти пакеты все неизменны (и поэтому, учитывая ограничивание конструкции объекта Java, они формируют нециклический граф объектов), таким образом, я не должен заниматься проблемами переменчивости. Они - также потомки общего Item интерфейс. То, что я хотел бы сделать, записать произвольное Item возразите против файла. Если Item содержит ссылки на другой Items, которые уже находятся в файле, я должен записать тем, которые в файл также, но только если они еще не были записаны. Иначе у меня будут дубликаты, которые я должен буду так или иначе объединить, когда я считал их назад.

6
задан Jason S 8 June 2010 в 19:50
поделиться

3 ответа

Вам действительно нужно это делать? Внутри ObjectOutputStream отслеживает, какие объекты уже были сериализованы. Последующие записи одного и того же объекта сохраняют только внутреннюю ссылку (аналогично записи только id), а не записывают весь объект снова.

Подробнее см. в Serialization Cache.

Если идентификаторы соответствуют какому-то внешнему идентификатору, например, идентификатору объекта, то это имеет смысл. Но в вопросе говорится, что идентификаторы генерируются исключительно для отслеживания того, какие объекты сериализуются.

Вы можете обрабатывать одиночные объекты с помощью метода readResolve. Простой подход заключается в сравнении свежего десериализованного экземпляра с вашими экземплярами синглтонов, и если есть совпадение, возвращайте экземпляр синглтона, а не десериализованный экземпляр. Например,

   private Object readResolve() {
      return (this.equals(SINGLETON)) ? SINGLETON : this;
      // or simply
      // return SINGLETON;
   }

EDIT: В ответ на комментарии, поток - это в основном двоичные данные (хранящиеся в оптимизированном формате) со сложными объектами, разбросанными в этих данных. С этим можно справиться, используя формат потока, поддерживающий подпотоки, например, zip, или простую разбивку на блоки. Например, поток может быть последовательностью блоков:

offset 0  - block type
offset 4  - block length N
offset 8  - N bytes of data
...
offset N+8  start of next block

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

Чтобы реализовать поток, пусть ваш основной поток разбирает блоки, например

   DataInputStream main = new DataInputStream(input);
   int blockType = main.readInt();
   int blockLength = main.readInt();
   // next N bytes are the data
   LimitInputStream data = new LimitInputStream(main, blockLength);

   if (blockType==BINARY) {
      handleBinaryBlock(new DataInputStream(data));
   }
   else if (blockType==OBJECTSTREAM) {
      deserialize(new ObjectInputStream(data));
   }
   else
      ...

Набросок LimitInputStream выглядит так:

public class LimitInputStream extends FilterInputStream
{
   private int bytesRead;
   private int limit;
   /** Reads up to limit bytes from in */
   public LimitInputStream(InputStream in, int limit) {
      super(in);
      this.limit = limit;
   }

   public int read(byte[] data, int offs, int len) throws IOException {
      if (len==0) return 0; // read() contract mandates this
      if (bytesRead==limit)
         return -1;
      int toRead = Math.min(limit-bytesRead, len);
      int actuallyRead = super.read(data, offs, toRead);
      if (actuallyRead==-1)
          throw new UnexpectedEOFException();
      bytesRead += actuallyRead;
      return actuallyRead;
   }

   // similarly for the other read() methods

   // don't propagate to underlying stream
   public void close() { }
}
4
ответ дан 17 December 2019 в 02:24
поделиться

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

Да, похоже, подойдет сериализация объектов по умолчанию, или, в конце концов, вы выполняете предварительную оптимизацию.

Вы можете изменить формат сериализованных данных (как это делает XMLEncoder ) на более удобный.

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

class Foo {
    private final int id;
    public Foo( int id, /*other*/ ) { // drop the int id
    }
 }

Итак, класс может быть "последовательностью" и вероятно, длиннее было бы более подходящим, чтобы избежать проблем с Integer.MAX_VALUE .

Использование AtomicLong , как описано в пакете java.util.concurrent.atomic (во избежание назначения двум потокам одного и того же идентификатора или во избежание чрезмерной синхронизации) тоже может помочь .

class Sequencer {
    private static AtomicLong sequenceNumber = new AtomicLong(0);
    public static long next() { 
         return sequenceNumber.getAndIncrement();
    }
}

Теперь в каждом классе у вас есть

 class Foo {
      private final long id;
      public Foo( String name, String data, etc ) {
          this.id = Sequencer.next();
      }
 }

И все.

(обратите внимание, я не помню, вызывает ли десериализация объекта конструктор, но вы поняли идею)

1
ответ дан 17 December 2019 в 02:24
поделиться

Зарегистрированы ли foos в FooRegistry? Вы можете попробовать этот подход (предположим, что у Bar и Baz также есть реестры для получения ссылок через id).

Это, вероятно, имеет много синтаксических ошибок, ошибок использования и т. Д. Но я чувствую, что подход хороший.

public class Foo {

public Foo(...) {
    //construct
    this.id = FooRegistry.register(this);
}

public Foo(long id, ...) {
    //construct
    this.id = id;
    FooRegistry.register(this,id);
}

}

public class FooRegistry() { Map foos = новая HashMap...

long register(Foo foo) {
    while(foos.get(currentFooCount) == null) currentFooCount++;
    foos.add(currentFooCount,foo);
    return currentFooCount;
}

void register(Foo foo, long id) {
    if(foo.get(id) == null) throw new Exc ... // invalid
    foos.add(foo,id);
}

}

публичный класс Bar() {

void writeToStream(OutputStream out) {
    out.print("<BAR><id>" + id + "</id><foo>" + foo.getId() + "</foo></BAR>");
}

}

публичный класс Baz() {

void.writeToStream(OutputStream out) {
    out.print("<BAZ><id>" + id + "</id>");
    for(Bar bar : barList) out.println("<bar>" + bar.getId() + </bar>");
    out.print("</BAZ>");
}

}

1
ответ дан 17 December 2019 в 02:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: