我们都知道,在我们自定义一个类的时候,如果该类实现了Serializable接口, 那么,他会给出一个警告,要求产生一个serialVersionUID,也就是我们所说的序列号。那么,这个序列号是什么,有什么用呢?我们先来做一个测试。
首先给出一个FileSectionInfo类,目的是让该类实现Serializable接口,然后产生一个序列号。(类里面其余的代码不用管)
public class FileSectionInfo implements Serializable {
private static final long serialVersionUID = -2935738602520618299L;
private int fileHandle;
private long offset;
private int size;
FileSectionInfo() {
}
FileSectionInfo(byte[] value) {
if (value.length != 16) {
System.out.println("长度不为16B!");
return;
}
byte[] bFileHandle = BytesToString.getBytesAt(value, 0, 4);
byte[] bOffset = BytesToString.getBytesAt(value, 4, 8);
byte[] bSize = BytesToString.getBytesAt(value, 12, 4);
this.fileHandle = BytesToString.bytesToInt(bFileHandle);
this.offset = BytesToString.bytesToLong(bOffset);
this.size = BytesToString.bytesToInt(bSize);
}
FileSectionInfo(int fileHandle, long offset, int size) {
this.fileHandle = fileHandle;
this.offset = offset;
this.size = size;
}
int getFileHandle() {
return fileHandle;
}
void setFileHandle(int fileHandle) {
this.fileHandle = fileHandle;
}
long getOffset() {
return offset;
}
void setOffset(long offset) {
this.offset = offset;
}
int getSize() {
return size;
}
void setSize(int size) {
this.size = size;
}
byte[] toBytes() {
byte[] bFileHandle = BytesToString.intToBytes(fileHandle);
byte[] bOffset =BytesToString.longToBytes(offset);
byte[] bSize = BytesToString.intToBytes(size);
byte[] result = new byte[16];
BytesToString.setBytesAt(result, 0, bFileHandle);
BytesToString.setBytesAt(result, 4, bOffset);
BytesToString.setBytesAt(result, 12, bSize);
return result;
}
@Override
public String toString() {
StringBuffer res = new StringBuffer("fileHandle:");
res.append(this.fileHandle).append("\n")
.append("offset:").append(this.offset).append("\n")
.append("size:").append(this.size);
return res.toString();
}
}
我们编写一下我们的测试类:
public static void main(String[] args) {
try {
FileOutputStream os = new FileOutputStream(
"F:\\example\\out.data");
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(new FileSectionInfo(10, 20, 30));
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
这段代码,其实是为了生成一个FileSectionInfo类的对像,然后把该对象写到一个out.data文件中。执行完上述代码,控制台是没有任何输出结果的,但是我们可以看到我们的out.data文件是有内容的。那么用UE软件打开它,将看到的是十六进制的字符集。
观察右面的字符集,我们可以清楚的看到,这个类所在的包名字,这个类的三个成员,而成员后面所跟的J、I等是这个成员的类型。我们现在要找的是我们的序列号。首先来看我们在类里面生成的序列号:-2935738602520618299L;
因为这是十进制原码,我们需要将它变为二进制补码,如下:
十进制原码:
-2935738602520618299L
变为二进制原码:
1010 1000 1011 1101 1101 0110 1011 0100 0110 1100 1010 0100 1010 0101 0011 1011
变为二进制补码:
1101 0111 0100 0010 0010 1001 0100 1011 1001 0011 0101 1011 0101 1010 1100 0101
变为十六进制补码:
D7 42 29 4B 93 5B 5A C5
可以看到这就是序列号,它是一个类的唯一标识,因为它的类型是带有符号的长整型,所以,用它来标识一个类,那这个类的的重复概率是万亿分之一。
由此,我们可以想到,java是将序列号作为键,类作为值,形成一个hashMap来存储,这样,就可以通过序列号来找到对应的类,这当然也需要反射机制来实现。
上述的传输形式,我采用的是发送Object对象来实现的。但是,这样虽然可以实现,但是效率未必是最高的。因为它也保存了许多对于我们来说没有用的信息。
假如我们去掉序列号,对于写操作是完全没有问题的,但是对于读操作,就会出现异常:
先对Test类进行修改:
public static void main(String[] args) {
try {
FileOutputStream os = new FileOutputStream(
"F:\\example\\out.data");
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(new FileSectionInfo(10, 20, 30));
oos.close();
FileInputStream is = new FileInputStream("F:\\example\\out.data");
ObjectInputStream ois = new ObjectInputStream(is);
FileSectionInfo file = (FileSectionInfo) ois.readObject();
System.out.println(file);
ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
然后去掉FileSectionInfo类的接口和其序列号,执行,结果如下:
进一步确定,序列号是一个类的唯一标识。