← 返回首页
JavaSE系列教程(七十三)
发表时间:2020-02-21 13:04:55
讲解 java IO 流之数据流和对象序列化。

1.数据流

数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。

实例:使用DataOutputStream把java 基本数据类型写入到文件,再使用DataInputStream把数据读取出来。


public class DataInputStreamDemo {

    public static void main(String[] args) {

        File destFile = new File("d:"+File.separator+"dest.data");
        DataInputStream din = null;
        DataOutputStream dout = null;
        try{

            dout = new DataOutputStream(new FileOutputStream(destFile));
            dout.writeInt(100); //写入整数
            dout.writeDouble(Math.PI); //写入浮点数
            dout.writeBoolean(true); //写入布尔
            dout.writeChar('中'); //写入字符
            dout.writeUTF("hello,world!"); //写入字符串

            dout.flush();

            din = new DataInputStream(new FileInputStream(destFile));
            //注意:读取类型顺序必须和写入类型顺序一致。
            System.out.println(din.readInt()); //读取整数
            System.out.println(din.readDouble()); //读取浮点数
            System.out.println(din.readBoolean()); //读取布尔
            System.out.println(din.readChar()); //读取字符
            System.out.println(din.readUTF()); //读取字符串

        }catch(Exception ex){
            ex.printStackTrace();
        }finally {
            try{
                if(din!=null){
                    din.close();
                    din = null;
                }
                if(dout!=null){
                    dout.close();
                    dout=null;
                }
            }catch(Exception ex){
                ex.printStackTrace();
            }
        }
    }
}
运行结果:
100
3.141592653589793
true
中
hello,world!

2.对象序列化和反序列化

序列化(Serialization)是将对象的状态信息转化为可以存储或者传输的形式的过程,一般将一个对象存储到一个储存媒介,例如文件或数据库等,在网络传输过程中,可以是字节或者json等格式;而字节或者json格式的可以还原成完全相等的对象,这个相反的过程又称为反序列化;

简单来说:把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。

对象的序列化主要有两种用途: 1)对象持久化:把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中; 2)网络传输对象:在网络上传送对象的字节序列。可以通过序列化把主机A进程上的对象序列化为二进制序列,传输到主机B上的进程从序列中重构出该对象。这在RMI中应用广泛,RMI的结果可以是一个对象。

Java为了方便开发人员将java对象序列化及反序列化提供了一套方便的API来支持,其中包括以下接口和类:

注意,一个类的对象要想序列化成功,必须满足两个条件: 1)该类必须实现 java.io.Serializable 接口。 2)该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。 如果你想知道一个 Java 标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现 java.io.Serializable接口。

实例,设计一个游戏玩家类Player和宠物类Dog,一个游戏玩家角色拥有一个宠物狗对象。使用对象序列化实现把游戏玩家角色对象保存到磁盘文件里面,实现类似游戏存盘的效果。再使用反序列实现读取存盘,把磁盘里的对象恢复到内存。

//宠物类
class Dog implements Serializable{
    private static final long serialVersionUID = 5215709583065948679L;
    private String alias;
    private int kill; //攻击力。

    public Dog() {
    }

    public Dog(String alias, int kill) {
        this.alias = alias;
        this.kill = kill;
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public int getKill() {
        return kill;
    }

    public void setKill(int kill) {
        this.kill = kill;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "alias='" + alias + '\'' +
                ", kill=" + kill +
                '}';
    }
}
//游戏玩家类
class Player implements Serializable {

    private static final long serialVersionUID = -3923850838982144178L;
    private String name; //昵称
    private int level; //经验值
    private int blood; //血值
    private Dog dog; //角色的宠物

    public Player() {
    }

