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}
这样的话用户就可以正常序列化了,可以用任何可能的方式存储起来(持久化),然后再需要使用的时候再反序列化(恢复)出来。
 
posted @ 2020-01-27 21:21  itwetouch  阅读(465)  评论(0编辑  收藏  举报