对象具有状态和行为两种属性。行为存在类中的方法中,想要保存状态有多种方法,这里介绍两种:
一是保存整个当前对象本身(通过序列化);一是将对象中各个状态值保存到文件中(这种方式可以给其他非JAVA程序用),如果其他程序需要用到的状态,可以通过特定的格式存储到文本中,方便其他程序解析提取数据
序列化(serialization)
只有自己写的JAVA程序用到存储的数据时,使用序列化
序列化将整个对象写入到文件中,该对象引用的实例变量、所有被引用的对象都会被序列化
创建一个可以被序列化的类
只有实现了Serializable接口的类可以被序列化(实际上Serializable没有任何方法需要实现,它的目的只是声明这个类是可以被序列化的)
实现了Serializable接口的类,它的子类也是都可以被序列化的
需要import java.io.Serializable
例如:
import java.io.Serializable;
public class Box implements Serializable{
private int width;
private int height;
transient int size;\transient关键字,标识该实例变量不会被序列化
private Duck duck =new Duck();
public void setWidth(int w){
width=w;
}
public void setHeight(int h){
height=h;
}
width=w;
}
public void setHeight(int h){
height=h;
}
}
实例变量width和height、duck的值,在序列化时值会被保存
实例变量size不会被序列化,因为用transient关键字的实例变量不进行序列化,会以null等默认值返回(有可能有些引用变量作者没有进行序列化,或者某些字段没有序列化的意义)
序列化是全有或全无的,一部分序列化失败了就不会保存:如果实例变量的引用对象duck,如果类Duck没有实现序列化的话,当前类也不能被序列化,否则会报错
序列化的步骤
第一步:创建FileOutputStream,FileOutputStream把字节写入文件
FileOutputStream fs = new FileOutputStream("foo.ser”);\如果文件footsore不存在,会自动创建该文件
第二步:创建ObjectOutputStream,ObjectOutputStream把对象转换成可以写入串流的数据
ObjectOutputStream os = new ObjectOutputStream(fs);\fs是一个FileOutputStream对象
第三步:写入对象,ObjectOutputStream调用writeObject方法把对象打成串流送到FileOutputStream来写入文件
os.writeObject(myBox);\myBox必须是一个实现了Serializable接口的类,是可以序列化的
第四步:关闭ObjectOutputStream
os.close();\关闭所关联的输出串流
反序列化【解序列化】(Deserialization):还原对象
序列化的反向操作,将对象恢复到存储时的状态,transient的变量不会恢复当时的状态,只会恢复成默认值,例如null。如果是不可序列化的类,会重新执行构造函数。序列化的类不会执行构造函数,会将新对象放到对上。
对象是从stream中读出来的,Java虚拟机通过存储信息判断要恢复的对象class的类型,如果找不到或无法加载该类,会抛出异常。
解序列化的步骤:
第一步:创建FileInputStream,读取文件字节,如果文件不存在会抛出异常
FileInputStream fileStream = new FileInputStream("foo.ser”);\如果文件不存在,会抛出异常
第二步:创建ObjectInputStream,将自己转换成对象的stream
ObjectInputStream os=new ObjectInputStream(fileStream);
第三步:读取对象,会按照os.writeObject(xx)存储的顺序读取,与写入顺序相同
Object one=os.readObject();
Object two=os.readObject();
Object two=os.readObject();
第四步:转换对象类型。默认返回的是Object类型,需要强转换为存储时的类型
GamecCharacter test1=(GameCharacter) one;
GamecCharacter test2=(GameCharacter) two;
GamecCharacter test2=(GameCharacter) two;
第五步:关闭ObjectInputStream
os.close();
serialVersionUID
用来解序列化还原时比对,是否和当前Java虚拟机上的类的UID一致,如果版本不符就会在还原过程中抛出异常。
查询类的serialVersionUID:在Java Development Kit中使用命令【serialver 类名】查询
具体步骤如下,例如java文件为Box.java,进入java文件目录,通过javac xxx.java把文件编译为.class文件,然后通过serialver xxx查询
如果完全想自己控制,可以将serialVersionUID写在class类中,但是这种就需要自己去承担类变动后,还原的对象一些实例变量可能不符的后果
使用方法如下:
public class Dog{
static final long serialVersionUID=xxxxxx;
//其他代码
}
这种情况如果后续类新增了字段或实例,反序列化时会将新增的字段初始为默认值
序列化总结的一些点
可以通过序列化来保存对象的状态
Stream是连接串流(表示源和目的地)或链接用的串流(衔接连接串流,例如从FileOutputStream到ObjectOutputStream)
只有实现了序列化接口的类才可以被序列化,所有实例都会被序列化(除非用来transient关键词会跳过)
静态变量不会被序列化,每个类共享一个静态变量
文件的输入输出
有时候需要把对象的状态存储为单纯文件,来提供给其他非JAVA得应用程序,所以这里讲了文件的输入和输出
java.io.File class
File这个类代表磁盘上的文件,但是不是文件的内容。可以把它想象成文件路径
File的简单应用
1.创建代表现存盘文件的File对象
File f = new File(“xxxx/mycode.txt”)
2.建立新的目录
File dir = new File(“chapter")
缓冲区
可以理解为购物车,为了省去磁盘操作的时间,,可以提高IO读写的效率
BufferedReader和BufferedWrite各被分配了8192个字的缓冲区
BufferedWrite:使用缓冲区来进行写操作,文件不会直接写入磁盘,而是先写入到缓冲区,当缓冲区满了时一次性写入到磁盘,减少直接操作磁盘的次数,提高效率。或者通过write.flush()来强制缓存区立即写入
用法:
只通过FileWrite写入的方法:
File fs=new File(“filename”);
FileWriter fw=new FileWriter(fs);
fs.write(“xxx”);
通过缓冲区进行写入的方法:
File fs=new File(“filename”);
FileWriter fw=new FileWriter(fs);
BufferedWrite bw=new BufferedWrite(fw);
bw.write(“xxx”);
BufferedReader:将FileReader链接到BufferedReader也会提升效率,BufferedReader会将文件读入的字节置入缓冲区。当以后使用read()等操作进行读时,会先从缓存中读取,当缓存中为空时再从文件中直接读取
用法:
File fs=new File(“filename”);
FileReader fr=new FileReader(fs);
BufferedReader br=new BufferedReader(fr);
while(br.readeLine() != null){//下面就是读取每一行的文件并打印了
System.out.println(br.readeLine() );
}