    public Player(String name, int level, int blood,Dog dog) {
        this.name = name;
        this.level = level;
        this.blood = blood;
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public int getBlood() {
        return blood;
    }

    public void setBlood(int blood) {
        this.blood = blood;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Player{" +
                "name='" + name + '\'' +
                ", level=" + level +
                ", blood=" + blood +
                ", dog=" + dog +
                '}';
    }
}

public class SerializableDemo {
    public static void main(String[] args) {
        Dog dog = new Dog("二郎犬",100);
        Player p = new Player("李逍遥",1000,300,dog);
        //对象序列化
        File saveFile = new File("iodemo/save.dat");

        ObjectInput oin = null;
        ObjectOutput oout = null;
        try {
            //对象输出流
            oout = new ObjectOutputStream(new FileOutputStream(saveFile));
            //输入输入流
            oin = new ObjectInputStream(new FileInputStream(saveFile));

            oout.writeObject(p);
            oout.close();

            //读取文件,把对象恢复到内存里面,读盘。
            Player temp = (Player) oin.readObject();
            System.out.println(temp);
            System.out.println(temp == p);//false;说明反序列出来的是新对象。

        }catch(Exception ex){
            ex.printStackTrace();
        }finally {
            try{
                if(oin!=null){
                    oin.close();
                    oin=null;
                }
                if(oout!=null){
                    oout.close();
                    oout=null;
                }
            }catch(Exception ex){
                ex.printStackTrace();
            }
        }
    }
}

运行结果:
Player{name='李逍遥', level=1000, blood=300, dog=Dog{alias='二郎犬', kill=100}}
false

如果,对象的某个属性不想参与序列化,可以使用transient修饰。transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 类型默认是 0,引用类型默认是 null。

例如,上例中不想序列化玩家对象的宠物信息,只需要给dog属性前添加 transient.

class Dog implements Serializable{
    private static final long serialVersionUID = 5215709583065948679L;
    private String alias;
    private int kill; //攻击力。

    public Dog() {
    }

    public Dog(String alias, int kill) {
        this.alias = alias;
        this.kill = kill;
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public int getKill() {
        return kill;
    }

    public void setKill(int kill) {
        this.kill = kill;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "alias='" + alias + '\'' +
                ", kill=" + kill +
                '}';
    }
}

class Player implements Serializable {

    private static final long serialVersionUID = -3923850838982144178L;
    private String name; //昵称
    private int level; //经验值
    private int blood; //血值

    private transient Dog dog; //角色的宠物,使用transient 表示该属性不会被序列化

    public Player() {
    }

    public Player(String name, int level, int blood,Dog dog) {
        this.name = name;
        this.level = level;
        this.blood = blood;
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public int getBlood() {
        return blood;
    }

    public void setBlood(int blood) {
        this.blood = blood;
    }

    public Dog getDog() {
        return dog;
    }


    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Player{" +
                "name='" + name + '\'' +
                ", level=" + level +
                ", blood=" + blood +
                ", dog=" + dog +
                '}';
    }
}

public class SerializableDemo {

    public static void main(String[] args) {
        Dog dog = new Dog("二郎犬",100);
        Player p = new Player("李逍遥",1000,300,dog);
        //对象序列化
        File saveFile = new File("iodemo/save.dat");

        ObjectInput oin = null;
        ObjectOutput oout = null;
        try {

            //对象输出流
            oout = new ObjectOutputStream(new FileOutputStream(saveFile));
            //输入输入流
            oin = new ObjectInputStream(new FileInputStream(saveFile));

            oout.writeObject(p);
            oout.close();

            //读取文件,恢复到内存里面,读盘
            Player temp = (Player) oin.readObject();
            System.out.println(temp);
            System.out.println(temp == p);//false;

        }catch(Exception ex){
            ex.printStackTrace();
        }finally {
            try{
                if(oin!=null){
                    oin.close();
                    oin=null;
                }
                if(oout!=null){
                    oout.close();
                    oout=null;
                }
            }catch(Exception ex){
                ex.printStackTrace();
            }
        }
    }
}

运行结果:
Player{name='李逍遥', level=1000, blood=300, dog=null}
false

s​e​r​i​a​l​V​e​r​s​i​o​n​U​I​D​:​ ​字​面​意​思​上​是​序​列​化​的​版​本​号​,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。Java的序列化的机制通过判断serialVersionUID来验证版本的一致性。在反序列化的时候与本地的类的serialVersionUID进行比较,一致则可以进行反序列化,不一致则会抛出异常InvalidCastException。serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。通常是通过eclipse或者idea这种开发工具自动生成的,千万不要自己手工赋值。

简单来说定义serialVersionUID有两种用途: 1)在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID; 2)在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。