Java IO(七、对象流)

Java IO. 对象流

对象流: ObjectInputStream和OjbectOutputSteam。是一种用于读取和储存基本数据类型数据或对象的处理流。它可以把Java中的对象写出到数据源中,也可以从数据源中读取数据还原为java对象。

1、对象序列化与反序列化

对象序列化的目的是将对象保存到数据源(如磁盘)中,或者在网络中直接传输对象。对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流(不论从磁盘还是网络获取的) ,都可以将这种二进制流恢复成原来的 Java 对象。

序列化: 用ObjectOutputStream类保存基本类型数据或对象的机制。
反序列化: 用ObjectInputStream类读取基本类型数据或对象的机制。

序列化的好处是可将任何实现了Serializable接口的对象转化为字节数据,使其在存储和传输时能够被还原。

static和transient修饰的成员变量不能被ObjectOutputStream和ObjectInputStream序列化。
一个类如果是可序列化的,这个类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常

  • Serializable
  • Externalizable

并且必须让其对象所属的类及其属性也是可序列化的。(这个类的引用类型属性必须是可序列化的,否则拥有该类型的Field 的类也不能序列化)

例如对一个Person类进行序列化与反序列化:

import java.io.*;

public class ObjectStreamTest {
    private static 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;
        }
    }

    //将对象写出到数据源
    private static void writeObject() {
        ObjectOutputStream oos = null;
        try {
            //1.创造流
            oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
            //2.造对象
            oos.writeObject(new Person("张三", 25));
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //3.关闭流
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //从数据源读取对象
    private static void readObject() {
        ObjectInputStream ois = null;
        try {
            //1.创造流
            ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
            //2.读取对象
            Person p = (Person) ois.readObject();
            System.out.println(p.getName() + "-->" + p.getAge());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    //3.关闭流
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        writeObject();
        readObject();
    }
}

运行结果:
运行结果


带有引用变量的对象序列化
这个类的引用类型属性必须是可序列化的,还是以Person类为例,添加了Job属性之后:

import java.io.*;

public class ObjectStreamTest {
    //测试类需要序列化
    private static class Person implements Serializable {
        private String name;
        private int age;
        private Job job;
        public Person(String name, int age, Job job) {
            this.name = name;
            this.age = age;
            this.job = job;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
        public Job getJob() {
            return job;
        }
    }
    //引用类型属性也必须序列化
    private static class Job implements Serializable {
        private String jobName;
        private double salary;
        public Job(String name, double salary) {
            this.jobName = name;
            this.salary = salary;
        }
        public String getJobName() {
            return jobName;
        }
        public double getSalary(){
            return salary;
        }
    }

    //将对象写出到数据源
    private static void writeObject() {
        ObjectOutputStream oos = null;
        try {
            //1.创造流
            oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
            //2.造对象
            oos.writeObject(new Person("张三", 25,new Job("医生",8000)));
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //3.关闭流
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //从数据源读取对象
    private static void readObject() {
        ObjectInputStream ois = null;
        try {
            //1.创造流
            ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
            //2.读取对象
            Person p = (Person) ois.readObject();
            System.out.println(p.getName() + "-->" + p.getAge() + "-->" + p.getJob().getJobName());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    //3.关闭流
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        writeObject();
        readObject();
    }
}

运行结果:
结果


2、serialVersionUID 和 transient

  • transient: 该关键字可以使属性不会被序列化。

例如ArrayList 中存储数据的数组 elementData 是用 transient 修饰的。
private transient Object[] elementData;
因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。


  • serialVersionUID:凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。
    private static final long serialVersionUID

serialVersionUID用来表明类的不同版本间的兼容性。其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。如果类没有显式定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。所以最好显示声明。

总之,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则会产生序列化版本不一致InvalidCastException。

还是以之前的Person类为例,当我们想隐藏对象的年龄时,我们可以在age前添加transient:

public class ObjectStreamTest {
    private static class Person implements Serializable {
        private static final long serialVersionUID = -7867359117310966094L;
        private String name;
        private transient int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
    }

    //将对象写出到数据源
    private static void writeObject() {
        ObjectOutputStream oos = null;
        try {
            //1.创造流
            oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
            //2.造对象
            oos.writeObject(new Person("张三", 25));
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //3.关闭流
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //从数据源读取对象
    private static void readObject() {
        ObjectInputStream ois = null;
        try {
            //1.创造流
            ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
            //2.读取对象
            Person p = (Person) ois.readObject();
            System.out.println(p.getName() + "-->" + p.getAge());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    //3.关闭流
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //writeObject();
        readObject();
    }
}

运行结果:
age为int类型,隐藏后初始为0
transient结果

然而如果我们不显式声明serialVersionUID,而加上了在原代码进行了修改(加上了transient),反序列化进行读取则会产生异常:

public class ObjectStreamTest {
    private static class Person implements Serializable {
        //private static final long serialVersionUID = -7867359117310966094L;
        private String name;
        private transient int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
    }

    //将对象写出到数据源
    private static void writeObject() {
        ObjectOutputStream oos = null;
        try {
            //1.创造流
            oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
            //2.造对象
            oos.writeObject(new Person("张三", 25));
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //3.关闭流
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //从数据源读取对象
    private static void readObject() {
        ObjectInputStream ois = null;
        try {
            //1.创造流
            ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
            //2.读取对象
            Person p = (Person) ois.readObject();
            System.out.println(p.getName() + "-->" + p.getAge());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    //3.关闭流
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //writeObject();
        readObject();
    }
}

运行结果:
异常

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值