這篇文章主要介紹了Java 物件序列化與反序列化,小編覺得蠻不錯的,現在分享給大家,也給大家做個參考。一起跟著小編過來看看吧
之前的文章中我們介紹過有關字節流字符流的使用,當時我們對於將一個對象輸出到流中的操作,使用DataOutputStream流將該對像中的每個屬性值逐一輸出到流中,讀出時相反。在我們看來這種行為實在是繁瑣,尤其是在這個物件中屬性值很多的時候。基於此,Java中物件的序列化機制就可以很好的解決這種操作。本篇就簡單的介紹Java物件序列化,主要內容如下:
簡潔的程式碼實作
一、簡潔的程式碼實作
//简单定义一个Student类 public class Student { private String name; private int age; public Student(){} public Student(String name,int age){ this.name = name; this.age=age; } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } public String getName(){ return this.name; } public int getAge(){ return this.age; } //重写toString @Override public String toString(){ return ("my name is:"+this.name+" age is:"+this.age); } }
//main方法实现了将对象写入文件并读取出来 public static void main(String[] args) throws IOException{ DataOutputStream dot = new DataOutputStream(new FileOutputStream("hello.txt")); Student stuW = new Student("walker",21); //将此对象写入到文件中 dot.writeUTF(stuW.getName()); dot.writeInt(stuW.getAge()); dot.close(); //将对象从文件中读出 DataInputStream din = new DataInputStream(new FileInputStream("hello.txt")); Student stuR = new Student(); stuR.setName(din.readUTF()); stuR.setAge(din.readInt()); din.close(); System.out.println(stuR); }輸出結果:my name is:walker age is:21
public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt")); Student stuW = new Student("walker",21); oos.writeObject(stuW); oos.close(); //从文件中读取该对象返回 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt")); Student stuR = (Student)ois.readObject(); System.out.println(stuR); }寫入檔案時,只用了一條語句就是writeObject,讀取時也是只用了一條語句readObject。而Student中的那些set,get方法都用不到了。是不是很簡潔呢?接下來介紹實作細節。
二、實現序列化的基本演算法
介面 java.io.Serializable;,這是一個標記接口,不用實作任何的方法。而我們的ObjectOutputStream流,就是一個可以將物件資訊轉為位元組的流,建構子如下:
public ObjectOutputStream(OutputStream out)也就是所有位元組流都可以作為參數傳入,相容一切位元組操作。在這個流中定義了writeObject和readObject方法,實作了序列化物件和反序列化物件。當然,我們也是可以透過在類別中實現這兩個方法來自訂序列化機制,具體的後文介紹。這裡我們只需要了解整個序列化機制,所有的物件資料只會保存一份,至於相同的物件再次出現,只保存對應的序號。下面,透過兩個特殊的情況直觀的感受下他的這個基本演算法。
三、兩個特殊的實例
public class Student implements Serializable { String name; int age; Teacher t; //另外一个对象类型 public Student(){} public Student(String name,int age,Teacher t){ this.name = name; this.age=age; this.t = t; } public void setName(String name){this.name = name;} public void setAge(int age){this.age = age;} public void setT(Teacher t){this.t = t;} public String getName(){return this.name;} public int getAge(){return this.age;} public Teacher getT(){return this.t;} } public class Teacher implements Serializable { String name; public Teacher(String name){ this.name = name; } } public static void main(String[] args) throws IOException, ClassNotFoundException { Teacher t = new Teacher("li"); Student stu1 = new Student("walker",21,t); Student stu2 = new Student("yam",22,t); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt")); oos.writeObject(stu1); oos.writeObject(stu2); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt")); Student stuR1 = (Student)ois.readObject(); Student stuR2 = (Student)ois.readObject(); if (stuR1.getT() == stuR2.getT()) System.out.println("相同对象"); }結果是很顯而易見的,輸出了相同對象。我們在main函數中定義了兩個student類型對象,他們卻都下面看第二個特殊實例:
public class Student implements Serializable { String name; Teacher t; } public class Teacher implements Serializable { String name; Student stu; } public static void main(String[] args) throws IOException, ClassNotFoundException { Teacher t = new Teacher(); Student s =new Student(); t.name = "walker"; t.stu = s; s.name = "yam"; s.t = t; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt")); oos.writeObject(t); oos.writeObject(s); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt")); Teacher tR = (Teacher)ois.readObject(); Student sR = (Student)ois.readObject(); if(tR == sR.t && sR == tR.stu)System.out.println("ok"); }
输出的结果是ok,这个例子可以叫做:循环引用。从结果我们可以看出来,序列化之前两个对象存在的相互的引用关系,经过序列化之后,两者之间的这种引用关系是依然存在的。其实按照我们之前介绍的判断算法来看,首先我们先序列化了teacher对象,因为他内部引用了student的对象,两者都是第一次遇到,所以将两者序列化到流中,然后我们去序列化student对象,发现这个对象以及内部的teacher对象都已经被序列化了,于是只保存对应的序列号。读取的时候根据序列号恢复对象。
四、自定义序列化机制
综上,我们已经介绍完了基本的序列化与反序列化的知识。但是往往我们会有一些特殊的要求,这种默认的序列化机制虽然已经很完善了,但是有些时候还是不能满足我们的需求。所以我们看看如何自定义序列化机制。自定义序列化机制中,我们会使用到一个关键字,它也是我们之前在看源码的时候经常遇到的,transient。将字段声明transient,等于是告诉默认的序列化机制,这个字段你不要给我写到流中去,我会自己处理的。
public class Student implements Serializable { String name; transient int age; public String toString(){ return this.name + ":" + this.age; } } public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt")); Student stu = new Student(); stu.name = "walker";stu.age = 21; oos.writeObject(stu); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt")); Student stuR = (Student)ois.readObject(); System.out.println(stuR); }
输出结果:walker:0
我们不是给age字段赋初始值了么,怎么会是0呢?正如我们上文所说的一样,被transient修饰的字段不会被写入流中,自然读取出来就没有值,默认是0。下面看看我们怎么自己来序列化这个age。
//改动过的student类,main方法没有改动,大家可以往上看 public class Student implements Serializable { String name; transient int age; private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeInt(25); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); age = ois.readInt(); } public String toString(){ return this.name + ":" + this.age; } }
输出结果:walker:25
结果既不是我么初始化的21,也不是0,而是我们在writeObject方法中写的25。现在我们一点一点看看每个步骤的意义。首先,要想要实现自定义序列化,就需要在该对象定义的类中实现两个方法,writeObject和readObject,而且格式必须和上面贴出来的一样,笔者试过改动方法修饰符,结果导致不能成功序列化。这是因为,Java采用反射机制,检查该对象所在的类中有没有实现这两个方法,没有的话就使用默认的ObjectOutputStream中的这个方法序列化所有字段,如果有的话就执行你自己实现的这个方法。
接下来,看看这两个方法实现的细节,先看writeObject方法,参数是ObjectOutputStream 类型的,这个拿到的是我们在main方法中定义的ObjectOutputStream 对象,要不然它怎么知道该把对象写到那个地方去呢?第一行我们调用的是oos.defaultWriteObject();这个方法实现的功能是,将当前对象中所有没有被transient修饰的字段写入流中,第二条语句我们显式的调用了writeInt方法将age的值写入流中。读取的方法类似,此处不再赘述。
五、版本控制
最后我们来看看,序列化过程的的版本控制问题。在我们将一个对象序列化到流中之后,该对象对应的类的结构改变了,如果此时我们再次从流中将之前保存的对象读取出来,会发生什么?这要分情况来说,如果原类中的字段被删除了,那从流中输出的对应的字段将会被忽略。如果原类中增加了某个字段,那新增的字段的值就是默认值。如果字段的类型发生了改变,抛出异常。在Java中每个类都会有一个记录版本号的变量:static final serivalVersionUID = 115616165165L,此处的值只用于演示并不对应任意某个类。这个版本号是根据该类中的字段等一些属性信息计算出来的,唯一性较高。每次读出的时候都会去比较之前和现在的版本号确认是否发生版本不一致情况,如果版本不一致,就会按照上述的情形分别做处理。
对象的序列化就写完了,如果有什么内容不妥的地方,希望大家指出!
以上是詳解Java物件序列化與反序列化的範例程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!