【Java】I/O 流篇 —— 转换流与序列化流

发布于:2025-02-27 ⋅ 阅读:(14) ⋅ 点赞:(0)

转换流

原理

  1. 转换流属于字符流,是字符流和字节流之间的桥梁

  2. 原理图如下:

在这里插入图片描述

  1. 作用:

    • 可以根据字符集一次读取多个字节

    • 读取数据不会乱码

    • 指定字符集读写数据(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();
	}	
}

练习

要求:利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码

思路:

  1. 字节流读取中文会出现乱码,但是字符流不会
  2. 字节流没有 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();
	 }
}

注意事项:

  1. 使用对象输出流将对象保存到文件时会出现 NotSerializableException 异常,需要让类实现 Serializable 接口
  2. 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 用于确保在反序列化时,加载的类与序列化时的类是兼容的。

作用

  1. 版本控制:在序列化对象时,该对象的 serialVersionUID 会被写入到序列化流中。在反序列化时,JVM 会将流中的 serialVersionUID 与本地类的 serialVersionUID 进行比较。如果两者相同,JVM 认为类的版本是兼容的,可以进行反序列化操作;如果不同,会抛出 InvalidClassException 异常,防止因类的版本不兼容而导致数据丢失或错误。
  2. 保证兼容性:当类的结构发生变化(如添加、删除或修改了类的字段、方法等)时,通过手动指定 serialVersionUID,可以在一定程度上控制类的兼容性。如果开发者认为类的变化不影响反序列化,可以保持 serialVersionUID 不变,这样即使类的定义有小改动,反序列化过程仍然可以正常进行。

使用方式

  1. 手动指定 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;
        }
    }
    
  2. 自动生成 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,以确保其不可修改,并且在类的所有实例中保持一致。