使用Serializable序列化,只要实现Serializable接口即可。一般情况下都会显示设置静态成员变量serialVersionUID为固定值。序列化时使用ObjectOutputStream写入,反序列化时使用ObjectInputStream读出。
如此简单,谁都会。但这是我碰到复杂点的情况,特作以下总结:
1、Serializable可继承:父类实现了序列化,子类也会自动实现序列化
PersonBean.java:
public class PersonBean implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private int age;
public PersonBean(String name, int age) {
this.name = name;
this.age = age;
}
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 name + "$$$" + age;
}
}
ProgrammerBean.java:
public class ProgrammerBean extends PersonBean {
private String language;
public ProgrammerBean(String name, int age, String language) {
super(name, age);
this.language = language;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
@Override
public String toString() {
return getName() + "@@@" + getAge() + "@@@" + language;
}
}
SerialTest.java:
package com.example;
import com.example.serialBean.PersonBean;
import com.example.serialBean.ProgrammerBean;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* Created by Ralap on 2016/7/11.
*/
public class SerialTest {
private static final String filePath = System.getProperty("user.dir");
private static final String fileName = "serialTest.st";
public static void test() {
// 1、Serializable可继承
testExtends();
// 2、transient和static修饰变量,使其不参加序列化
// 3、含有不可序列化的对象,自定义writeObject()和readObject()
// 4、集合序列化
}
private static void testExtends() {
PersonBean personBean = new PersonBean("Boss", 38);
ProgrammerBean programmerBean = new ProgrammerBean("hacker", 29, "Java");
printfn("==========Before Serialize :==========");
printfn(personBean.toString());
printfn(programmerBean.toString());
File file = createFile();
if (file == null) {
printfn("create file fail");
return;
}
// 序列化
serialize(file, personBean, programmerBean);
// 反序列化
Object[] objs = new Object[2];
unserialize(file, objs);
PersonBean perBean = (PersonBean) objs[0];
ProgrammerBean proBean = (ProgrammerBean) objs[1];
// 打印
printfn("==========After Unserialize:==========");
printfn(perBean.toString());
printfn(proBean.toString());
}
/**
* 序列化
*
* @param file 序列化目标文件
* @param objs 序列化对象
*/
private static void serialize(File file, Object... objs) {
ObjectOutputStream objectOS = null;
try {
FileOutputStream fos = new FileOutputStream(file);
objectOS = new ObjectOutputStream(fos);
for (Object obj : objs) {
objectOS.writeObject(obj);
}
objectOS.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOS != null) {
try {
objectOS.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 反序列化
* @param file 反序列化文件
* @return 反序列化出来的对象数组
*/
private static void unserialize(File file, Object[] objs) {
ObjectInputStream objectIS = null;
try {
FileInputStream fis = new FileInputStream(file);
objectIS = new ObjectInputStream(fis);
Object obj;
int offset = 0;
while ((obj = objectIS.readObject()) != null) {
objs[offset++] = obj;
if (offset >= objs.length) {
return;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (objectIS != null) {
try {
objectIS.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static void printf(String s) {
System.out.printf("%s", s);
}
private static void printfn(String s) {
System.out.printf("%s\n", s);
}
/**
* 创建文件
* 存在:删除后新建。不存在:新建
*/
private static File createFile() {
File file = new File(filePath, fileName);
try {
if (file.exists()) {
if (!file.delete()) {
return null;
}
}
if (!file.createNewFile()) {
return null;
}
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
}
结果:
2、static和transient修饰的变量,将不参加序列化
序列化只对类中的filed字段属性进行序列化,且序列化的只是类的实例对象的属性类型及值,所以method方法和static修饰的属性将不被序列化。如果要让非static属性也不序列化,使用transient。
扩展:另一个修饰变量符volatile,易变的意思。一般用于多线程中,因为每个线程都会有自己独立的内存空间,共享变量会从主内存拷贝一份到自己的内存中,操作的是自己内存中的数值,在进入线程或退出同步代码块时,才与共享中的成员变量进行比对、同步,这样可能导致其他线程获取到的不是最新值。所以,用volatile来修饰共享成员变量,在每次使用变量时都会强迫从共享内存中重新读取共享变量的值。
static:静态的,被所有对象共享,非某个对象私有,不会被持久化
transient:代表瞬间的意思,表示不会被持久化
把上面ProgrammerBean和SerialTest进行修改
ProgrammerBean.java
public class ProgrammerBean extends PersonBean {
private String language;
private static String belongIndustry = "Linux"; // static修饰
transient private boolean hasGF; // transient修饰。boolean默认值是false,注意比较
public ProgrammerBean(String name, int age, String language) {
super(name, age);
this.language = language;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public static String getBelongIndustry() {
return belongIndustry;
}
public static void setBelongIndustry(String belongIndustry) {
ProgrammerBean.belongIndustry = belongIndustry;
}
public boolean isHasGF() {
return hasGF;
}
public void setHasGF(boolean hasGF) {
this.hasGF = hasGF;
}
@Override
public String toString() {
return getName() + "^^^" + getAge() + "^^^" + language + "^^^" + belongIndustry + "^^^" + hasGF;
}
}
SerialTest.java修改部分
public static void test() {
// 1、Serializable可继承
// testExtends();
// 2、transient和static修饰变量,使其不参加序列化
testNotSerial();
// 3、含有不可序列化的对象,自定义writeObject()和readObject()
// 4、集合序列化
}
private static void testNotSerial() {
PersonBean personBean = new PersonBean("Boss", 38);
ProgrammerBean programmerBean = new ProgrammerBean("hacker", 29, "Java");
programmerBean.setHasGF(true);
printfn("==========Before Serialize :==========");
printfn(personBean.toString());
printfn(programmerBean.toString());
File file = createFile();
if (file == null) {
printfn("create file fail");
return;
}
// 序列化
serialize(file, personBean, programmerBean);
// 序列化后修改static属性值
ProgrammerBean.setBelongIndustry("Android");
// 反序列化
Object[] objs = new Object[2];
unserialize(file, objs);
PersonBean perBean = (PersonBean) objs[0];
ProgrammerBean proBean = (ProgrammerBean) objs[1];
// 打印
printfn("==========After Unserialize:==========");
printfn(perBean.toString());
printfn(proBean.toString());
}
结果:
3、使用自定义序列化使不可序列化的类序列化
一些类没有实现Serializable,就不能序列化。如ProgrammerBean中包含了一个不可序列化的类InterestBean。
ProgrammerBean.java
public class ProgrammerBean extends PersonBean {
private String languages;
InterestBean interest;
public ProgrammerBean(String name, int age, String languages) {
super(name, age);
this.languages = languages;
}
public String getLanguages() {
return languages;
}
public void setLanguages(String languages) {
this.languages = languages;
}
public InterestBean getInterest() {
return interest;
}
public void setInterest(InterestBean interest) {
this.interest = interest;
}
@Override
public String toString() {
return getName() + "^^^" + getAge() + "^^^" + languages + ":::" + interest.getType() + "^^^" + interest.getNames();
}
}
InterestBean.java
public class InterestBean{
private String type;
private String names;
public InterestBean(String type, String names) {
this.type = type;
this.names = names;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getNames() {
return names;
}
public void setNames(String names) {
this.names = names;
}
}
SerialTest.java部分修改(从别人那学习到更漂亮的序列化代码,因此作了修改)
package com.example;
import com.example.serialBean.InterestBean;
import com.example.serialBean.ProgrammerBean;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* Created by Ralap on 2016/7/11.
*/
public class SerialTest {
private static final String filePath = System.getProperty("user.dir");
private static final String fileName = "serialTest.st";
public static void test() {
// 1、Serializable可继承
// testExtends();
// 2、transient和static修饰变量,使其不参加序列化
// testNotSerial();
// 3、含有不可序列化的对象,自定义writeObject()和readObject()
testCustomSerial();
// 4、集合序列化
}
private static void testCustomSerial() {
ProgrammerBean programmerBean = new ProgrammerBean("hacker", 29, "Java");
InterestBean interest = new InterestBean("gril", "Gril God");
programmerBean.setInterest(interest);
printfn("==========Before Serialize :==========");
printfn(programmerBean.toString());
File file = createFile();
if (file == null) {
printfn("create file fail");
return;
}
// 序列化
serialize(file, programmerBean);
// 反序列化
ProgrammerBean proBean = unserialize(file);
// 打印
printfn("==========After Unserialize:==========");
printfn(proBean.toString());
}
/**
* 序列化
*
* @param file 序列化目标文件
* @param object 序列化对象
*/
private static <T> void serialize(final File file, final T object) {
ObjectOutputStream objectOS = null;
try {
FileOutputStream fos = new FileOutputStream(file);
objectOS = new ObjectOutputStream(fos);
objectOS.writeObject(object);
objectOS.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOS != null) {
try {
objectOS.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 反序列化
* @param file 反序列化文件
* @return 反序列化出来的对象数组
*/
private static <T> T unserialize(final File file) {
ObjectInputStream objectIS = null;
T retObj = null;
try {
FileInputStream fis = new FileInputStream(file);
objectIS = new ObjectInputStream(fis);
retObj = (T)objectIS.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (objectIS != null) {
try {
objectIS.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return retObj;
}
}
上面在写入和读出的时候都会报不可序列化异常:NotSerializableException
如果这是自己的类,实现一下Serializable就OK了。但万一这是别人封装好的,不能修改,那怎么办?这时就可以使用自定义的序列化方法。在类中
【1】给该属性添加修饰符transient,为不可序列化。
【2】加上下面三个方法(一般前面两个就可以)并实现之:
// Serializable接口中没有抽象方法,这些方法不是重写接口的方法,且他们都是private,但序列化时会自动调用这里的方法。这就是机制
private void writeObject(ObjectOutputStream out) throws IOException {
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
}
private void readObjectNoData() throws ObjectStreamException
{
}
修改后的ProgrammerBean.java
public class ProgrammerBean extends PersonBean {
private String languages;
transient private InterestBean interest;
public ProgrammerBean(String name, int age, String languages) {
super(name, age);
this.languages = languages;
}
public String getLanguages() {
return languages;
}
public void setLanguages(String languages) {
this.languages = languages;
}
public InterestBean getInterest() {
return interest;
}
public void setInterest(InterestBean interest) {
this.interest = interest;
}
@Override
public String toString() {
return getName() + "^^^" + getAge() + "^^^" + languages + ":::" + interest.getType() + "^^^" + interest.getNames();
}
private void writeObject(ObjectOutputStream out) throws IOException {
// 先使用默认写入,会自动把可序列化的序列化
out.defaultWriteObject();
out.writeUTF(interest.getType());
out.writeUTF(interest.getNames());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
// 先使用默认读出,会自动把可序列化的读出
in.defaultReadObject();
// interest默认是null,这里必须新建。且读出与写入顺序必须一致
interest = new InterestBean(in.readUTF(), in.readUTF());
}
private void readObjectNoData() throws ObjectStreamException
{
throw new InvalidObjectException("No Stream Data");
}
}
这样结果就出来:
4、集合的序列化
ArrayList实现了Serializable接口,完全可以序列化。这个完全当做是验证测试
SerialTest.java部分修改
public class SerialTest {
private static final String filePath = System.getProperty("user.dir");
private static final String fileName = "serialTest.st";
public static void test() {
// 1、Serializable可继承
// testExtends();
// 2、transient和static修饰变量,使其不参加序列化
// testNotSerial();
// 3、含有不可序列化的对象,自定义writeObject()和readObject()
// testCustomSerial();
// 4、集合序列化
testListSerial();
}
private static void testListSerial() {
List<ProgrammerBean> list = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
ProgrammerBean programmerBean = new ProgrammerBean("hacker" + i, 29, "Java");
programmerBean.setInterest(new InterestBean("gril", "Gril God" + i));
list.add(programmerBean);
}
// printfn("==========Before Serialize :==========");
// printfn(programmerBean.toString());
File file = createFile();
if (file == null) {
printfn("create file fail");
return;
}
// 序列化
serialize(file, list);
// 反序列化
List<ProgrammerBean> programmerList = unserialize(file);
// 打印
printfn("==========After Unserialize:==========");
for (ProgrammerBean pro : programmerList) {
printfn(pro.toString());
}
}
结果:
……
serialTest.st文件的大小:247 KB (252,987 字节)
我们目前使用的是XML(json因故暂时不考虑),XML也可以实现序列化与反序列化,具体有什么区别,还没研究,但先来简单计算下文件大小:
①Serializable中,UTF-8编码格式,都是英文字母或数字,每个字母或数字占一个字节,一个bean中的有意义的数据(属性值)大概是33个字节,33*5000 = 165,000字节。其他的占空间的都是包名+类名,属性类型。
②XML中,有意义的数据不变,也是165,000字节,其他的主要是tag占空间。保守假设每个tag名称为5字节,格式是<tag01>value</tag01>
,也就是说每个属性值还要是15字节,每个bean共有6个属性。这样一个bean就是7*15 = 105字节,tag总空间:105*5000 = 525,000字节。
So,从大小上来说,还是Serializable节省空间。代码还不用写xml这样复杂的序列化与反序列化,看到那么多Bean,简单重复的操作,真想写个框架(反射+注解)改掉它。老大说xml执行效率高,也许吧,下次验证下就知道了。
下篇进攻XML……
补充(2016/7/12 18:00):
上面第一种情况是父类序列化,子类会自动实现序列化。如果父类不序列化,子类需要序列化。如上,PersonBean不实现Serializable,而ProgrammerBean实现Serializable,其他保持原样不变。这样,子类对象在序列化时正常,但反序列化时会报以下异常:
java.io.InvalidClassException: com.example.serialBean.ProgrammerBean; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1772)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
这表示,在反序列化时检测的时候抛的异常,提示该类没有有效的构造方法。
解决办法:在父类中添加无参构造方法。具体原因,不详,百度也没找到满意的答案(希望哪位大神帮忙解惑下)。这让我想起了以前一位老师说过,永远给出无参构造方法。