序列化的过程

本文探讨了序列化的过程,其在网络和跨进程通信中的应用。介绍了多种序列化方案,如JSON、XML、Protobuf、Java的Serializable和Android的Parcelable。详细讲解了Java的Serializable接口,包括serialVersionUID的作用,如何处理不序列化成员变量,以及序列化时可能出现的问题和自定义序列化过程。此外,还分析了序列化与反序列化的特性,如对象的深拷贝关系以及Bundle的内存优化。

序列化:

将数据结构或对象转换成二进制的过程。

序列化的用途: 网络上,跨进程

序列化方案:json xml protobuf serializable(java) percelable(Android独有)....

持久化:

把数据结构或对象存储起来 硬盘

如何选择合理的序列化方案。

Serializable学习:

//里面没有任何方法,就是一个标志。

public interface Serializable { }
public interface Externalizable extends java.io.Serializable {

    void writeExternal(ObjectOutput out) throws IOException;

    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

}

案例一

   
    /*
      如果没有实现Serializable,执行就会报错
      Caused by: java.io.NotSerializableException: 
                 com.android.serializable.SerializableTest$User
     */
    static class User  { //定义非基本数据类型的对象,不实现序列化
        private String mName;
        private int mAge;
        public User(String name, int age) {
            mName = name;
            mAge = age;
        }
        @Override
        public String toString() {
            return "User{" +
                    "mName='" + mName + '\'' +
                    ", mAge=" + mAge +
                    '}';
        }
    }

//对它进行写出操作,path为磁盘路径
outputStream = new ObjectOutputStream(new FileOutputStream(path));
outputStream.writeObject(object);

//对它进行读入操作
inputStream = new ObjectInputStream(new FileInputStream(path));
 return inputStream.readObject();

在上面的案例可以看出当中就算user实现了Serializable接口,也要自己使用对象的IO流(ObjectOutput,ObjectInput )辅助实现序列化。

  static class User implements Externalizable {
        private String mName;
        private int mAge;

        public User(){} //使用Externalizable 必要要有无参构造函数

        public User(String name, int age) {
            mName = name;
            mAge = age;
        }
        @Override
        public String toString() {
            return "User{" +
                    "mName='" + mName + '\'' +
                    ", mAge=" + mAge +
                    '}';
        }
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            //方便指定哪些成员变量需要序列化,哪些成员变量不需要序列化
            out.writeObject(mName);
            out.writeInt(mAge);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            mName = (String) in.readObject();
            mAge = in.readInt();
        }
    }


 private static void externalizableTest(){
        User user = new User("fw123",18);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = null;
        byte[] userDate = null;
        try {
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(user);
            userDate = byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }

        ObjectInputStream objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(new ByteArrayInputStream(userDate));
            User object = (User) objectInputStream.readObject();
            System.out.println("fw123 object: " + object);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }



上面必须要注意的是:使用Externalizable 必须要有无参构造函数。

问题一 serialVersionUID的作用

上面的两个案例都没有定义常量,可是可以进行正常完成的:

private static final long serialVersionUID = 7579060052569229166L;

既然没有serialVersionUID也没报错,那这个有什么作用吗?

1.作用将对象A进行序列化,2 .修改A,3.反序列化A。 如果有了serialVersionUID将不会报错。

问题二  序列化时,你希望某些成员变量不需要序列化,你如何实现昵?

就是将这个变量定义为静态或者瞬态(trasient)变量。反序列化以后,被这个修饰的变量打印出来为null。我自己测试静态的变量是可以反序列化,这里有问题???

//静态成员变量属于类不属于对象,所以不会参与序列化(对象序列化保存的是对象的状态,也
就是它的成员变量,因此序列化不会关注静态变量)

问题三, 如果类中的一个成员未实现可序列化接口,会发生什么?

运行时会报NotSerializableException异常。

问题四 如何自定义序列化过程,

在类中写下面两个函数就可以了。 

 private void writeObject(ObjectOutputStream out) throws IOException{}

 private void readObject(ObjectInputStream in) throws IOException{}

如果父类可以序列化,那么它的子类也是可以序列化的。如果子类不想序列化,就可以复写上面的方法抛出异常就可以了。

Serializable源码分析:

首先:
ObjectOutputStream.java#writeObject(Object obj)
--->writeObject0(Object obj, boolean unshared)
--->} else if (obj instanceof Serializable) {
   writeOrdinaryObject(obj, desc, unshared);}
