Java序列化与反序列化

用途

  1) 把对象的字节序列永久地保存到硬盘上,一般存放在一个文件中。
  2) 在网络上传送对象的字节序列。
 

操做类

java.io.ObjectOutputStream
 
  表明对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把获得的字节序列写到一个目标输出流中。
 
java.io.ObjectInputStream
 
  表明对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
 

步骤

对象序列化包括以下步骤:
  1) 建立一个对象输出流,它能够包装一个其余类型的目标输出流,如文件输出流;
  2) 经过对象输出流的writeObject()方法写对象。
 
对象反序列化的步骤以下:
  1) 建立一个对象输入流,它能够包装一个其余类型的源输入流,如文件输入流;
  2) 经过对象输入流的readObject()方法读取对象。
 

前提

  只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类彻底由自身来控制序列化的行为,而仅实现Serializable接口的类能够 采用默认的序列化方式 。
 

serialVersionUID

  字面意思上是序列化的版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。
 
  主要是辅助序列化和反序列化,序列化和反序列化的时候,serialVersionUID必须一致,反序列化才会成功。固然,若是不显式的声明 serialVersionUID,系统在进行序列化的时候会根据当前类的特征进行哈希运算,最终获得一个版本号。
 
  序列化操做的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,若是一致就说明序列化类的版本与当前类版本是同样的,能够反序列化成功,不然失败。
 
  为了提升serialVersionUID的独立性和肯定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
 

自动生成

  IDEA下设置自动生成 serialVersionUID
 
  输入:serialVer 
 
这时候再来看看user对象,已经有黄色背景的警告了。
 
双击User对象,按 Alt + Enter 键,会弹出一个下面那样的小框,而后直接Enter确认便可。
 
接下来就会自动生成一个版本id
 
固然了,java也提供了方法让咱们来本身生成这个版本号。
public static void main(String[] args) { ObjectStreamClass osc = ObjectStreamClass.lookup(User.class); long id = osc.getSerialVersionUID(); System.out.println("用户对象版本号:" + id); }

 

运行结果与咱们自动生成的值同样(由于对象并无发生改变)
用户对象版本号:-6389397398684165955

 

生成原理

首先提早对象特征:类名,属性名,属性类型,方法等等。而后使用SHA摘要算法进行哈希运算。
感兴趣的能够去这个getSerialVersionUID()方法里面看看。
截图为证:
 

部分代码

序列化

ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { ObjectOutputStream oos = new ObjectOutputStream(bos); //准备写入用户对象
 oos.writeObject(user); oos.flush(); //序列化后获得的字符串
    String value = Hex.toHexString(bos.toByteArray()); bos.close(); } catch (IOException e) { e.printStackTrace(); }

 

反序列化

byte[] serialized = Hex.decode(value); ByteArrayInputStream bis = new ByteArrayInputStream(serialized); ObjectInputStream ois = new ObjectInputStream(bis); User user = (User) ois.readObject();

 

实例

不实现序列化接口
import org.bouncycastle.util.encoders.Hex; import java.io.*; /** * 序列化与反序列测试 * * @author wzm * @version 1.0.0 * @date 2020/1/27 18:21 **/
public class JavaSerializationTest { static class User { String name; String sex; int age; User(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } @Override public String toString() { return "User{" +
                    "name='" + name + '\'' +
                    ", sex='" + sex + '\'' +
                    ", age=" + age +
                    '}'; } } private static String save(User user) { String value = ""; try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { //准备写入用户对象
 oos.writeObject(user); oos.flush(); //序列化后获得的字符串
            value = Hex.toHexString(bos.toByteArray()); System.out.println("用户:" + value); } catch (IOException e) { e.printStackTrace(); System.out.println(e.getMessage()); } return value; } private static User get(String userStr) { try { byte[] serialized = Hex.decode(userStr); ByteArrayInputStream bis = new ByteArrayInputStream(serialized); ObjectInputStream ois = new ObjectInputStream(bis); return (User) ois.readObject(); } catch (Exception e) { e.printStackTrace(); System.out.println(e.getMessage()); throw new RuntimeException(e); } } public static void main(String[] args){ User user = new User("小明", "男", 20); String userStr = save(user); User user1 = get(userStr); System.out.println(user1); } } 

 

报错以下:
排查错误的缘由每每须要从根源着手。
java.io.NotSerializableException: fabric.edu.common.JavaSerializationTest$User fabric.edu.common.JavaSerializationTest$User at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at fabric.edu.common.JavaSerializationTest.save(JavaSerializationTest.java:42) at fabric.edu.common.JavaSerializationTest.main(JavaSerializationTest.java:69) java.io.EOFException null at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2681) at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3156) at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:862) at java.io.ObjectInputStream.<init>(ObjectInputStream.java:358) at fabric.edu.common.JavaSerializationTest.get(JavaSerializationTest.java:58) at fabric.edu.common.JavaSerializationTest.main(JavaSerializationTest.java:70) Exception in thread "main" java.lang.RuntimeException: java.io.EOFException at fabric.edu.common.JavaSerializationTest.get(JavaSerializationTest.java:63) at fabric.edu.common.JavaSerializationTest.main(JavaSerializationTest.java:70) Caused by: java.io.EOFException at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2681) at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3156) at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:862) at java.io.ObjectInputStream.<init>(ObjectInputStream.java:358) at fabric.edu.common.JavaSerializationTest.get(JavaSerializationTest.java:58) ... 1 more

 

而后去源码里面看看(ObjectOutputStream.java:1184)
private void writeObject0(Object obj, boolean unshared) throws IOException { boolean oldMode = bout.setBlockDataMode(false); depth++; try { // handle previously written and non-replaceable objects
        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; } // check for replacement object
        Object orig = obj; Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { // REMIND: skip this check for strings/arrays?
            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 object replaced, run through original checks a second time
        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; } } // remaining cases
        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); } }

 

实现序列化接口
从抛出的异常就能够肯定是没有实现序列化: 所以对这个用户对象稍加修改:

 

static class User implements Serializable { String name; String sex; int age; User(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } @Override public String toString() { return "User{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                '}'; } }

 

执行结果:
用户:aced00057372002c6661627269632e6564752e636f6d6d6f6e2e4a61766153657269616c697a6174696f6e546573742455736572e16c1276e8beef3b0200034900036167654c00046e616d657400124c6a6176612f6c616e672f537472696e673b4c000373657871007e0001787000000014740006e5b08fe6988e740003e794b7 User{name='小明', sex='男', age=20}
这样的话用户就能够正常序列化了,能够用任何可能的方式存储起来(持久化),而后再须要使用的时候再反序列化(恢复)出来。