Java

Java IO笔记(ObjectInputStream/Obje

2019-12-16  本文已影响0人  moonfish1994

(最近刚来到简书平台,以前在CSDN上写的一些东西,也在逐渐的移到这儿来,有些篇幅是很早的时候写下的,因此可能会看到一些内容杂乱的文章,对此深感抱歉,以下为正文)


正文

本篇讲的内容是Java中的序列化问题,以及相关的ObjectInputStream和ObjectOutputStream类。

我们知道Java是基于对象编程的,在前面的篇幅中,当我们要进行传输数据时,都是以流的形式来进行数据传输。如果能在流中传直接输对象岂不是更为方便,幸运的是Java在这个方面本身就提供了支持,序列化和反序列技术帮我我们实现了该功能。

序列化指的是将对象转换为字节序列,而反序列化就是将字节序列转换为对象了,Java IO中的ObjectInputStream类中的readObject()方法对应着反序列化,ObjuectOutputStream类中的writeObjext(Object object)对应着序列化,当然所序列化的对象object必须是可被序列化的。

序列化反序列化操作是成对使用的,使用时可以将信息写入至存储介质之中,待到要用的时候再从存储介质之中中还原,这样可以节省很多的操作空间,如Web中的session,当并发数量很高时,可能会将一些存储到硬盘中,等需要时再还原。

那么如何能让对象可以被序列化呢,其实很简单,那就是该对象必须实现java.io.Serializable接口或者java.io.Externlizabel接口,其中Externlizable接口继承了Serializable接口。如果采用默认的序列化方式实现Serializable接口就行了,如果想自己控制序列化,那么就需要实现Externlizable接口。像我们常用的String,Number对象本身都实现了Serializable接口:


String类
Number类

下面先用一个最简单的例子来让大家熟悉序列化和反序列化的操作:

package objectIO;
 
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ObjectIOTest {
    public static void main(String[] args) {
        Person person = new Person("tom", 19);
        try {
            ObjectOutputStream oos = new ObjectOutputStream(
                    new FileOutputStream("./src/objectIO/test.txt"));
            oos.writeObject(person);
            oos.close();
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                    "./src/objectIO/test.txt"));
            Person temp = (Person) ois.readObject();
            System.out.println(temp);
            ois.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
 
}
 
class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
 
    public Person() {
    }
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    @Override
    public String toString() {
        return "[name: " + name + " age: " + age + "]";
    }
 
}

执行上述代码可以在控制台得到如下打印:


控制台输出

可以看出成功的从文件中还原了对象,打开test.txt文件可以看到如下情况:


测试文件

虽然大部分是乱码但依稀能看出一点儿Person对象的样子~

看上去序列化和反序列化是不是很简单呢,其实它们也有很多要注意的地方,首先我们在Person类中看到了一个变量,serialVersionUID,这个是什么呢?其实它相当于一序列化和反序列化的一个标识符。
比如当你序列化和反序列化不在同一台机器时,那么反序列化时,除了两点的对象本身要完全一样意外,还要对比两点的serialVersionUID是否相同,如果相同才能进行反序列化,否则将会失败。

serialVersionUID有两种生成方式:
第一种为默认的值为1L,就如上面使用的那样。
第二种则是会根据实体类来生成一个不重复的long类型数据。所使用的JVM不同,即使是相同的实体类,也可能获得不同的值,如果实体类有过更改,那么生成的ID值肯定会发生变化,这点要注意。

一般没有特殊要求,使用第一种即可,如果你不显示的声明这个值,它会默认以第二种方式来帮你生成。
下面将上面的案例进行修改,来加深一下对serialVersionUID的认识:

