1、字符编码
2、数据流
- 数据流主要负责的是基本数据类型的读、写。写使用的是
java.io..DataOutputStream类,读使用的是java.io.DataInputStream类。
2.1、DataOutputStream 类
java.io.DataOutputStream类主要用于以适当的方式将基本数据类型写入输出流中。常用方法:

2.2、 DataInputStream 类
java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据。常用方法:

2.3、使用案例
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataStreamTest {
public static void main(String[] args) {
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new FileOutputStream("E:\\test\\k.txt"));
dos.writeInt(66); // 向文件中写入一个整数,大小4个字节
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

Java 中整数66的为4个字节,写成二进制是“0000 0000 0000 0000 0000 0000 0100 0010”,先写高位,在文本文件中,将这个二进制根据 ASCII 码转换为对应的字符显示,所以在文本文件中显示出来是就是“ B”(B 之前有3个“空格”)。
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class DataStreamTest {
public static void main(String[] args) {
DataInputStream dis = null;
try {
dis = new DataInputStream(new FileInputStream("E:\\test\\k.txt"));
int num = dis.readInt();
System.out.println(num); // 66
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 在使用这两个数据流的时候要注意,读、写的方法要对应,比如使用
writeInt方法对应要使用readInt方法。
3、对象流
对象流可以用来实现对象的读、写。
java.io.ObjectOutputStream类用于写,java.io.ObjectInputStream类用于读。(对象的)序列化(Serialization),是将对象转换为二进制流(字节序列)的过程;反之,将二进制流恢复为对象的过程称为“反序列化(Deserialization)”。
通过序列化,才能将对象进行数据持久化以及网络传输。
java.io.Serializable接口,被称为“序列化接口”,一旦类实现了该接口就表示该类“开启”了其序列化的功能。“序列化”是每一个类都具备的能力,只不过默认情况下处于一个“关闭”、“未激活”的状态,实现java.io.Serializable接口,相当于一个显式声明,表示“开启”、“激活”序列化。对于对象的读、写(输入、输出)操作而言,“写”对应序列化这一过程,“读”对应反序列化这一过程。
注意,一个类中序列化的范围仅限于非静态且没有被
transient关键字修饰的成员变量,而像静态成员变量、方法等均不会被序列化。(也很好理解,同一个类只让每一个对象独有的东西(一般就是指非静态成员变量)进行序列化)序列化的方案、形式有很多,这里所介绍的序列化,是 Java 提供的序列化方案,可以称为“Java 原生序列化”。像后来也会有“JSON 序列化”等其他方案。
3.1、ObjectOutputStream 类
java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。只能将支持(实现)
java.io.Serializable接口的对象写入流中。常用方法:

3.2、ObjectInputStream 类
java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。常用方法:

3.3、案例
将 User 类型的对象写入一文本文件,以及读出。
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 475730776938249795L;
private String account;
private String password;
private String phoneNumber;
public User() {
}
public User(String account, String password, String phoneNumber) {
this.account = account;
this.password = password;
this.phoneNumber = phoneNumber;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
@Override
public String toString() {
return "User{" +
"account='" + account + '\'' +
", password='" + password + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
'}';
}
}
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ObjectOutputTest {
public static void main(String[] args) {
ObjectOutputStream oos = null;
User user = new User("yscyber", "xyl_zw369", "182****0000");
try {
oos = new ObjectOutputStream(new FileOutputStream("E:\\test\\p.txt"));
oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectInputTest {
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("E:\\test\\p.txt"));
Object obj = ois.readObject();
if (obj instanceof User) {
User user = (User) obj;
System.out.println(user); // User{account='yscyber', password='xyl_zw369', phoneNumber='182****0000'}
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace(); // 一个 catch 的参数可以有多个异常类型,用“|”分隔
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.4、补充
3.4.1、序列化版本号
- 序列化版本号,就是上面
User类代码中定义的变量serialVersionUID。
private static final long serialVersionUID = 475730776938249795L
一般要求是,如果一个类实现了
java.io.Serializable接口,则必须显式地定义serialVersionUID。在进行序列化的时候,
serialVersionUID会被 JVM “加到”序列化所得到数据中;在反序列化的过程中,JVM 会从需要进行反序列化的数据中得到的serialVersionUID,与本地相应的类中定义的serialVersionUID相比较。如果一致,可以正常进行反序列化;如果不一致,反序列化失败,会报InvalidCastException异常。为什么要求必须显式定义
serialVersionUID?
如果不显式设置serialVersionUID,每次运行时,编译器会基于类的内部实现(包括类名、属性、方法等)来自动生成一个serialVersionUID。
可能会认为“自动生成还不好吗,省得写”,但是要注意,如果出现了一些情况使得这个类发生了一些修改(比如增加一个成员变量等),再运行的时候,serialVersionUID已经与以前的不一样了,因为编译器所自动生成的serialVersionUID是基于类的内容的,类的内容都变了,serialVersionUID肯定会变。这样的话,之前已经序列化的数据就没有办法再成功反序列化。
所以,必须显式设置serialVersionUID!只有这样,序列化、反序列化成功与否才能掌握在自己手里。
- 《阿里巴巴 码出高效》中关于序列化的阐述:

3.4.2、transient 关键字
transient是 Java 的关键字。该关键字只能修饰类的非静态成员变量(因为不论是否用transient修饰静态成员变量,静态的不参与序列化)。一旦被
transient修饰,被修饰的成员变量将不参与序列化,反序列化时,对应的成员变量的取值将是默认值(null、0等默认值)。
3.4.3、其他
- 当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一次
readObject方法就可以将整个集合的数据读取出来,从而避免了通过返回值进行是否达到文件末尾的判断(因为java.io.ObjectInputStream是没法判断是否到达文件末尾的)。
4、RandomAccessFile 类
java.io.RandomAccessFile类主要支持对随机访问文件的读写操作。这个类的最大特点是,可以根据需要“跳跃”读、写文件,也就是说,在读、写文件的时候,可以控制(或指定)读、写位置。
机制类似于“游标”,也就是说,不管在文件中的什么位置,执行一次读或写的操作后,自动向后移动。
常用方法:

- 案例:
/* 文本文件,文件中的内容是:Hello World! */
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
public static void main(String[] args) {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("E:\\test\\a.txt", "r");
int res = raf.read();
System.out.println((char) res); // H
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/* 文本文件,文件中的内容是:Hello World! */
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
public static void main(String[] args) {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("E:\\test\\a.txt", "r");
raf.seek(1); // 原读写位置向后偏移 1
int res = raf.read();
System.out.println((char) res); // e
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/* 文本文件,文件中的内容是:Hello World! */
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
public static void main(String[] args) {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile("E:\\test\\a.txt", "rw");
raf.seek(1);
raf.write('x'); // 写完后文件中内容变成:Hxllo World!
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
