第3节 IO(下)
一、.properties文件与Properties类
1.1 .properties文件介绍
.properties文件一种属性文件,以键值对 的格式存储内容,在Java中可以使用Properties
类来读取这个文件,一般来说它作为一些参数的存储,作为配置文件使用。
1.2 Properties类
它是Hashtable
的子类,使用键值对来存储数据。
构造方法:
方法:
1、根据键获取值:
2、将键值对打印到指定的输出流:
3、从输入字节流或字符流中读取全部内容:
4、返回所有键的枚举:
5、新增键值对:
6、存储到字节输出流或字符输出流,comment是备注,写在存储文件的开头:
7、返回所有的值,以Set类型:
看个例子:
package com.kaikeba.coreclasslibrary.io;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.util.Properties;
public class properties {
public static void main(String[] args) throws IOException {
//.properties文件与Properties类
//新建一个Properties对象
Properties ppt = new Properties();
//新建键值对
ppt.setProperty("name","三体");
ppt.setProperty("info","科幻小说");
//要写入的字符流
FileWriter fw = new FileWriter("book.properties");
//往fw中存储该键值对集合的对象,备注为"存储的图书"
ppt.store(fw, "存储的图书");
fw.close();
//从.properties文件读取属性
Properties ppt2 = new Properties();
Reader r = new FileReader("book.properties");
ppt2.load(r);
System.out.println(ppt.get("name"));
System.out.println(ppt.get("info"));
}
}
输出为:
三体
科幻小说
且book.properties文件中存储的内容为:
最上面是备注信息,使用了Unicode编码。
二、序列化与反序列化
Java序列化是指把java对象转换为字节序列的过程 ,Java反序列化是指把字节序列恢复为java对象的过程 。通过序列化和反序列化实现网络传输、本地存储的目的 。
2.1 Serializable实现Java序列化
要实现java对象的序列化,只要将类实现标识接口 ——Serializable
接口即可,不需要我们重写任何方法就可以实现序列化。
编写书籍类:
package com.kaikeba.coreclasslibrary.io.serializable;
import java.io.Serializable;
public class Book implements Serializable {
private String name;
private String info;
private Person user;
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", info='" + info + '\'' +
", user=" + user +
'}';
}
public Book() {
}
public Book(String name, String info, Person user) {
this.name = name;
this.info = info;
this.user = user;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public Person getUser() {
return user;
}
public void setUser(Person user) {
this.user = user;
}
}
编写借书人类:注意Book中有Person类的对象属性,所以Person类也要实现Serializable接口
package com.kaikeba.coreclasslibrary.io.serializable;
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Person() {
}
public Person(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;
}
}
编写序列化和反序列化:
package com.kaikeba.coreclasslibrary.io.serializable;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
Person p = new Person("smile", 23);
Book b = new Book("三体", "科幻小说", p);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
oos.writeObject(b);
oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
Book book = (Book) ois.readObject();
System.out.println(book);
}
}
结果如下:
原始对象:
Book{name='三体', info='科幻小说', user=Person{name='smile', age=23}}
反序列化之后的对象:
Book{name='三体', info='科幻小说', user=Person{name='smile', age=23}}
主要是使用到了ObjectOutputStream
和ObjectInputStream
类,ObjectOutputStream
类使用writeObject
将对象写到文件中,ObjectInputStream
类使用readObject
将对象从文件中读取出来。
2.2 部分属性的序列化
实现部分字段序列化的方式:
-
使用transient修饰符
-
使用static修饰符
-
默认方法writeObject和readObject
-
Externalizable实现
2.2.1 使用transient修饰符
修改实体类,将实体类中不想被序列化的属性添加transient修饰符。
public class Book implements Serializable {
private String name;
private transient String info;
private Person user;
...
public class Person implements Serializable {
private String name;
private transient int age;
...
结果如下:
原始对象:
Book{name='三体', info='科幻小说', user=Person{name='smile', age=23}}
反序列化之后的对象:
Book{name='三体', info='null', user=Person{name='smile', age=0}}
用transient
修饰的属性都没有被序列化,因此反序列化的结果也是默认值。
2.2.2 使用static修饰符
static
修饰符修饰的属性会参与序列化,但是如果在反序列化的时候对其进行重新赋值,反序列化后的结果会随之改变:
public class Book implements Serializable {
private String name;
private static String info;
private Person user;
...
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
Person p = new Person("smile", 23);
Book b = new Book("三体", "科幻小说", p);
System.out.println("原始对象:\n"+b);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
oos.writeObject(b);
oos.close();
//反序列化
b.setInfo("中国最牛的科幻小说");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
Book book = (Book) ois.readObject();
System.out.println("反序列化之后的对象:\n"+book);
}
}
结果:
原始对象:
Book{name='三体', info='科幻小说', user=Person{name='smile', age=23}}
反序列化之后的对象:
Book{name='三体', info='中国最牛的科幻小说', user=Person{name='smile', age=23}}
解释:其实就是静态属性被修改之后,因为所有对象都是用的同一个内容,所以反序列化的静态属性也就变了。
2.2.3 默认方法writeObject和readObject
在Book中添加两个方法:
public class Book implements Serializable {
private String name;
private String info;
private Person user;
private void writeObject(ObjectOutputStream oos) throws IOException {
System.out.println("writeObject---------------");
oos.writeObject(name);
oos.writeObject(user);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println("readObject----------------");
name = (String) ois.readObject();
user = (Person) ois.readObject();
}
...
结果为:
原始对象:
Book{name='三体', info='科幻小说', user=Person{name='smile', age=23}}
writeObject---------------
readObject----------------
反序列化之后的对象:
Book{name='三体', info='null', user=Person{name='smile', age=23}}
info属性没有写入两个函数中,就没有被序列化。
源码分析:
注意:添加的两个方法必须是private void
修饰的,否则不生效。
首先看一下Serializable
接口的描述:
ObjectStreamClass
类:在序列化(反序列化)的时候,ObjectOutputStream
(ObjectInputStream
)会寻找目标类中私有的writeObject
(readObject
)方法,赋值给变量writeObjectMethod
(readObjectMethod
)。
通过上面这段代码可以知道,如果writeObjectMethod ≠ null
(目标类中定义了私有的writeObject
方法),那么将调用目标类中的writeObject
方法,如果writeObject == null
,那么将调用默认的defaultWriteMethod
方法来读取目标类中的属性。
readObject
的调用逻辑和WriteObject
一样。
总结一下,如果目标类中没有定义私有的writeObject
或readObject
方法,那么序列化和反序列化的时候讲调用默认的方法来根据目标类中的属性来进行序列化和反序列化,而如果目标类中定义了私有的writeObject
或readObject
方法,那么序列化和反序列化的时候讲调用目标类指定的writeObject
或readObject
方法来实现。
2.3 Externalizable实现java序列化
实现部分属性序列化的方式的第四种,通过Externalizable
接口。
Externalizable
继承自Serializable
接口,使用Externalizable
接口需要实现readExternal
方法和writeExternal
方法来实现序列化和反序列化。
Externalizable
接口继承自Serializable
接口,所以实现Externalizable
接口也能实现序列化和反序列化。Externalizable
接口中定义了writeExternal
和readExternal
两个抽象方法,这两个方法其实对应Serializable
接口的writeObject
和readObject
方法,可以这样理解:Externalizable
接口被设计出来的目的就是为了抽象出writeObject
和readObject
这两个方法,但是目前这个接口使用的并不多。
三、Serializable vs Externalizable
区别 | Serializable | Externalizable |
---|---|---|
实现复杂度 | 实现简单,java对其有内建支持 | 实现复杂,由开发人员自己完成 |
执行效率 | 所有对象由java统一保存,性能较低 | 开发人员决定哪个对象保存,可能造成速度提升 |
保存信息 | 保存时占用空间大 | 部分存储,可能造成空间减少 |
使用频率 | 高 | 偏低 |