package objectIO;
 
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ObjectIOTest {
    public static void main(String[] args) {
        Person person = new Person("tom", 19);
        try {
            ObjectOutputStream oos = new ObjectOutputStream(
                    new FileOutputStream("./src/objectIO/test.txt"));
            oos.writeObject(person);
            oos.close();
            System.out.println("序列化成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
class Person implements Serializable {
    
    private String name;
    private int age;
 
    public Person() {
    }
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    @Override
    public String toString() {
        return "[name: " + name + " age: " + age + "]";
    }
 
}

上述的实体类Person没有显示的定义serialVersionUID,所以默认以第二种方式生成。执行完上述打印可以得到如下打印:


控制台输出

然后执行反序列化代码:

package objectIO;
 
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ObjectIOTest {
    public static void main(String[] args) {
        Person person = new Person("tom", 19);
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                    "./src/objectIO/test.txt"));
            Person temp = (Person) ois.readObject();
            System.out.println(temp);
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 
class Person implements Serializable {
    
    private String name;
    private int age;
    private String sex;
    public Person() {
    }
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public Person(String name, int age, String sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
 
    @Override
    public String toString() {
        return "[name: " + name + " age: " + age +" sex: "+sex+ "]";
    }
 
}

执行后发现反序列化失败,控制台输出如下内容:


控制台输出

因为第二次我们修改了实体类,所以当再次自动生成serialVersionUID的值与之前的不同,所以反序列化无法达成,现在修改代码,显示的定义该值:

package objectIO;
 
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ObjectIOTest {
    public static void main(String[] args) {
        Person person = new Person("tom", 19);
        try {
            ObjectOutputStream oos = new ObjectOutputStream(
                    new FileOutputStream("./src/objectIO/test.txt"));
            oos.writeObject(person);
            oos.close();
            System.out.println("序列化成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
class Person implements Serializable {
    
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    public Person() {
    }
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
 
    @Override
    public String toString() {
        return "[name: " + name + " age: " + age +"]";
    }
 
}

执行代码后,控制台输出如下:


控制台输出

然后修改实体类:


package objectIO;
 
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
 
public class ObjectIOTest {
    public static void main(String[] args) {
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                    "./src/objectIO/test.txt"));
            Person temp = (Person) ois.readObject();
            System.out.println(temp);
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
class Person implements Serializable {
    
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private String sex;
    
    public Person() {
    }
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public Person(String name, int age, String sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
 
    @Override
    public String toString() {
        return "[name: " + name + " age: " + age +" sex: "+sex+ "]";
    }
 
}

执行上述代码后,控制台输出如下:


控制台输出

可以看出反序列化成功,实体类修改过后,对于未赋过值的属性为其初始化默认值。

对于serialVersionUID的具体使用方式还是要根据实际使用场景来觉定,比如你做了一个c/s程序,当你服务器代码升级过后,如果你希望用户也升级对应的客户端,那么你可以修改该值,使得客户端无法使用,强制用户升级(好像有点儿强盗啊。。),这种时候显然就不需要用一个定值了,当然如果你希望客户端能一直使用,那么定义一个不变的值是个很好的选择。

那么对象序列化是所有的属性都可以被序列化吗?答案是否定的。序列化是记录对象的状态,那么当定一个静态属性的时候,因为其属于类的状态,所以它不会被序列化。同时,被Transient关键字修饰过的属性也不可以被序列化,下面举例说明:

package objectIO;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ObjectIOTest {
    public static void main(String[] args) {
        Person person = new Person("tom", 19,"123456789");
        Person.hobby = "swim";
        try {
            ObjectOutputStream oos = new ObjectOutputStream(
                    new FileOutputStream("./src/objectIO/test.txt"));
            oos.writeObject(person);
            oos.close();
            System.out.println("序列化成功");
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                    "./src/objectIO/test.txt"));
            Person temp = (Person) ois.readObject();
            System.out.println("反序列化成功");
            System.out.println(temp);
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
class Person implements Serializable {
    
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private String sex;
    public static String hobby;
    private transient String numbers;
    
    public Person() {
    }
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public Person(String name, int age, String sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    
    
 
    public Person(String name, int age, String sex, String numbers) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.numbers = numbers;
    }
 
    @Override
    public String toString() {
        return "[name: " + name + " age: " + age +" sex: "+sex+" hobby: "+hobby+" numbers: "+numbers+ "]";
    }
 
}

执行上述代码后,控制台输出如下打印:


控制台输出

按照所说的静态属性和被transient修饰过的属性应该都无法被序列化,从控制台可以看出,被transient修饰过的numbers确实没有数据,为什么静态属性hobby有值呢?其实它并不是反序列化得到的,因为其是静态属性,所以当jvm在对象中无法找到该值的时候会继续扩大寻找范围,此时内存中的hobby因为被赋过值且一直存在,所以此时打印出来的是内存中的数据。

当序列化操作和反序列化操作分开执行的时候,可以看到如下打印:


控制台输出

事实证明静态属性是不可以被序列化的。

除这些还有一种情况需要考虑,那就是父子类继承的情况,我们知道,当我们创建一个子类对象时,需要先创建其父类对象。那么问题来了,当子类实现了Serializable接口,父类却没有实现时,序列化时,便不会对其父类进行序列化,那么当反序列化时,便会调用其父类的无参构造函数来创建其父类对象,此时父类特有的那些属性值时,便会被赋予初始化值(如果无参构造中没有进行过赋值的话),举例说明:

package objectIO;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ObjectIOTest {
    public static void main(String[] args) {
        Person person = new Person("hi","tom", 19,"boy","123456789");
        Person.hobby = "swim";
        try {
            ObjectOutputStream oos = new ObjectOutputStream(
                    new FileOutputStream("./src/objectIO/test.txt"));
            oos.writeObject(person);
            oos.close();
            System.out.println("序列化成功");
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                    "./src/objectIO/test.txt"));
            Person temp = (Person) ois.readObject();
            System.out.println("反序列化成功");
            System.out.println(temp);
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
class Person extends PersonFather implements Serializable {
    
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private String sex;
    public static String hobby;
    private transient String numbers;
    
    public Person() {
    }
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public Person(String name, int age, String sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    
    public Person(String name, int age, String sex, String numbers) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.numbers = numbers;
    }
    
    public Person(String greet,String name, int age, String sex, String numbers){
        super(greet);
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.numbers = numbers;
    }
    
 
    @Override
    public String toString() {
        return "[name: " + name + " age: " + age +" sex: "+sex+" hobby: "+hobby+" numbers: "+numbers+" greet: "+greet+"]";
    }
 
}
 
 
class PersonFather{
    public String greet;
    public PersonFather(){
        
    }
    public PersonFather(String greet){
        this.greet = greet;
    }
}

执行上述代码可以得到如下打印:


控制台输出

从控制台输出可以看出,父类的属性确实没有被序列化,如果想要实现的话,那么父类也必须实现Serializable接口。
通过这种特性也可以实现transient关键字的效果。

最后要讲述的则是序列化中的序列化和反序列化方法了,一般情况下我们使用对象流中的readObject和writeObject就可以了,但有些时候我们需要自行控制序列化和反序列化的过程,这样可以提升数据的安全性,这时我们就就要自己重写这两个方法了,在Serializable接口中也有说明:

/**

package java.io;

import java.io.ObjectOutput;
import java.io.ObjectInput;

public interface Externalizable extends java.io.Serializable {

void writeExternal(ObjectOutput out) throws IOException;

void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

}
下面举两个例子,一个是实现Serialiabel接口,重写readObject,writeObject方法,来实现加密,另一个是实现Externalizable接口,重写writeExternal,readExternal方法,实现自主控制序列化反序列化流程。

例子1:

package objectIO;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;
import java.io.Serializable;

public class ObjectIOTest {
public static void main(String[] args) {
Person1 person = new Person1("tom", 19, "boy", "administrator");
Person1.hobby = "swim";
try {
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("./src/objectIO/test.txt"));
oos.writeObject(person);
oos.close();
System.out.println("序列化成功");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"./src/objectIO/test.txt"));
Person1 temp = (Person1) ois.readObject();
System.out.println("反序列化成功");
System.out.println(temp);
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}

class Person1 implements Serializable {

private static final long serialVersionUID = 1L;
private String name;
private int age;
private String sex;
private String password;
public static String hobby;

public Person1() {

}

public Person1(String name, int age) {
    this.name = name;
    this.age = age;
}

public Person1(String name, int age, String sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}

public Person1(String name, int age, String sex, String numbers) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.password = numbers;
}

@Override
public String toString() {
    return "[name: " + name + " age: " + age + " sex: " + sex + " hobby: "
            + hobby + " password: " + password + "]";
}

private String getSecretNumber(String password){
    byte[] bytes = password.getBytes();
    for(int i = 0; i < bytes.length; i++){
        bytes[i] += 1;
    }
    return new String(bytes,0,bytes.length);
}
private String explainSecretNumber(String password){
    byte[] bytes = password.getBytes();
    for(int i = 0; i < bytes.length; i++){
        bytes[i] -= 1;
    }
    return new String(bytes,0,bytes.length);
}

private void writeObject(java.io.ObjectOutputStream out) {
    try {
        PutField putField = out.putFields();
        putField.put("name", name);
        putField.put("age", age);
        putField.put("sex", sex);
        putField.put("password", getSecretNumber(password));
        System.out.println(password+"加密后为"+getSecretNumber(password));
        out.writeFields();
    } catch (Exception e) {
        // TODO: handle exception
        e.printStackTrace();
    }
    
}

private void readObject(java.io.ObjectInputStream in) {
    try {
        GetField getField = in.readFields();
        Object object1 = getField.get("name", null);
        int object2 = getField.get("age", 0);
        Object object3 = getField.get("sex", null);
        Object object4 = getField.get("password", null);
        name = object1.toString();
        age =  object2;
        sex = object3.toString();
        password = explainSecretNumber(object4.toString());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}
执行上述代码可以得到如下打印:

例子2:

package objectIO;

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;
import java.io.Serializable;

public class ObjectIOTest1 {
public static void main(String[] args) {
Person person = new Person("tom", 19, "boy", "administrator");
Person.hobby = "swim";
try {
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("./src/objectIO/test1.txt"));
oos.writeObject(person);
oos.close();
System.out.println("序列化成功");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"./src/objectIO/test1.txt"));
Person temp = (Person) ois.readObject();
System.out.println("反序列化成功");
System.out.println(temp);
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}

class Person implements Externalizable {

private static final long serialVersionUID = 1L;
private String name;
private int age;
private String sex;
private String password;
public static String hobby;

public Person() {

}

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

public Person(String name, int age, String sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}

public Person(String name, int age, String sex, String numbers) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.password = numbers;
}

@Override
public String toString() {
    return "[name: " + name + " age: " + age + " sex: " + sex + " hobby: "
            + hobby + " password: " + password + "]";
}



@Override
public void writeExternal(ObjectOutput out) throws IOException {
    // TODO Auto-generated method stub
    out.writeObject(name);
}

@Override
public void readExternal(ObjectInput in) throws IOException,
        ClassNotFoundException {
    // TODO Auto-generated method stub
    name = (String) in.readObject();
    
}

}

执行上述代码打印如下:

由上可见,只序列化了name属性。
以上为本篇内容。

上一篇 下一篇

猜你喜欢

热点阅读