什么是序列化
Java是面向对象的编程语言,有时需要保存对象,并在下次使用时可以顺利还原该对象。由于这种需求很常见,所以Java API对此提供了支持,添加相关程序代码到标准类库中,并将保存和还原的过程称之为“对象序列化”。
序列化的用途
序列化主要有三个用途:
1.对象持久化(persistence)
对象持久化是指延长对象的存在时间。通常状况下,当程序结束时,程序中的对象不再存在。
如果通过序列化功能,将对象保存到文件中,就可以延长对象的存在时间,在下次程序运行是再恢复该对象。
一般来说,在大多数情况下,数据持久化一般是和数据库连接起来的,但是在某些特殊的情景下,也会用到利用序列化来实现数据持久化
比如:一个爬虫,利用set来实现url去重,因为为了将资源让给其他程序或其他等原因,在爬了一段时间后需要关闭一段时间(进程kill),那么如何让爬虫记得“它”已经爬取了那些网页呢?最简单的方法就是把set序列化存储为文件,下次爬取时在重新读取
2.对象复制
通过序列化,将对象保存在内存中,可以再通过此数据得到多个对象的副本。
3.对象传输
通过序列化,将对象转化字节流后,可以通过网络发送给另外的Java程序。
序列化实现
import org.apache.log4j.Logger;
import java.io.*;
/**
* 序列化于反序列化测试
*/
public class Test {
private static Logger logger=Logger.getLogger(Test.class);
/**
* 将对象序列化储存为文件
*
* @param obj 将储存的对象
*/
public static void writeObjectToFile(Object obj,String filePath) {
File file = new File(filePath);
FileOutputStream out;
try {
out = new FileOutputStream(file);
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
objOut.flush();
objOut.close();
} catch (IOException e) {
logger.error("写对象失败");
e.printStackTrace();
}
}
/**
* 将文件反序列化
*
* @return
*/
public static Object readObjectFromFile(String filePath) {
Object temp = null;
File file = new File(filePath);
FileInputStream in;
try {
in = new FileInputStream(file);
ObjectInputStream objIn = new ObjectInputStream(in);
temp = objIn.readObject();
objIn.close();
} catch (IOException e) {
logger.error("读取对象失败");
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return temp;
}
public static void main(String[] args) {
TestData zf =new TestData();
zf.setName("66666");
Test.writeObjectToFile(zf,"test");
TestData result = (TestData) Test.readObjectFromFile("test");
System.out.println(result.getName());
}
}
class TestData implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
关于Serializable
参数obj一定要实现Serializable接口,否则会抛出java.io.NotSerializableException异常。另外,如果写入的对象是一个容器,例如List、Map,也要保证容器中的每个元素也都是实现 了Serializable接口
序列化有如下规则
a)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
b)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
c)并非所有的对象都可以序列化,,至于为什么不可以,有很多原因了,比如:
1.Serializable只能保存对象的非静态成员交量,不能保存任何的成员方法和静态的成员变量,而且串行化保存的只是变量的值,对于变量的任何修饰符都不能保存。
举个很有欺骗性的例子
在上述代码中将TestData 类的name熟悉改为静态,然后运行
class TestData implements Serializable {
private static String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
结果:
66666
看起来是不是感觉。。。。。静态成员变量也是能够序列化的啊~~
然而。。。其实出现这种情况的原因是因为测试的序列化和反序列化都在同一个机器(jvm),因为这个jvm已经把静态成员变量加载进来了,所以获取的是加载好的静态变量。。。而不是反序列化得到的
如果将序列化和反序列化分开进行(不在一个jvm中),就不会出现这种很有欺骗性的情况了
总而言之:
静态成员属于类级别的,所以不能序列化,序列化只是序列化了对象而已。
2.资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分 配,而且也是没有必要这样实现
关于serialVersionUID
serialVersionUID表示:“串行化版本统一标识符”(serial version universal identifier),简称UID
serialVersionUID必须定义成下面这种形式:static final long serialVersionUID = xxxL;(因为在序列化相关类中已经对serialVersionUID 做了特殊处理,所以可以标注为static)
serialVersionUID 用来表明类的不同版本间的兼容性。有两种生成方式: 一个是默认的1L;另一种是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段 。
显式地定义serialVersionUID有两种用途:
1)在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;在某些场合,不希望类的不同版本对序列化兼容,
因此需要确保类的不同版本具有不同的serialVersionUID。
2)当你序列化了一个类实例后,希望更改一个字段或添加一个字段,不设置serialVersionUID,所做的任何更改都将导致无法反序化旧有实例,并在反序列化时抛出一个异常。
如果你添加了serialVersionUID,在反序列旧有实例时,新添加或更改的字段值将设为初始化值(对象为null,基本类型为相应的初始默认值),字段被删除将不设置。