java中的序列化和反序列化

介绍

序列化就是将对象转换成字节序列,反序列化就是将字节序列转换成对象。

使用

默认序列化和反序列化

public class Client {
  public static void main(String[] args) {
    User user = new User();
    user.setUserName("lisi");
    System.out.println(user);
    byte[] data = serialize(user);
    User newUser = deserialize(data, User.class);
    System.out.println(newUser);
    System.out.println(user == newUser);
  }

// 将一个对象序列化成字节序列
  private static byte[] serialize(Object obj) {
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(baos);) {
      oos.writeObject(obj);
      return baos.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }
// 将字节序列反序列化成指定类型的对象
  private static <T> T deserialize(byte[] data, Class<T> clazz) {
    try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
         ObjectInputStream bis = new ObjectInputStream(bais)) {
      Object obj = bis.readObject();
      return clazz.cast(obj);
    } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
    }
    return null;
  }

  @Setter
  @Getter
  @ToString
  public static class User implements Serializable {
    private String userName;
  }
}

一个类要序列化必须实现Serializable接口,序列化字段为非static非transient修饰,

从结果可以看到反序列化出的对象是一个新的对象。

自定义序列化和反序列化

public class Client {
  public static void main(String[] args) {
    User user = new User();
    user.setUserName("lisi");
    System.out.println(user);
    byte[] data = serialize(user);
    User newUser = deserialize(data, User.class);
    System.out.println(newUser);
    System.out.println(user == newUser);
  }

  private static byte[] serialize(Object obj) {
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(baos);) {
      oos.writeObject(obj);
      return baos.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }

  private static <T> T deserialize(byte[] data, Class<T> clazz) {
    try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
         ObjectInputStream bis = new ObjectInputStream(bais)) {
      Object obj = bis.readObject();
      return clazz.cast(obj);
    } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
    }
    return null;
  }

  @Setter
  @Getter
  @ToString
  public static class User implements Serializable {
    private String userName;
// 自定义序列化过程
    private void writeObject(ObjectOutputStream oos) throws IOException {
// 默认序列化逻辑
      oos.defaultWriteObject();
// 将用户名长度序列化
      oos.writeInt(userName.length());
    }
// 自定义反序列化过程
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// 默认反序列化逻辑
      ois.defaultReadObject();
// 读取用户名长度
      System.out.println(ois.readInt());
    }
  }
}

自定义序列化必须添加writeObject方法,必须为private非static,参数类型为ObjectOutputStream,返回类型为void。
自定义反序列化必须添加readObject方法,必须为private非static,参数类型为ObjectInputStream,返回类型为void。


HashMap就是通过自定义来实现序列化和反序列化的。

/**
     * Save the state of the <tt>HashMap</tt> instance to a stream (i.e.,
     * serialize it).
     *
     * @serialData The <i>capacity</i> of the HashMap (the length of the
     *             bucket array) is emitted (int), followed by the
     *             <i>size</i> (an int, the number of key-value
     *             mappings), followed by the key (Object) and value (Object)
     *             for each key-value mapping.  The key-value mappings are
     *             emitted in no particular order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws IOException {
        int buckets = capacity();
        // Write out the threshold, loadfactor, and any hidden stuff
        s.defaultWriteObject();
        s.writeInt(buckets);
        s.writeInt(size);
        internalWriteEntries(s);
    }

    /**
     * Reconstitute the {@code HashMap} instance from a stream (i.e.,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        reinitialize();
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        s.readInt();                // Read and ignore number of buckets
        int mappings = s.readInt(); // Read number of mappings (size)
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                             mappings);
        else if (mappings > 0) { // (if zero, use defaults)
            // Size the table using given load factor only if within
            // range of 0.25...4.0
            float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
            float fc = (float)mappings / lf + 1.0f;
            int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                       DEFAULT_INITIAL_CAPACITY :
                       (fc >= MAXIMUM_CAPACITY) ?
                       MAXIMUM_CAPACITY :
                       tableSizeFor((int)fc));
            float ft = (float)cap * lf;
            threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                         (int)ft : Integer.MAX_VALUE);

            // Check Map.Entry[].class since it's the nearest public type to
            // what we're actually creating.
            SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
            table = tab;

            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }

更彻底的自定义

public class Client {
  public static void main(String[] args) {
    User user = new User();
    user.setUserName("lisi");
    System.out.println(user);
    byte[] data = serialize(user);
    User newUser = deserialize(data, User.class);
    System.out.println(newUser);
    System.out.println(user == newUser);
  }

  private static byte[] serialize(Object obj) {
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(baos);) {
      oos.writeObject(obj);
      return baos.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }

  private static <T> T deserialize(byte[] data, Class<T> clazz) {
    try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
         ObjectInputStream bis = new ObjectInputStream(bais)) {
      Object obj = bis.readObject();
      return clazz.cast(obj);
    } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
    }
    return null;
  }

  @Setter
  @Getter
  @ToString
  public static class User implements Serializable {
    private String userName;
// 替换序列化对象
    private Object writeReplace() {
      User user = new User();
      user.setUserName(userName);
      return user;
    }

// 处理反序列出的对象
    private Object readResolve() {
      userName = "lisi1";
      return this;
    }
  }
}

writeReplace方法会在真正的序列化执行之前执行,会替换我们的待序列化对象,readResolve会在反序列化执行之后执行,就可以对结果做一些后置处理。通过readResolve我们可以进行单例的保护性恢复,直接返回单例实例。

java11中的不可变集合序列化和反序列化就是通过这两个方法实现的。

序列化Id

/** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

必须为static且final的字段。

如果我们没有声明这个字段,序列化时会根据class,method信息综合计算出一个。

posted @ 2020-07-25 12:42  strongmore  阅读(247)  评论(0编辑  收藏  举报