Java 序列流:Java 对象的序列化和反序列化详解

1.概念

序列化是指将一个对象转换为一个字节序列(包含对象的数据对象的类型对象中存储的属性等信息),它可以将多个InputStream对象串联起来,使得它们可以被当作一个单一的输入流来处理,以便在网络上传输或保存到文件中,或者在程序之间传递。在 Java 中,序列化通过实现 java.io.Serializable 接口来实现,只有实现了 Serializable 接口open in new window的对象才能被序列化。

当你序列化对象时,你把它包装成一个特殊文件,可以保存、传输或存储。反序列化则是打开这个文件,读取序列化的数据,然后将其还原为对象,以便在程序中使用。

序列流的基本特性包括:

  • 懒加载:序列流的操作是懒加载,意味着它们不会立即执行,而是在需要结果时才执行。
  • 不可变:一旦创建了序列流,就不能修改它。如果需要修改,必须创建一个新的流。
  • 并行处理:序列流支持并行处理,可以利用多核处理器提高性能。

2.ObjectOutputStream

java.io.ObjectOutputStream 继承自 OutputStream 类,因此可以将序列化后的字节序列写入到文件、网络等输出流中。

来看 ObjectOutputStream 的构造方法:
ObjectOutputStream(OutputStream out)

该构造方法接收一个 OutputStream 对象作为参数,用于将序列化后的字节序列输出到指定的输出流中。例如:

FileOutputStream fos = new FileOutputStream("file.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);

 使用示例如下:

public class Employee implements Serializable {
    public String name;
    public String address;
    public transient int age; // transient瞬态修饰成员,不会被序列化
}

接下来,来聊聊 writeObject (Object obj) 方法,该方法是 ObjectOutputStream 类中用于将对象序列化成字节序列并输出到输出流中的方法,可以处理对象之间的引用关系、继承关系、静态字段和 transient 字段。

public class ObjectOutputStreamDemo {
    public static void main(String[] args) {
        Person person = new Person("沉默王二", 20);
        try {
            FileOutputStream fos = new FileOutputStream("logs/person.dat");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.close();
        } catch (IOException 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;
    }
}

一个对象要想序列化,必须满足两个条件:

3.ObjectInputStream 

ObjectInputStream 可以读取 ObjectOutputStream 写入的字节流,并将其反序列化为相应的对象(包含对象的数据对象的类型对象中存储的属性等信息)。

主要功能就是将ObjectOutputStream序列化的字节流转换回java对象。

使用步骤:

1、创建对象输入流:

FileInputStream fileIn =new FileInputStream("example.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);

2、读取对象:使用readObject的方法从流中读取对象。注意,读取的对象需要强制类型转换到正确的类型。

ObjectInputStream in =new ObjectInputStream(fileIn);
Object obj =in.readObject();
Employee emp =(Employee) obj;

整体演示:

String filename = "logs/person.dat"; // 待反序列化的文件名
try (FileInputStream fileIn = new FileInputStream(filename);
     ObjectInputStream in = new ObjectInputStream(fileIn)) {
     // 从指定的文件输入流中读取对象并反序列化
     Object obj = in.readObject();
     // 将反序列化后的对象强制转换为指定类型
     Person p = (Person) obj;
     // 打印反序列化后的对象信息
     System.out.println("Deserialized Object: " + p);
} catch (IOException | ClassNotFoundException e) {
     e.printStackTrace();
}

我们首先指定了待反序列化的文件名(前面通过 ObjectOutputStream 序列化后的文件),然后创建了一个 FileInputStream 对象和一个 ObjectInputStream 对象。接着我们调用 ObjectInputStream 的 readObject 方法来读取指定文件中的对象,并将其强制转换为 Person 类型。最后我们打印了反序列化后的对象信息。

4.transient

transient关键字是一个变量修饰符,用于声明一个字段不会被序列化。当一个对象被序列化时,默认状态下,对象的所有非静态和非瞬态字段都会被保存在字节流中。然而,如果你不希望某个字段被序列化,可以将其声明为transient。

 关键用途:

  • 保护敏感数据:如果你有一个包含敏感信息到字段(比如密码,信用卡等等)。
  • 避免序列化不必要的字段:有些字段可能在序列化时没有意义,或者不应该被保留在对象状态中。
public class TransientTest {
    public static void main(String[] args) {
        
        User user = new User();
        user.setUsername("我是大帅比");
        user.setPasswd("123456");
        
        System.out.println("read before Serializable: ");
        System.out.println("username: " + user.getUsername());
        System.err.println("password: " + user.getPasswd());
        
        try {
            ObjectOutputStream os = new ObjectOutputStream(
                    new FileOutputStream("user.txt"));
            os.writeObject(user); // 将User对象写进文件
            os.flush();
            os.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream(
                    "user.txt"));
            user = (User) is.readObject(); // 从流中读取User的数据
            is.close();
            
            System.out.println("\nread after Serializable: ");
            System.out.println("username: " + user.getUsername());
            System.err.println("password: " + user.getPasswd());
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class User implements Serializable {
    private static final long serialVersionUID = 8294180014912103005L;  
    
    private String username;
    private transient String passwd;
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public String getPasswd() {
        return passwd;
    }
    
    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

}

运行结果:

read before Serializable:
username: 沉默王二
password: 123456 
read after Serializable:
username: 沉默王二
password: null

transitent的使用小结:

一旦字段被 transient 修饰,成员变量将不再是对象持久化的一部分,该变量的值在序列化后无法访问。

transient 关键字只能修饰字段,而不能修饰方法和类。

被 transient 关键字修饰的字段不能被序列化,一个静态变量(static关键字open in new window修饰)不管是否被 transient 修饰,均不能被序列化。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值