转换流、对象操作流
- 1 转换流
- 1.1 构造方法
- 1.2 指定编码读写
- 2 对象操作流
- 2.1 对象操作流概述
- 2.2 对象序列化流
- 2.3 对象反序列化流
- 2.4 案例
1 转换流
1.1 构造方法
转换流就是来进行字节流和字符流之间转换的
- InputStreamReader:是从字节流到字符流的桥梁,父类是Reader
- 它读取字节,并使用指定的编码将其解码为字符
- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
- OutputStreamWriter:是从字符流到字节流的桥梁,父类是Writer
- 使用指定的编码将写入的字符编码为字节
- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
1.2 指定编码读写
public static void main(String[] args) throws IOException {
//method1();
//method2();
//在JDK11之后,字符流新推出了一个构造,也可以指定编码表
FileReader fr = new FileReader("C:\\Users\\apple\\Desktop\\a.txt", Charset.forName("gbk"));
int ch;
while ((ch = fr.read())!=-1){
System.out.println((char) ch);
}
fr.close();
}
private static void method2() throws IOException {
//如何解决乱码现象
//文件是什么码表,那么咱们就必须使用什么码表去读取.
//我们就要指定使用GBK码表去读取文件.
//读文件
InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\apple\\Desktop\\a.txt"),"gbk");
int ch;
while((ch = isr.read())!=-1){
System.out.println((char) ch);
}
isr.close();
//写文件,如果我们使用idea自动创建的文件并写内容,那么windows系统中默认该文件编码utf-8
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("C:\\Users\\apple\\Desktop\\b.txt"),"UTF-8");
osw.write("我爱学习,谁也别打扰我");
osw.close();
}
//这个方法直接读取会产生乱码
//因为文件是GBK码表
//而idea默认的是UTF-8编码格式.
//所以两者不一致,导致乱码
private static void method1() throws IOException {
FileReader fr = new FileReader("C:\\Users\\apple\\Desktop\\a.txt");
int ch;
while ((ch = fr.read())!=-1){
System.out.println((char) ch);
}
fr.close();
}
2 对象操作流
2.1 对象操作流概述
需求:把这个用户信息保存到本地文件去
public static void main(String[] args) throws IOException {
User user = new User("zhangsan","qwer");
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
bw.write(user.getUsername());
bw.newLine();
bw.write(user.getPassword());
bw.close();
}
但是,对于该例有个问题,就是将用户信息保存到本地文件后,任何人都可以看到这个信息,数据存在安全隐患,那么如何解决该问题呢?
对象操作流:
- 可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中
- 对象操作流分为两类:
- 对象操作输出流(对象序列化流):就是将对象写到本地文件中 ,或者在网络中传输对象
- 对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象
2.2 对象序列化流
- 对象序列化介绍
- 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
- 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
- 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
- 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
- 对象序列化流: ObjectOutputStream
- 将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
- 构造方法
- 序列化对象的方法
- 示例代码
User类
public class User implements Serializable {
//serialVersionUID 序列号
//如果我们自己没有定义,那么虚拟机会根据类中的信息会自动的计算出一个序列号.
//问题:如果我们修改了类中的信息.那么虚拟机会再次计算出一个序列号.
//第一步:把User对象序列化到本地. --- -5824992206458892149
//第二步:修改了javabean类. 导致 --- 类中的序列号 4900133124572371851
//第三步:把文件中的对象读到内存. 本地中的序列号和类中的序列号不一致了.
//解决?
//不让虚拟机帮我们自动计算,我们自己手动给出.而且这个值不要变.
//private static final long serialVersionUID = 1L;
private String username;
private String password;
//private transient String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
注意:
- 如果想要这个类的对象能被序列化,那么这个类必须要实现一个接口Serializable(否则运行测试类会报错)
- Serializable 接口的意义
- 称之为是一个标记性接口,里面没有任何的抽象方法(实现一个接口就要实现里面所有的抽象方法)只要一个类实现了这个Serializable接口,那么就表示这个类的对象可以被序列化。
- 实现该接口,不需要重写任何方法
测试类
public static void main(String[] args) throws IOException {
User user = new User("zhangsan","qwer");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
oos.writeObject(user);
oos.close();
}
2.3 对象反序列化流
- 对象反序列化流: ObjectInputStream
ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
- 构造方法
- 反序列化对象的方法
- 示例代码
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
User o = (User) ois.readObject();
System.out.println(o);
ois.close();
}
用对象序列化流序列化了一个对象后,假如我们修改了对象所属的JavaBean类,读取数据会不会造成问题?
- 会出问题,会抛出
InvalidClassException
异常
出了问题如何解决?
- 重新序列化
- 给对象所属的类加一个serialVersionUID
private static final long serialVersionUID = 42L;
如果一个对象中的某个成员变量的值不想被序列化,又该如何处理?
- 给该成员变量加
transient
关键字修饰,该关键字标记的成员变量不参与序列化过程
2.4 案例
- 案例需求
- 创建多个学生类对象写到文件中,再次读取到内存中
- 实现步骤
- 创建序列化流对象
- 创建多个学生对象
- 将学生对象添加到集合中
- 将集合对象序列化到文件中
- 创建反序列化流对象
- 将文件中的对象数据,读取到内存中
- 代码实现
已存在标准类 Student 类
测试类1
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student s1 = new Student("杜子腾",16);
Student s2 = new Student("张三",23);
Student s3 = new Student("李四",24);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
oos.writeObject(s1);
oos.writeObject(s2);
oos.writeObject(s3);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
Object obj;
/* while((obj = ois.readObject()) != null){// 对象输入流(反序列化流)的文件读取结束不可以用null来作为标志
System.out.println(obj);
}*/
while(true){
try {
Object o = ois.readObject();
System.out.println(o);
} catch (EOFException e) {//EOFException异常表示在输入的过程中意外地到达文件结束或流结束
break;
}
}
ois.close();
}
测试类2
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student s1 = new Student("杜子腾",16);
Student s2 = new Student("张三",23);
Student s3 = new Student("李四",24);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
ArrayList<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
//我们往本地文件中写的就是一个集合
oos.writeObject(list);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
ArrayList<Student> list2 = (ArrayList<Student>) ois.readObject();
for (Student student : list2) {
System.out.println(student);
}
ois.close();
}