由于各种原因,我有一个自定义序列化,我将一些相当简单的对象转储到数据文件。可能有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 */) { ... }
}
id字段仅用于序列化,因此当我序列化到文件时,我可以通过保存到目前为止序列化了哪些ID的记录来编写对象,然后为每个对象检查其子对象是否已经序列化并编写那些没有的,最后通过编写其数据字段和与其子对象相对应的ID来编写对象本身。
让我感到困惑的是如何分配id。我想到了,似乎有三种情况可以分配ID:
我该如何妥善处理?我觉得我正在重新发明轮子,必须有一套完善的技术来处理所有情况。
澄清:就像一些切向信息一样,我正在查看的文件格式大致如下(掩盖了一些不应该相关的细节)。它经过优化处理相当大量的密集二进制数据(数十/数百MB),并能够在其中散布结构化数据。密集的二进制数据占文件大小的99.9%。
该文件由一系列纠正错误的块组成,这些块用作容器。可以认为每个块包含由一系列包组成的字节数组。可以一次一个地读取一个数据包(例如,可以告诉每个数据包的结束位置,然后立即开始下一个数据包。)
因此,该文件可以被认为是存储在纠错层之上的一系列数据包。绝大多数这些数据包都是不透明的二进制数据,与此问题无关。然而,这些数据包中的少数是包含序列化结构化数据的项目,形成一种由数据“孤岛”组成的“群岛”,可以通过对象参考关系链接。
所以我可能有一个文件,其中包2971包含一个序列化的Foo,而包12083包含一个序列化的Bar,它引用了包2971中的Foo。(包0-2970和2972-12082是不透明的数据包)
所有这些数据包都是不可变的(因此给定了Java对象构造的约束,它们形成了一个非循环对象图),所以我不必处理可变性问题。它们也是常见Item
接口的后代。我想要做的是将任意Item
对象写入文件。如果Item
包含对文件中已有的其他Item
的引用,我也需要将它们写入文件,但前提是它们尚未编写。否则,当我读回它时,我将需要以某种方式合并的重复项。
答案 0 :(得分:4)
你真的需要这样做吗?在内部,ObjectOutputStream
跟踪已经序列化了哪些对象。对同一对象的后续写入仅存储内部引用(类似于仅写出id),而不是再次写出整个对象。
有关详细信息,请参阅Serialization Cache。
如果ID对应于某些外部定义的身份,例如实体ID,那么这是有道理的。但问题是,ID的生成纯粹是为了跟踪哪些对象被序列化。
您可以通过readResolve
方法处理单身人士。一种简单的方法是将刚刚反序列化的实例与单例实例进行比较,如果匹配,则返回单例实例而不是反序列化实例。 E.g。
private Object readResolve() {
return (this.equals(SINGLETON)) ? SINGLETON : this;
// or simply
// return SINGLETON;
}
编辑:在回应评论时,流主要是二进制数据(以优化格式存储),其中复杂对象不分散在该数据中。这可以通过使用支持子流的流格式来处理,例如,拉链,或简单的块状块。例如。流可以是一系列块:
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() { }
}
答案 1 :(得分:1)
foo注册了FooRegistry吗?您可以尝试这种方法(假设Bar和Baz也有注册表通过id获取引用)。
这可能有很多语法错误,使用错误等。但我觉得这种方法很好。
公共课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 = new 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>");
}
}
答案 2 :(得分:1)
我觉得我正在重新发明轮子,必须有一套完善的技术来处理所有情况。
是的,看起来像默认对象序列化会做,或者最终你是预先优化的。
您可以更改序列化数据的格式(例如XMLEncoder)以获得更方便的格式。
但是如果你坚持,我认为带有动态计数器的单例应该这样做,但是不要把id放在构造函数的公共接口中:
class Foo {
private final int id;
public Foo( int id, /*other*/ ) { // drop the int id
}
}
因此,该类可能是一个“序列”,并且可能更长一段时间更适合避免Integer.MAX_VALUE
出现问题。
使用java.util.concurrent.atomic包中描述的AtomicLong
(以避免两个线程分配相同的ID,或避免过度同步)也会有所帮助。
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();
}
}
就是这样。
(注意,我不记得反序列化对象是否会调用构造函数,但是你明白了)