zl程序教程

您现在的位置是:首页 >  Java

当前栏目

聊一聊Serializable和Externalizable

2023-02-18 16:46:03 时间

本篇文章我们来聊一聊Java中的Serializable和Externalizable。

大索:小码、没人理,你们知道Java中的Serializable(可序列化)和Externalizable(可外部化)吗? 小码:Serializable我知道,Externalizable还真不知道。 大索:那你给说说序列化是什么呗 小码:序列化是将对象的状态转换为字节流。如果我们想将一个对象存储在一个文件中,就需要将其转换为字节。然后可以通过称为反序列化的过程将这些字节恢复为原始形式的对象,这是对象持久化的方式。 没人理:小码说的对呀~ 大索:嗯,是这样的。我们都知道在Java中,序列化过程通过实现Serializable接口自动发生,我们无法控制幕后发生的整个过程。如果我们想使用自定义加密来操作要持久化的字节,或者对序列化进行一些控制,那怎么办呢?

小码:Externalizable? 大索:孺子可教也。通过实现Externalizable接口,给我们提供了实现序列化逻辑的机会。序列化是将对象状态自动转换为字节流的机制,Externalizable也是如此,但在将对象状态转换为字节流时给予了程序员控制权。 小码:那您再给我们详细说说Serializable接口和Externalizable接口 大索:好咧

在 Java 中,序列化是通过java.io.Serializable接口实现的,需要有序列化功能的类在其设计期间要实现此接口。因此,可以利用序列化机制的工具保存和恢复此类的实例。Serializable接口没有方法或字段,仅用于识别可序列化的语义。任何实现此接口的类都表明该类的对象是可序列化的,从此类派生的任何子类也继承可序列化属性。但是,声明为transientstatic的成员不可序列化,因此不会持久化。实现了Serializable接口的类,可以通过声明一个名为serialVersionUID字段来显式声明自己的serialVersionUID,该字段必须是staticfinallong类型的。根据Java(TM)对象序列化规范(Object Serialization Specification),如果没有声明serialVersionUID字段,则序列化运行时将根据类的各个方面计算该类的默认 serialVersionUID 值。

Externalizable接口提供对序列化过程的控制,它实现了Serializable接口。与Serializable接口类似,实现Externalizable接口的类被标记为持久化,除了那些声明为transientstatic的成员。与没有成员的Serializable接口相比,Externalizable接口提供了两个方法,readExternal()和writeExternal(),我们可以在其中编写自己的序列化规则集。

小码:光说不练假把式,光练不说傻把式。举个例子说明一下 没人理:小码说的对呀~ 大索:下面就使用Externalizable接口,对自定义的PersonalInformation类的密码进行加密测试。

public class PersonalInformation implements Externalizable {
    private int id;
    private String password;
    private transient String secret = "~!@#$%^&*()";
    private static SecretKeySpec secretKey;
    private static byte[] key;

    public PersonalInformation() {
        id = 0;
        password = "";
    }

    public PersonalInformation(int id, String pass) {
        this.id = id;
        this.password = pass;
    }

    private static void createKey(String k) {
        MessageDigest md = null;
        try {
            key = k.getBytes(StandardCharsets.UTF_8);
            md = MessageDigest.getInstance("SHA-1");
            secretKey = new SecretKeySpec(
                    Arrays.copyOf(md.digest(key), 16), "AES");
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }
    }

    private static String encrypt(String str, String secret) {
        String retStr = null;
        try {
            createKey(secret);
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            retStr = Base64.getEncoder()
                    .encodeToString(cipher
                            .doFinal(str.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return retStr;
    }

    private static String decrypt(String str, String secret) {
        String retStr = null;
        try {
            createKey(secret);
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            retStr = new String(cipher.doFinal(Base64
                    .getDecoder().decode(str)));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return retStr;
    }

    @Override
    public String toString() {
        return "PersonalInformation{" +
                "id=" + id +
                ", password=" + password +
                "}";
    }

    @Override
    public void writeExternal(ObjectOutput out) {
        try {
            out.write(id);
            password = encrypt(password, secret);
            out.writeUTF(password);
            System.out.println("加密后:");
            System.out.println("ID: " + id + " PASSWORD:" + password);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void readExternal(ObjectInput in) {
        try {
            id = in.read();
            password = decrypt(in.readUTF(), secret);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

测试类:

public class ExternalizableTest {
    public static void main(String[] args) {
        try {
            String file = "classified.dat";
            PersonalInformation pi = new PersonalInformation(101, "abc_123");
            System.out.println("序列化之前数据");
            System.out.println(pi);
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
            os.writeObject(pi);
            os.close();

            ObjectInputStream is = new ObjectInputStream(new FileInputStream(file));
            pi = (PersonalInformation) is.readObject();
            System.out.println("序列化之后数据");
            System.out.println(pi.toString());
            is.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

测试结果:

序列化之前数据
PersonalInformation{id=101, password=abc_123}
加密后:
ID: 101 PASSWORD:C0r5xUs3by6IAwgBVzqk2g==
序列化之后数据
PersonalInformation{id=101, password=abc_123}

小码:能简单的明白了。那Serializable和Externalizable接口有什么区别呢? 大索:它们两个区别如下:

  • 实现Serializable接口的类,对象持久化会自动启动,无需开发者的任何干预。实现Externalizable接口的类,需要提供序列化逻辑。
  • 由于在实现Serializable接口的情况下,序列化过程是自动的,因此该过程的性能没有改进的余地。Externalizable接口提供对过程的完全控制,并可用于提高性能。
  • Serializable和Externalizable接口都用于对象序列化。Serializable接口没有成员,而Externalizable接口包含两个方法:readExternal()和writeExternal()。

没人理:大索说的对呀~ 大索:最后总结一下吧。

「总结:」

序列化和反序列化过程的主要用途是在没有程序员直接干预的情况下启用保存和恢复对象状态功能。这是通过Serializable接口自动完成的。但是,在某些情况下,我们希望控制这些过程,例如在对象序列化期间吸收压缩或加密设施。这只能通过Enternalizable接口实现。