首页 > 代码库 > java序列化详解(一)

java序列化详解(一)

所谓对象的序列化,就是让对象可以保存。这里的保存是指保存到本地,你可以理解为文件也可以理解为网络中传输的流~

今天我们就来讲一下 java序列化的知识,弄懂这个就可以明白android中 对象序列化 ,activity之间对象的传递是怎么一回事。

 

新建一个person类

package com.burning.test;import java.io.Serializable;public class Person implements Serializable{        public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    @Override    public String toString() {        return "Person [name=" + name + ", age=" + age + ", gender=" + gender                + "]";    }    public Gender getGender() {        return gender;    }    public void setGender(Gender gender) {        this.gender = gender;    }    private String name;        private int age;        private Gender gender;    }

再新建一个gender类

 

package com.burning.test;public class Gender {    private int sex;    @Override    public String toString() {        return "Gender [sex=" + sex + "]";    }    public int getSex() {        return sex;    }    public void setSex(int sex) {        this.sex = sex;    }            }

最后看看我们的主类。

 

 1 package com.burning.test; 2  3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.FileOutputStream; 7 import java.io.IOException; 8 import java.io.ObjectInputStream; 9 import java.io.ObjectOutputStream;10 11 public class TestMain {12 13     public static void main(String[] args) {14         // TODO Auto-generated method stub15         File file = new File("person.out");16 17         try {18             ObjectOutputStream oout = new ObjectOutputStream(19                     new FileOutputStream(file));20             Person person = new Person();21             person.setAge(18);22             person.setName("burning");23             //Gender gender = new Gender();24             ///gender.setSex(1);25             ///person.setGender(gender);26             oout.writeObject(person);27             oout.close();28 29             ObjectInputStream oin = new ObjectInputStream(new FileInputStream(30                     file));31             Object person2 = oin.readObject();32             oin.close();33             System.out.println(person2);34 35         } catch (FileNotFoundException e) {36             // TODO Auto-generated catch block37             e.printStackTrace();38         } catch (IOException e) {39             // TODO Auto-generated catch block40             e.printStackTrace();41         } catch (ClassNotFoundException e) {42             // TODO Auto-generated catch block43             e.printStackTrace();44         }45 46     }47 48 }

可以看一下 这个就是把person 这个对象保存在了一个文件中。

可以看一下23-25 这个被注释掉的语句,如果不注释掉,后果就是这段代码会抛出一个异常,告诉你要序列化的对象里面有个成员变量没有继承Serializable 这个接口。

所以如果你想把gender这个属性也序列化的话 就要让Gender这个类也继承这个Serializable 接口。

 

我们可以打开看一下这个保存对象的文件。

 

aced 0005 7400 5d50 6572 736f 6e20 5b6e
616d 653d 6275 726e 696e 672c 2061 6765
3d31 382c 2067 656e 6465 723d 6e75 6c6c
5d20 2044 3a5c 5573 6572 735c 6275 726e
696e 675c 776f 726b 7370 6163 655c 5465
7374 5072 6f6a 6563 745c 7065 7273 6f6e
2e6f 7574

 

实际上是二进制文件,而绝非我们设想的是文本文件。

 

当然了如果我们不想默认的序列化这个对象里的所有成员变量(序列化开销较大 应该选择性的序列化我们需要的变量 而不是默认全部序列化),应该怎么做?

 

我们只要把person类的 name声明改成

 

transient private String name;

 

即可。

 

这样序列化以后 这个name的值是不会进行序列化的 ,你取出来是空~~

 

我们甚至可以加上一个构造函数。

 1 package com.burning.test; 2  3 import java.io.Serializable; 4  5 public class Person implements Serializable { 6  7     /** 8      *  9      */10     private static final long serialVersionUID = 1L;11 12     public Person() {13         // TODO Auto-generated constructor stub14         System.out.println("person的构造函数");15     }16 17     public String getName() {18         return name;19     }20 21     public void setName(String name) {22         this.name = name;23     }24 25     public int getAge() {26         return age;27     }28 29     public void setAge(int age) {30         this.age = age;31     }32 33     transient private String name;34 35     private int age;36 37 }

我们发现 最终读取这个对象的时候 构造函数是没有走的。就好像直接从二进制-----------转成对象一样。

 

我们可以看一下 序列化 写入对象的源代码。

 

 1   /** 2      * Underlying writeObject/writeUnshared implementation. 3      */ 4     private void writeObject0(Object obj, boolean unshared) 5         throws IOException 6     { 7         boolean oldMode = bout.setBlockDataMode(false); 8         depth++; 9         try {10             // handle previously written and non-replaceable objects11             int h;12             if ((obj = subs.lookup(obj)) == null) {13                 writeNull();14                 return;15             } else if (!unshared && (h = handles.lookup(obj)) != -1) {16                 writeHandle(h);17                 return;18             } else if (obj instanceof Class) {19                 writeClass((Class) obj, unshared);20                 return;21             } else if (obj instanceof ObjectStreamClass) {22                 writeClassDesc((ObjectStreamClass) obj, unshared);23                 return;24             }25 26             // check for replacement object27             Object orig = obj;28             Class cl = obj.getClass();29             ObjectStreamClass desc;30             for (;;) {31                 // REMIND: skip this check for strings/arrays?32                 Class repCl;33                 desc = ObjectStreamClass.lookup(cl, true);34                 if (!desc.hasWriteReplaceMethod() ||35                     (obj = desc.invokeWriteReplace(obj)) == null ||36                     (repCl = obj.getClass()) == cl)37                 {38                     break;39                 }40                 cl = repCl;41             }42             if (enableReplace) {43                 Object rep = replaceObject(obj);44                 if (rep != obj && rep != null) {45                     cl = rep.getClass();46                     desc = ObjectStreamClass.lookup(cl, true);47                 }48                 obj = rep;49             }50 51             // if object replaced, run through original checks a second time52             if (obj != orig) {53                 subs.assign(orig, obj);54                 if (obj == null) {55                     writeNull();56                     return;57                 } else if (!unshared && (h = handles.lookup(obj)) != -1) {58                     writeHandle(h);59                     return;60                 } else if (obj instanceof Class) {61                     writeClass((Class) obj, unshared);62                     return;63                 } else if (obj instanceof ObjectStreamClass) {64                     writeClassDesc((ObjectStreamClass) obj, unshared);65                     return;66                 }67             }68 69             // remaining cases70             if (obj instanceof String) {71                 writeString((String) obj, unshared);72             } else if (cl.isArray()) {73                 writeArray(obj, desc, unshared);74             } else if (obj instanceof Enum) {75                 writeEnum((Enum) obj, desc, unshared);76             } else if (obj instanceof Serializable) {77                 writeOrdinaryObject(obj, desc, unshared);78             } else {79                 if (extendedDebugInfo) {80                     throw new NotSerializableException(81                         cl.getName() + "\n" + debugInfoStack.toString());82                 } else {83                     throw new NotSerializableException(cl.getName());84                 }85             }86         } finally {87             depth--;88             bout.setBlockDataMode(oldMode);89         }90     }

注意看一下70-85行。 你就知道

 NotSerializableException  这个异常是哪来的,你也可以知道 array类型 string类型 enum 都是默认可以序列化的~~(查看这些类型的源代码你会发现都默认实现了Serializable接口)

到这里 我们如果想再次对name 进行序列化怎么办?除了 去掉那个关键字,其实还有一个办法。

 1 package com.burning.test; 2  3 import java.io.IOException; 4 import java.io.ObjectInputStream; 5 import java.io.ObjectOutputStream; 6 import java.io.Serializable; 7  8 public class Person implements Serializable { 9 10     /**11      * 12      */13     private static final long serialVersionUID = 1L;14 15     public Person() {16         // TODO Auto-generated constructor stub17         System.out.println("person的构造函数");18     }19 20     @Override21     public String toString() {22         return "Person [name=" + name + ", age=" + age + "]";23     }24 25     public String getName() {26         return name;27     }28 29     public void setName(String name) {30         this.name = name;31     }32 33     public int getAge() {34         return age;35     }36 37     public void setAge(int age) {38         this.age = age;39     }40 41     transient private String name;42 43     private int age;44 45     private void writeObject(ObjectOutputStream os) {46         try {47             os.defaultWriteObject();48         } catch (IOException e1) {49             // TODO Auto-generated catch block50             e1.printStackTrace();51         }52         try {53             os.writeUTF(name);54         } catch (IOException e) {55             // TODO Auto-generated catch block56             e.printStackTrace();57         }58     }59 60     private void readObject(ObjectInputStream in) {61         try {62             in.defaultReadObject();63         } catch (ClassNotFoundException e1) {64             // TODO Auto-generated catch block65             e1.printStackTrace();66         } catch (IOException e1) {67             // TODO Auto-generated catch block68             e1.printStackTrace();69         }70         try {71             name = in.readUTF();72         } catch (IOException e) {73             // TODO Auto-generated catch block74             e.printStackTrace();75         }76     }77 78 }

 

这个writeobject和readobject是私有方法,那么肯定就是在反射中进行调用的了。这个地方实际上和android里面的序列化已经是差不多的了。写法都类似。

可以看一下源代码objectoutputstream里的

 1   /** 2      * Writes instance data for each serializable class of given object, from 3      * superclass to subclass. 4      */ 5     private void writeSerialData(Object obj, ObjectStreamClass desc) 6         throws IOException 7     { 8         ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); 9         for (int i = 0; i < slots.length; i++) {10             ObjectStreamClass slotDesc = slots[i].desc;11             if (slotDesc.hasWriteObjectMethod()) {12                 PutFieldImpl oldPut = curPut;13                 curPut = null;14                 SerialCallbackContext oldContext = curContext;15 16                 if (extendedDebugInfo) {17                     debugInfoStack.push(18                         "custom writeObject data (class \"" +19                         slotDesc.getName() + "\")");20                 }21                 try {22                     curContext = new SerialCallbackContext(obj, slotDesc);23                     bout.setBlockDataMode(true);24                     slotDesc.invokeWriteObject(obj, this);25                     bout.setBlockDataMode(false);26                     bout.writeByte(TC_ENDBLOCKDATA);27                 } finally {28                     curContext.setUsed();29                     curContext = oldContext;30                     if (extendedDebugInfo) {31                         debugInfoStack.pop();32                     }33                 }34 35                 curPut = oldPut;36             } else {37                 defaultWriteFields(obj, slotDesc);38             }39         }40     }

 

这个方法 一目了然。



当然了我们还可以用Externalizable 这个接口来序列化。这个接口序列化的话 就和android里面是完全一样的。需要你自己制定序列化的细节。


 1 package com.burning.test; 2  3 import java.io.Externalizable; 4 import java.io.IOException; 5 import java.io.ObjectInput; 6 import java.io.ObjectInputStream; 7 import java.io.ObjectOutput; 8 import java.io.ObjectOutputStream; 9 import java.io.Serializable;10 11 public class Person implements Externalizable {12 13     /**14      * 15      */16     private static final long serialVersionUID = 1L;17 18     public Person() {19         // TODO Auto-generated constructor stub20         System.out.println("person的构造函数");21     }22 23     @Override24     public String toString() {25         return "Person [name=" + name + ", age=" + age + "]";26     }27 28     public String getName() {29         return name;30     }31 32     public void setName(String name) {33         this.name = name;34     }35 36     public int getAge() {37         return age;38     }39 40     public void setAge(int age) {41         this.age = age;42     }43 44      private String name;45 46     private int age;47 48     49     //这2个方法不写的话 是没有序列化的 也就是说这个序列化接口需要你自己去实现序列化的细节。50     @Override51     public void writeExternal(ObjectOutput out) throws IOException {52         // TODO Auto-generated method stub53         54     }55 56     @Override57     public void readExternal(ObjectInput in) throws IOException,58             ClassNotFoundException {59         // TODO Auto-generated method stub60         61     }62 63     64 65 }

这个地方要注意 用这种接口的时候在反序列化的时候 是调用了构造函数的~~可以打日志自己看一下。

 

在这里要额外提一下。如果单例对象也要实例化的话要注意一下 增加一个readResolve() 这样的方法。

不然你序列化的单例 再反序列化出来以后 会发现这是2个对象 并不相等。

 

 

 




 

java序列化详解(一)