目录
转换流
原理
转换流属于字符流,是字符流和字节流之间的桥梁
原理图如下:
作用:
可以根据字符集一次读取多个字节
读取数据不会乱码
指定字符集读写数据(JDK 11 之后有代替方法)
字节流可以使用字符流中的方法
InputStreamReader 转换输入流
构造方法
InputStreamReader(InputStream in)
- 功能:创建一个使用默认字符集的
InputStreamReader
对象。
- 功能:创建一个使用默认字符集的
InputStreamReader(InputStream in, String charsetName)
- 功能:创建一个使用指定字符集的
InputStreamReader
对象。
- 功能:创建一个使用指定字符集的
InputStreamReader(InputStream in, Charset cs)
- 功能:创建一个使用指定
Charset
对象的InputStreamReader
对象。
- 功能:创建一个使用指定
代码示例
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class demo {
public static void main(String[] args) throws IOException {
// 创建一个使用默认字符集的 InputStreamReader 对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("aaa.txt"));
int ch;
while((ch = isr.read()) != -1) {
System.out.print((char)ch);
}
// 创建一个使用指定字符集的 InputStreamReader 对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("aaa.txt"),"GBK");
int ch;
while((ch = isr.read()) != -1) {
System.out.print((char)ch);
}
// 创建一个使用指定 `Charset` 对象的 InputStreamReader 对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("aaa.txt"),Charset.forName("UTF-8"));
int ch;
while((ch = isr.read()) != -1) {
System.out.print((char)ch);
}
isr.close();
}
}
JDK 11 给出了以上方法的替代方法,可以直接使用 FileReader:
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
public class demo {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("aaa.txt",Charset.forName("UTF-8"));
int ch;
while((ch = fr.read()) != -1) {
System.out.println((char)ch);
}
fr.close();
}
}
OutputStreamWriter 转换输出流
构造方法
OutputStreamWriter(OutputStream out)
- 功能:创建一个使用默认字符集的
OutputStreamWriter
对象。
- 功能:创建一个使用默认字符集的
OutputStreamWriter(OutputStream out, String charsetName)
- 功能:创建一个使用指定字符集的
OutputStreamWriter
对象。
- 功能:创建一个使用指定字符集的
OutputStreamWriter(OutputStream out, Charset cs)
- 功能:创建一个使用指定
Charset
对象的OutputStreamWriter
对象。
- 功能:创建一个使用指定
代码示例
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class demo {
public static void main(String[] args) throws IOException {
// 创建一个使用默认字符集的 `OutputStreamWriter` 对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("bbb.txt"));
osw.write("月色真美");
// 创建一个使用指定字符集的 `OutputStreamWriter` 对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("bbb.txt"),"GBK");
osw.write("月色真美");
// 创建一个使用指定 `Charset` 对象的 `OutputStreamWriter` 对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("bbb.txt"),Charset.forName("UTF-8"));
osw.write("月色真美");
osw.close();
}
}
JDK 11 给出了以上方法的替代方法,可以直接使用 FileWriter:
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
public class demo {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("bbb.txt",Charset.forName("UTF-8"));
fw.write("月色真美");
fw.close();
}
}
练习
要求:利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
思路:
- 字节流读取中文会出现乱码,但是字符流不会
- 字节流没有 readLine 方法,但是缓冲流有
代码示例:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class demo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("aaa.txt");
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String str = br.readLine();
System.out.println(str);
br.close();
}
}
简化一下代码:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class demo {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("aaa.txt")));
String str = br.readLine();
System.out.println(str);
br.close();
}
}
序列化流
序列化流
序列化流,又叫对象操作输出流,可以把 Java 中的对象写到本地文件中
构造方法:public ObjectOutputStream(OutputStream out)
把基本流包装成高级流
成员方法:public final void writeObject(Object obj)
把对象序列化(写出)到文件中
代码示例:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Person implements Serializable{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
@Override
public String toString() {
return "姓名:" + name + " 年龄:" + age;
}
public static void main(String[] args) throws IOException {
Person person1 = new Person("张三", 25);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("bbb.txt"));
oos.writeObject(person1);
oos.close();
}
}
注意事项:
- 使用对象输出流将对象保存到文件时会出现 NotSerializableException 异常,需要让类实现 Serializable 接口
- Serializable 接口里面没有抽象方法,是标记型接口,一旦实现该接口就表示当前类可以被序列化
反序列化流
反序列化流,又叫对象操作输入流,可以把序列化到本地文件中的对象数据读取到程序中
构造方法:public ObjectInputStream(InputStream out)
把基本流包装成高级流
成员方法:public Object readObject()
把序列化到本地文件中的对象数据读取到程序中
代码示例:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("bbb.txt"));
Object o = ois.readObject();
System.out.println(o);
ois.close();
}
}
serialVersionUID
serialVersionUID
是 Java 中用于序列化和反序列化过程的一个重要的版本控制机制,下面从基本概念、作用、使用方式、默认生成规则等方面详细介绍。
基本概念
serialVersionUID
是一个序列化版本标识符,是一个 private static final long
类型的常量,它被用于 Serializable
接口的实现类中。当一个类实现了 Serializable
接口,意味着该类的对象可以被序列化(转换为字节流)和反序列化(从字节流恢复为对象),而 serialVersionUID
用于确保在反序列化时,加载的类与序列化时的类是兼容的。
作用
- 版本控制:在序列化对象时,该对象的
serialVersionUID
会被写入到序列化流中。在反序列化时,JVM 会将流中的serialVersionUID
与本地类的serialVersionUID
进行比较。如果两者相同,JVM 认为类的版本是兼容的,可以进行反序列化操作;如果不同,会抛出InvalidClassException
异常,防止因类的版本不兼容而导致数据丢失或错误。 - 保证兼容性:当类的结构发生变化(如添加、删除或修改了类的字段、方法等)时,通过手动指定
serialVersionUID
,可以在一定程度上控制类的兼容性。如果开发者认为类的变化不影响反序列化,可以保持serialVersionUID
不变,这样即使类的定义有小改动,反序列化过程仍然可以正常进行。
使用方式
手动指定
serialVersionUID
在实现
Serializable
接口的类中,手动定义一个serialVersionUID
常量,示例如下:import java.io.Serializable; public class Person implements Serializable { // 手动指定 serialVersionUID private static final long serialVersionUID = 123456789L; private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } }
自动生成
serialVersionUID
大多数集成开发环境(IDE),如 IntelliJ IDEA、Eclipse 等,都提供了自动生成
serialVersionUID
的功能。以 IntelliJ IDEA 为例,当一个类实现了Serializable
接口但未定义serialVersionUID
时,IDE 会在类名旁边显示一个提示,点击提示即可自动生成serialVersionUID
。
transient 关键字
如果一个对象中的某个成员变量的值不想被序列化,给该成员变量加 transient 关键字修饰,该关键字标记的成员变量不参与序列化过程
import java.io.*;
class User implements Serializable {
private String username;
// 用 transient 修饰 password,使其不被序列化
private transient String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User("testUser", "testPassword");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
oos.writeObject(user);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
User deserializedUser = (User) ois.readObject();
System.out.println("Username: " + deserializedUser.getUsername());
System.out.println("Password: " + deserializedUser.getPassword());
}
}
注意事项
- 版本管理:当类的结构发生重大变化,可能影响反序列化结果时,应该更新
serialVersionUID
,以避免旧版本的序列化数据被错误地反序列化。 - 安全性:
serialVersionUID
常量应该声明为private static final long
,以确保其不可修改,并且在类的所有实例中保持一致。