其他
今天聊聊 Java 序列化
点击上方 Java后端,选择 设为星标
优质文章,及时送达什么是序列化和反序列化?为什么要序列化? 怎么实现序列化? 序列化的原理是什么呢? transient关键字 序列化时应注意什么?
1. 什么是序列化和反序列化?
Java序列化是指把Java对象转换为字节序列的过程;
Java反序列化是指把字节序列恢复为Java对象的过程;
2. 为什么要序列化?
一般Java对象的生命周期比Java虚拟机端,而实际开发中如果需要JVM停止后能够继续持有对象,则需要用到序列化技术将对象持久化到磁盘或数据库。 在多个项目进行RPC调用时,需要在网络上传输JavaBean对象,而网络上只允许二进制形式的数据进行传输,这时则需要用到序列化技术。
3. 怎么实现序列化?
将对象实例相关的类元数据输出 递归地输出类的超类描述,直到没有超类 类元数据输出之后,开始从最顶层的超类输出对象实例的实际数据值 从上至下递归输出实例的数据
实现序列化的要求:目标对象实现Serializable接口
public class NY implements Serializable {
private static final long serialVersionUID = 8891488565683643643L;
private String name;
private String blogName;
@Override
public String toString() {
return "NY{" +
"name='" + name + '\'' +
", blogName='" + blogName + '\'' +
'}';
}
}
JDK类库中序列化和反序列化API
public class SerializeNY {
public static void main(String[] args) throws IOException, ClassNotFoundException {
serializeNY();
NY ny = deserializeNY();
System.out.println(ny.toString());
}
private static void serializeNY() throws IOException {
NY ny = new NY();
ny.setName("NY");
ny.setBlogName("NYfor2020");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\serialable.txt")));
oos.writeObject(ny);
System.out.println("NY 对象序列化成功!");
oos.close();
}
private static NY deserializeNY() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\serialable.txt")));
NY ny = (NY) ois.readObject();
System.out.println("NY 对象反序列化成功");
return ny;
}
}
NY 对象序列化成功!
NY 对象反序列化成功
NY{name='NY', blogName='NYfor2020'}
但是我们平时看到序列化的实体中的serialVersionUID,为什么有的是1L,有的是一长串数字?
private static final long serialVersionUID = 8891488565683643643L; //将末尾的2改成3
Exception in thread "main" java.io.InvalidClassException: NY; local class incompatible: stream classdesc serialVersionUID = 8891488565683643642, local class serialVersionUID = 8891488565683643643
......
序列化的原理是什么呢?
private static void serializeNY() throws IOException {
NY ny = new NY();
ny.setName("NY");
ny.setBlogName("NYfor2020");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\serialable.txt")));
oos.writeObject(ny);
System.out.println("NY 对象序列化成功!");
oos.close();
}
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
writeStreamHeader();
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
我们进入writeStreamHeader()方法:
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);
bout.writeShort(STREAM_VERSION);
}
final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
int h;
if ((obj = subs.lookup(obj)) == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
for (;;) {
Class<?> repCl;
desc = ObjectStreamClass.lookup(cl, true);
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
}
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
}
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
desc.checkSerialize();
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
int handle;
if (desc == null) {
writeNull();
} else if (!unshared && (handle = handles.lookup(desc)) != -1) {
writeHandle(handle);
} else if (desc.isProxy()) {
writeProxyDesc(desc, unshared);
} else {
writeNonProxyDesc(desc, unshared);
}
}
private void writeNull() throws IOException {
bout.writeByte(TC_NULL);
}
final static byte TC_NULL = (byte)0x70;
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
bout.writeByte(TC_CLASSDESC);
handles.assign(unshared ? null : desc);
if (protocol == PROTOCOL_VERSION_1) {
desc.writeNonProxy(this);
} else {
writeClassDescriptor(desc);
}
Class<?> cl = desc.forClass();
bout.setBlockDataMode(true);
if (cl != null && isCustomSubclass()) {
ReflectUtil.checkPackageAccess(cl);
}
annotateClass(cl);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
‘
writeClassDesc(desc.getSuperDesc(), false);
}
protected void writeClassDescriptor(ObjectStreamClass desc)
throws IOException{
desc.writeNonProxy(this);
}
void writeNonProxy(ObjectOutputStream out) throws IOException {
out.writeUTF(name);
out.writeLong(getSerialVersionUID());
byte flags = 0;
if (externalizable) {
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
int protocol = out.getProtocolVersion();
if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
}
} else if (serializable) {
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
}
if (hasWriteObjectData) {
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
}
if (isEnum) {
flags |= ObjectStreamConstants.SC_ENUM;
}
out.writeByte(flags);
out.writeShort(fields.length);
for (int i = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
out.writeByte(f.getTypeCode());
out.writeUTF(f.getName());
if (!f.isPrimitive()) {
out.writeTypeString(f.getTypeString());
}
}
}
调用writeUTF()方法将对象所属类的名字写入。 调用writeLong()方法将类的序列号serialVersionUID写入。 判断被序列化对象所属类的流类型flag,写入底层字节容器中(占两个字节)。 写入对象中的所有字段,以及对应的属性
private void writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared)
throws IOException{
...
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
当执行到defaultWriteFields()方法时,会将实例数据写入:
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals);
bout.write(primVals, 0, primDataSize, false);
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
if (extendedDebugInfo) {
debugInfoStack.push(
"field (class \"" + desc.getName() + "\", name: \"" +
fields[numPrimFields + i].getName() + "\", type: \"" +
fields[numPrimFields + i].getType() + "\")");
}
try {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
...
writeClassDescriptor(desc);
}
Class<?> cl = desc.forClass();
bout.setBlockDataMode(true);
if (cl != null && isCustomSubclass()) {
ReflectUtil.checkPackageAccess(cl);
}
annotateClass(cl);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
‘
writeClassDesc(desc.getSuperDesc(), false);
}
transient关键字
transient关键字的使用
private transient String name;
当我们再次运行SerializeNY类中的main()方法时,运行结果如下:NY 对象序列化成功!
NY 对象反序列化成功
NY{name='null', blogName='NYfor2020'}
transient关键字的特点
private static String name;
NY 对象序列化成功!
NY 对象反序列化成功
NY{name='NY', blogName='NYfor2020'}
private static void serializeNY() throws IOException {
NY ny = new NY();
ny.setName("NY");
ny.setBlogName("NYfor2020");
ny.setTest("12");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\serialable.txt")));
oos.writeObject(ny);
System.out.println("NY 对象序列化成功!");
System.out.println(ny.toString());
oos.close();
ny.setName("hey, NY");
}
NY 对象序列化成功!
NY{name='NY', blogName='NYfor2020'}
NY 对象反序列化成功
NY{name='hey, NY', blogName='NYfor2020'}
transient修饰的变量真的就不能被序列化了吗?
public class ExternalizableTest implements Externalizable {
private transient String content = "即使被transient修饰,我也会序列化";
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(content);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
content = (String)in.readObject();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ExternalizableTest et = new ExternalizableTest();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\externalizable.txt")));
oos.writeObject(et);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\externalizable.txt")));
et = (ExternalizableTest) ois.readObject();
System.out.println(et.content);
oos.close();
ois.close();
}
}
即使被transient修饰,我也会序列化
如果实现的是Serializable接口,则所有信息(不包括被static、transient修饰的变量信息)的序列化将自动进行。 如果实现的是Externalizable接口,则不会进行自动序列化,需要开发者在writeExternal()方法中手工指定需要序列化的变量,与是否被transient修饰无关。
序列化注意事项
序列化对象必须实现序列化接口Serializable。 序列化对象中的属性如果也有对象的话,其对象需要实现序列化接口。 类的对象序列化后,类的序列号不能轻易更改,否则反序列化会失败。 类的对象序列化后,类的属性增加或删除不会影响序列化,只是值会丢失。 如果父类序列化,子类会继承父类的序列化;如果父类没序列化,子类序列化了,子类中的属性能正常序列化,但父类的属性会丢失,不能序列化。 用Java序列化的二进制字节数据只能由Java反序列化,如果要转换成其他语言反序列化,则需要先转换成Json/XML通用格式的数据。 如果某个字段不想序列化,在该字段前加上transient关键字即可。(咳咳,下一篇就是写这个了,敬请关注~)