Java IO 和序列化

一、序列化实现方式之实现Serializable接口

1.Serializable接口的作用

serializable接口就是一个标识,实现了serializable接口的类就相当于打上了一个tag,有这个tag的类就可以被序列化,如果没有实现Serializable接口在被序列化的时候就会抛出NotSerializableException异常:

ObjectOutPutStream源码中的writeObject0方法:

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
           ...

            // 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);
        }
    }

在序列化对象的时候,会获取对象中所有的属性,然后循环把对象的属性也一同序列化,如果对象的属性中没有实现Serializable属性也会抛出 NotSerializableException异常。

2. 实现Serializable接口后,具体是如何序列化的

序列化本质是把内存中的对象通过IO流的方式保存在磁盘中,所以做序列化的工作的是ObjectOutPutStream,而ObjectOutPutStream把对象序列化的能力是通过实现ObjectOutPut接口。

3.ObjectOutPutStream之ObjectStreamClass

ObjectStreamClass用来描述被序列化的对象,例如对象是否是数组类型,是否是枚举类型,对象是否实现了Serializable接口、是否有writeObject()方法、是否有readObject()方法。

4.序列化之serialVersionUID

虚拟机是否允许反序列化,不仅取决于类路径(全类名必须一致)和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致
SerialVersionUID 是一个标识符,它通常使用对象的哈希码,序列化时会标记在对象上。
可以通过 Java 中 serialver 工具找到该对象的 serialVersionUID,命令格式:serialver classname。
Java 序列化机制通过判断类的 serialVersionUID 验证版本的一致性。在进行反序列化时,Java 虚拟机会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是 InvalidCastException。
serialVersionUID 有两种显示的生成方式。
默认为 1L,例如 private static final long serialVersionUID = 1L;
根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段,随机生成的序列化 ID,通过改变序列化 ID 可以用来限制某些用户的使用。
当实现 java.io.Serializable 接口的类没有显式地定义一个 serialVersionUID 变量时候,Java 序列化机制会根据编译的 Class 自动生成一个 serialVersionUID 作为序列化版本比较使用。这种情况下,如果 Class 文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID 也不会变化。

5.Transient关键字

(1).Transient关键字作用

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。
在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

(2).Transient 与 Static

静态变量不是对象状态的一部分,因此它不参与序列化。
将静态变量声明为 transient 变量没有用处。

(3).Final 与 Transient

final 变量将直接通过值参与序列化,将 final 变量声明为 transient 变量不会产生任何影响。

6.writeObject和readObject方法

如果想自己控制序列化的,可以实现这两个方法:

    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        aa = (String) s.readObject();
        cc = s.readInt();
    }

    private void writeObject(ObjectOutputStream s) throws IOException {
        s.writeObject(aa);
        s.writeInt(cc);
    }

注意一旦实现了这两个方法,序列化就会完全按照这两个方法去序列化,不会再走默认的序列化。

源码:

   private void writeSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        for (int i = 0; i < slots.length; i++) {
            ObjectStreamClass slotDesc = slots[i].desc;
            if (slotDesc.hasWriteObjectMethod()) {
                PutFieldImpl oldPut = curPut;
                curPut = null;
                SerialCallbackContext oldContext = curContext;

                if (extendedDebugInfo) {
                    debugInfoStack.push(
                        "custom writeObject data (class \"" +
                        slotDesc.getName() + "\")");
                }
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);
                    bout.setBlockDataMode(true);
                    slotDesc.invokeWriteObject(obj, this);
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }

                curPut = oldPut;
            } else {
                defaultWriteFields(obj, slotDesc);
            }
        }
    }

 可以看到这个是通过slotDesc.hasWriteObjectMethod()来判断对象中是否有writeObject方法,slotDesc是ObjectStreamClass用来描述序列化对象的类,如果有的话就通过反射调用slotDesc.invokeWriteObject(obj, this);writeObject,readObject方法逻辑也是一样的。

7.如果子类实现了Serializable,父类没有实现Serializable接口

父类的字段不会进行序列化,当反序列化的时候,父类中的字段会是默认值,如果想让没有实现Serializable接口的父类的字段也能够序列化,可以使用readObject和writeObject方法。

8.如果父类实现了Serializable,那子类没有显示的实现Serializable接口

父类实现了Serializable,那子类会默认实现Serializable接口

9.如果防止序列化破坏单例模式

在单例的类中加入一个readResolve方法:

import java.io.Serializable;
 
public class Singleton implements Serializable {
 
    private volatile static Singleton singleton;
 
    private Singleton() {
    }
 
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
 
    private Object readResolve() {
        return getSingleton();
    }
}
// print true

readResolve方法:

    private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        ...

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

是序列化反射中最后一个执行的方法,所以在这里做的操作会覆盖之前的操作。 

二、序列化之Externalizable

1.注意点

(1).序列化的对象必须有默认的空构造方法

在反序列化的时候有一个判断:

 private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        ...
        return obj;
    }


    void checkDeserialize() throws InvalidClassException {
        if (deserializeEx != null) {
            throw deserializeEx.newInvalidClassException();
        }
    }

readClassDesc获取类的序列化描述对象ObjectStreamClass,然后通过ObjectStreamClass去检查是否有异常。

使用 Externalizable 进行序列化读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,再将被保存对象的字段的值分别填充到新对象中。所以,实现 Externalizable 接口的类必须 提供一个 public 的无参构造器

(2).writeExternal和readExternal

这两个方法是Externalizable中的两个方法,writeExternal管理哪些属性从内存中写入磁盘,readExternal管理从磁盘中读入哪些属性,注意:读入的顺序必须和写出的顺序是一致的,可以少读,但是不能错读和多读,很符合文件流的读入写出规律

(3).transient关键字对于Externalizable不起作用

如果想让哪个字段不参加序列化,在writeExternal和readExternal的方法中不加入这个字段即可。

三、安卓中独有的序列化Parcelable

https://blog.csdn.net/jdsjlzx/article/details/109064067

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的IO序列是指将一个Java对象转换为字节流以便存储或传输,也可以将字节流转换回Java对象。序列可以用于网络通信,分布式系统、数据库存储等场景。 Java中的序列是通过实现Serializable接口来实现的,在这个接口中没有定义任何方法,仅仅起到了一个标识作用。实现了Serializable接口的对象可以被序列为字节流,然后再通过反序列操作将其转换回对象。 Java中的序列可以使用ObjectOutputStream和ObjectInputStream类来实现。ObjectOutputStream可以将一个对象序列为字节流并输出到文件或网络流中,而ObjectInputStream可以将字节流反序列为对象。 下面是一个简单的Java序列示例: ```java import java.io.*; public class SerializationDemo { public static void main(String[] args) { // 创建一个Person对象 Person person = new Person("Tom", 20); // 将Person对象序列到文件中 try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"))) { out.writeObject(person); } catch (IOException e) { e.printStackTrace(); } // 从文件中读取Person对象 try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"))) { Person p = (Person) in.readObject(); System.out.println(p); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } class Person implements Serializable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } ``` 在上面的示例中,我们创建了一个Person类,并实现了Serializable接口。然后将一个Person对象序列到文件中,并从文件中读取并反序列为Person对象。最后输出反序列得到的Person对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值