java IO流 序列化与反序列化

1)java io流相关概念

输出流:

 

输入流:


因此输入和输出都是从程序的角度来说的。

字节流:一次读入或读出是8位二进制

字符流:一次读入或读出是16位二进制

字节流和字符流的原理是相同的,只不过处理的单位不同而已。后缀是Stream是字节流,而后缀是ReaderWriter是字符流。 

java IO流 序列化与反序列化_第1张图片



2.序列化与反序列化

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

class Student implements Serializable{        
    private String name;  
    private transient int age;  
    private Course course;        
    public void setCourse(Course course){  
        this.course = course;  
    }      
    public Course getCourse(){  
        return course;  
    }        
    public Student(String name, int age){  
        this.name = name;  
        this.age = age;  
    }   
    public String  toString(){  
        return "Student Object name:"+this.name+" age:"+this.age;  
    }  
}  
  
class Course implements Serializable{        
    private static String courseName;  
    private int credit;        
    public Course(String courseName, int credit){  
        this.courseName  = courseName;  
        this.credit = credit;  
    }        
    public String toString(){            
        return "Course Object courseName:"+courseName  
               +" credit:"+credit;  
    }  
}

序列化写入文件

public class TestWriteObject{    
    public static void main(String[] args) {    
        String filePath = "C://obj.txt";  
        ObjectOutputStream objOutput = null;  
        Course c1 = new Course("C language", 3);  
        Course c2 = new Course("OS", 4);           
        Student s1 = new Student("king", 25);  
        s1.setCourse(c1);            
        Student s2 = new Student("jason", 23);  
        s2.setCourse(c2);   
        try {  
            objOutput = new ObjectOutputStream(new FileOutputStream(filePath));  
            objOutput.writeObject(s1);  
            objOutput.writeObject(s2);  
            objOutput.writeInt(123);  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }finally{  
            try {  
                objOutput.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }            
        System.out.println("Info:对象被写入"+filePath);  
    } 

反序列化从文件读取数据

public class TestReadObject  {  
      
    public static void main(String[] args) {  
          
        String filePath = "C://obj.txt";  
        ObjectInputStream objInput = null;  
        Student s1 = null,s2 = null;  
        int intVal = 0;  
      
        try {  
            objInput = new ObjectInputStream(new FileInputStream(filePath));  
            s1 = (Student)objInput.readObject();  
            s2 = (Student)objInput.readObject();  
            intVal = objInput.readInt();  
              
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }catch(ClassNotFoundException cnfe){  
            cnfe.printStackTrace();  
        }finally{  
              
            try {  
                objInput.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }            
        System.out.println("Info:文件"+filePath+"中读取对象");  
        System.out.println(s1);  
        System.out.println(s1.getCourse());  
        System.out.println(s2);  
        System.out.println(s2.getCourse());  
        System.out.println(intVal);  
    }  
}  

1、在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。

2、通过ObjectOutputStreamObjectInputStream对对象进行序列化及反序列化

3、序列化并不保存静态变量。

java中transient

在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。

总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

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

ArrayList的序列化

java.util.ArrayList 的源码
public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    transient Object[] elementData; // non-private to simplify nested class access
    private int size;
}

从上面的代码中可以知道ArrayList实现了 java.io.Serializable 接口,那么我们就可以对它进行序列化及反序列化。因为elementData是 transient 的,所以我们认为这个成员变量不会被序列化而保留下来。我们写一个Demo,验证一下我们的想法:
public static void main(String[] args) throws IOException, ClassNotFoundException {
        List stringList = new ArrayList();
        stringList.add("hello");
        stringList.add("world");
        stringList.add("hollis");
        stringList.add("chuang");
        System.out.println("init StringList" + stringList);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist"));
        objectOutputStream.writeObject(stringList);
 
        IOUtils.close(objectOutputStream);
        File file = new File("stringlist");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        List newStringList = (List)objectInputStream.readObject();
        IOUtils.close(objectInputStream);
        if(file.exists()){
            file.delete();
        }
        System.out.println("new StringList" + newStringList);
    }

ArrayList底层是通过数组实现的。那么数组 elementData 其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢?

writeObject和readObject方法

在ArrayList中定义了来个方法: writeObjectreadObject

这里先给出结论:

在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。

如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。

用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

那么为什么ArrayList要用这种方式来实现序列化呢?

why transient

ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。

why writeObject and readObject

前面说过,为了防止一个包含大量空对象的数组被序列化,为了优化存储,所以,ArrayList使用transient来声明elementData。 但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写writeObject 和 readObject方法的方式把其中的元素保留下来。

writeObject方法把elementData数组中的元素遍历的保存到输出流(ObjectOutputStream)中。

readObject方法从输入流(ObjectInputStream)中读出对象并保存赋值到elementData数组中。

虽然ArrayList中写了writeObject 和 readObject 方法,但是这两个方法并没有显示的被调用啊。

如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?

答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会通过反射的方式调用。


1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出NotSerializableException异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。

2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。

3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略


你可能感兴趣的:(java)