-->
if (desc.isExternalizable() && !desc.isProxy()) {
     writeExternalData((Externalizable) obj);//是不是实现Externalizable
 } else {
   writeSerialData(obj, desc);
 }
--writeString((String) obj, unshared);//一层一层往下走,直到最后的对象是基本数据类型


在writeSerialData中:
 
    private void writeSerialData(Object obj, ObjectStreamClass desc)
         
    {
      
            if (slotDesc.hasWriteObjectMethod()) {//如果有自定义WriteObject函数,将走这里
              
             slotDesc.invokeWriteObject(obj, this);

            } else {
                defaultWriteFields(obj, slotDesc);// 没有自定义WriteObject,将走这里
            }
        }
    }

在使用为什么需要无参构造函数:
在ObjectInputStream#readObject()--》readObject0-》readOrdinaryObject(unshared)
---》
try {
    obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
     throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
}

1.反序列化后的对象,不需要调用构造函数重新构造。

2.序列化前后的对象是地址是不一样的,是深拷贝关系,但是枚举类型是一样。

3.Bundle的内部有变量mMap(ArrayMap<String, Object>)这个比Map的存储大小有优化,

4.

<think>我们正在讨论序列化过程序列化是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。 序列化过程通常包括以下步骤: 1. 创建输出流:打开一个输出流(例如文件输出流、网络输出流等)以写入序列化数据。 2. 创建序列化对象:使用特定的序列化工具(如Java中的ObjectOutputStream)来将对象写入输出流。 3. 写入对象:调用序列化工具的方法将对象写入流中。这个过程通常是递归的,因为对象可能包含其他对象的引用。 4. 处理引用:序列化机制需要处理对象的引用,确保每个对象只被序列化一次,并且相同的引用在反序列化时能够恢复为相同的引用关系。 5. 写入元数据:序列化过程中会写入关于对象类的元数据,包括类名、字段名和类型等。 6. 关闭流:完成序列化后关闭输出流。 反序列化过程则是相反的操作: 1. 打开输入流:从存储介质(如文件、网络等)读取序列化的字节流。 2. 创建反序列化对象:使用反序列化工具(如Java中的ObjectInputStream)从输入流中读取对象。 3. 读取对象:从流中读取对象的描述,包括其类信息和字段值。 4. 创建对象实例:根据类信息创建新的对象实例,并填充字段值。 5. 解析引用:恢复对象间的引用关系。 6. 关闭流:完成反序列化后关闭输入流。 序列化流程图可以简单描述为: 开始 -> 打开输出流 -> 创建序列化工具 -> 写入对象(包括处理引用和写入元数据)-> 关闭流 -> 结束 由于文本环境,我将用文字描述流程图,并使用Mermaid语法来绘制流程图(但请注意,在纯文本中无法显示,但可以生成代码供支持Mermaid的地方使用)。不过,由于本环境不支持图形,我将用文字描述,并给出Mermaid代码供用户在其他地方渲染。 序列化流程图(Mermaid代码): ```mermaid graph TD A[开始] --> B[打开输出流] B --> C[创建序列化工具] C --> D[写入对象] D --> E[处理引用] E --> F[写入元数据] F --> G[关闭流] G --> H[结束] ``` 详细步骤说明: 1. 打开输出流:选择序列化数据的目标,如文件、内存缓冲区或网络套接字,并打开相应的输出流。 2. 创建序列化工具:实例化一个序列化工具对象(如ObjectOutputStream),将其与输出流关联。 3. 写入对象:调用序列化工具的writeObject()方法,传入要序列化的对象。 - 序列化工具会检查对象是否实现了Serializable接口(在Java中),如果未实现则抛出异常。 - 序列化工具会递归地序列化对象的所有非transient和非static字段。 4. 处理引用:序列化机制会为每个对象分配一个唯一的序列号(serial number),如果同一个对象被多次引用,则后续引用只写入这个序列号,从而避免重复序列化和保持引用关系。 5. 写入元数据:对于每个对象的类,会写入类描述(包括类名、序列化版本ID、字段描述等)。这样在反序列化时,可以根据这些信息重建类结构。 6. 关闭流:确保所有数据都被写入输出流,并释放资源。 序列化过程中需要注意的问题: - 版本控制:如果类的定义发生变化(如字段增删),反序列化可能会失败。通常通过指定serialVersionUID来显式控制版本。 - 安全性:序列化数据可能被篡改,因此对于敏感数据应加密或避免序列化。 - 性能:序列化过程可能比较耗时,特别是在处理大型对象图时。 下面是一个简单的Java序列化示例: ```java import java.io.*; public class SerializationDemo { public static void main(String[] args) { // 创建对象 Person person = new Person("Alice", 30); // 序列化 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) { oos.writeObject(person); System.out.println("序列化完成"); } catch (IOException e) { e.printStackTrace(); } // 反序列化 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) { Person deserializedPerson = (Person) ois.readObject(); System.out.println("反序列化: " + deserializedPerson); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + '}'; } } ``` 在这个示例中,我们定义了一个Person类,它实现了Serializable接口。我们创建了一个Person对象,然后将其序列化到文件"person.ser"中,接着再从这个文件中反序列化回对象。 序列化技术广泛应用于分布式系统(如远程方法调用RMI)、持久化存储(如将对象存储到数据库或文件)以及网络通信(如HTTP会话)等场景。 相关问题: 1. 序列化过程中如何处理循环引用? 2. 如何自定义序列化过程(例如,使用transient关键字或自定义writeObject/readObject方法)? 3. 不同编程语言(如Python、C#)的序列化机制与Java有何异同? 4. 序列化数据的安全性问题有哪些?如何防范? 5. 序列化与反序列化过程中可能抛出哪些异常?如何正确处理?</think>### 序列化过程详解 #### **序列化流程图** ```mermaid graph TD A[开始] --> B[创建对象实例] B --> C[打开输出流<br>文件/网络/内存] C --> D[创建序列化工具<br>如ObjectOutputStream] D --> E[写入对象数据] E --> F[递归处理引用对象] F --> G[写入元数据<br>类名/字段类型/版本号] G --> H[关闭输出流] H --> I[生成序列化字节流] I --> J[结束] ``` #### **详细步骤解析** 1. **创建对象实例** 初始化需要序列化的对象,包含其属性和关联对象(如嵌套对象、集合等)。 2. **打开输出流** 选择目标存储介质: - 文件流(`FileOutputStream`) - 网络套接字(`SocketOutputStream`) - 内存缓冲区(`ByteArrayOutputStream`) 3. **初始化序列化工具** 使用语言特定的序列化器(如Java的`ObjectOutputStream`): ```java ObjectOutputStream oos = new ObjectOutputStream(outputStream); ``` 4. **写入对象数据** 按顺序处理对象内容: - **基本类型**:直接转换为字节(如`int→4字节`) - **引用类型**:递归序列化关联对象 - **特殊标记**: - `transient`字段跳过 - 写入`NULL`标记处理空引用 5. **处理引用关系** 解决循环引用问题(如A引用B,B引用A): - 为每个对象分配唯一ID - 重复引用时写入ID而非重新序列化[^1] 6. **写入元数据** 在字节流头部嵌入描述信息: - 完整类名(如`com.example.User`) - 字段类型签名(`SerialVersionUID`) - 序列化协议版本号 7. **生成字节流** 最终输出结构示例: ```plaintext [元数据头] [对象A数据] [对象B数据] [引用ID映射表] ``` 8. **关闭资源** 确保数据完全刷入目标介质: ```java oos.close(); ``` #### **反序列化关键过程** 1. 读取元数据重建类结构 2. 按写入顺序解析字段值 3. 通过引用ID恢复对象关联 4. 调用`readObject()`自定义逻辑(如存在) #### **技术注意事项** - **版本兼容**:修改类定义需保持`SerialVersionUID`一致 - **安全风险**:避免反序列化不可信数据(可能触发恶意代码) - **性能优化**:二进制协议(如Protobuf)比XML/JSON更高效 - **跨语言支持**:使用中立格式(如Apache Avro, Thrift) > **示例:Java序列化代码** > ```java > // 序列化 > try (ObjectOutputStream oos = new ObjectOutputStream( > new FileOutputStream("data.bin"))) { > oos.writeObject(user); // User需实现Serializable > } > > // 反序列化 > try (ObjectInputStream ois = new ObjectInputStream( > new FileInputStream("data.bin"))) { > User user = (User) ois.readObject(); > } > ``` ### 相关问题 1. 序列化过程中如何处理`transient`字段和`static`字段? 2. 不同序列化协议(JSON/XML/Protobuf)的性能对比如何? 3. 反序列化时如何解决类版本不兼容问题? 4. 为什么序列化可能引发安全漏洞?如何防范? 5. 分布式系统中序列化技术选型要考虑哪些因素? [^1]: 对象引用处理机制可避免无限递归和重复数据,提升存储效率。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值