一.字符流
字符流对数据的操作是以一个个字符为单位的,字符流只能读文本文件,并将读到的字节按照编码表转为对应的字符,Reader和Writer是字符流的两个最大的抽象类,InputStreamReader和OutputStreamWriter分别继承了Reader和Writer,它俩的功能就是将读取到的字节转换为字符,所以又被称为转换流
1. FileReader和FileWriter读写文件
FileReader常用方法 | 方法阐述 |
---|---|
FileReader(String fileName) | 构造方法,用来指定要读的文件 |
int read() | 读取一个字符,返回读到的字符的码值,如果读到-1则表示读取完毕 |
int read(char[] chars) | 一次读一个字符数组,返回实际读取到的字符个数 |
FileWriter常用方法 | 方法阐述 |
---|---|
FileWriter(String fileName,boolean append) | 构造方法,用来指定要写入的文件,并且可以设置是追加,还是覆盖当前内容 |
void writer(int c) | 一次写一个字符 |
void writer(char[] chars,in off,int len) | 一次写一个字符数组,从字符数组的off位置开始,写len个 |
1.1 一个字符一个字符读
public class CharDemo1 {
public static void main(String[] args) throws IOException {
/*
字符流只能读文本文件,会将读到字节结合编码表转换为一个字符编码
*/
//一次读一个字符
FileReader fileReader = new FileReader("E:/demo.txt");
FileWriter fileWriter = new FileWriter("E:/demo1.txt");
int c = 0;
while((c = fileReader.read())!=-1){
fileWriter.write(c);
}
fileReader.close();
fileWriter.close();
}
}
1.2 一次读一个字符数组
public class CharDemo2 {
public static void main(String[] args) throws IOException {
/*
字符流只能读文本文件,会将读到字节结合编码表转换为一个字符编码
*/
//一次读一个char数组
FileReader fileReader = new FileReader("E:/demo.txt");
FileWriter fileWriter = new FileWriter("E:/demo1.txt");
int size = 0;
char[] chars = new char[10];
while((size = fileReader.read(chars))!=-1){
fileWriter.write(chars,0,size);
}
fileReader.close();
fileWriter.close();
}
}
2. 字符缓冲流(包装流)
BufferedReader常用方法 | 方法阐述 |
---|---|
BufferedReader(Reader in) | 构造方法,可以传入一个Reader类对象 |
String readLine() | 一次读一行数据 |
BufferedWrite常用方法 | 方法阐述 |
---|---|
BufferedWriter(Writer our) | 构造方法,可以传一个Writer类对象 |
void write(String s) | 一次写一个字符串 |
void newLine() | 写一个换行符 |
2.1 BufferedReader和BufferedWriter读写文件
BufferReader和BufferWriter是两个包装流,可以对节点流FileReader和FileWriter进行包装,支持一次读写一行数据大大提高了数据的读写效率,FileWriter的构造方法中可以增加一个布尔类型的参数,表示写入文件的内容不会覆盖原内容,而是追加到文件原有内容的后面,bufferWriter中的newLine()方法表示写入一个换行符到文件中
public class CharDemo3 {
/*
字符流只能读文本文件,会将读到字节结合编码表转为一个字符编码
*/
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("E:/demo.txt");
FileWriter fileWriter = new FileWriter("E:/demo1.txt",true);//保留原来的内容,在原内容基础上向后追加(续写)
BufferedReader bufferedReader = new BufferedReader(fileReader);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
String s = null;
while((s=bufferedReader.readLine())!=null){
bufferedWriter.write(s);
bufferedWriter.newLine();//插入换行符
}
bufferedReader.close();
bufferedWriter.close();
}
}
2.2 BufferReader从控制台读取数据
public class CharDemo4 {
public static void main(String[] args) throws IOException {
/*
利用BufferReader从控制台读取内容
*/
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入字符串");
String s = bufferedReader.readLine();
System.out.println(s);
}
}
二. 对象输入输出字节流
对象输入流(ObjectInputStream):从文件中把对象输入到程序中
对象输出流(ObjectOutputStream):把java中的对象输出到文件中
这两个属于包装流,可以对字节输入流和字节输出流进行包装处理
1. 为什么要把对象输入到文件中
这是因为我们在程序中new的对象都存在内存中,当程序运行结束后,对象会随着销毁,无法做到将对象的信息永久保存,但有时我们有这种将对象信息永久保存的需求,在需要的时候在还原到程序中.
例如:平时一个游戏凌晨更新维护的时候,往往需要关闭服务器,那我们玩家的账号信息肯定要保存下来,等到维护完成,再将数据恢复到相应的玩家账号中
2. 对象的序列化和反序列化
对象的序列化:将对象信息从程序写到文件中保存
对象的反序列化:将对象信息从文件读到程序中
对象的序列化通过对象输出流来实现(ObjectOutputStream)
ObjectOutputStream常用方法 | 方法阐述 |
---|---|
ObjectOutputStream(OutputStream out) | 构造方法,传入一个字节输出流对象 |
writeObject(Object obj) | 将一个对象信息写入文件中 |
对象的反序列化通过对象输入流来实现(ObjectInputStream)
ObjectInputStream常用方法 | 方法阐述 |
---|---|
ObjectInputStream(InputStream in) | 构造方法,传入一个字节输入流对象 |
Object readObject(Object obj) | 从文件中读一个对象信息,并返回该对象 |
由Object readObject(Object obj)看出,字节输入流也是创建对象的一种方式
import java.io.Serializable;
/*
如果一个类需要被序列化到文件中,那么这个类就需要实现Serializable接口,实现后,会自动的为该类生成一个序列化编号
编号是类的唯一标识
但是自动生成的编号在类信息改变后,会重新为类生成一个编号
可以在类中显示的生成一个编号,这样类信息修改后,也不会改变
*/
public class Student implements Serializable {
private static final long serialVersionUID = 771652260758459933L; //类的序列化编号
private int num;
private String name;
@Override
public String toString() {
return "Student{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
public Student(int num, String name) {
this.num = num;
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
//当一个类的对象需要被序列化到文件时,这个类必须要生成一个序列化编号
Student student = new Student(100,"jim");
//对象序列化
FileOutputStream fileOutputStream = new FileOutputStream("E:/demo.txt");
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
outputStream.writeObject(student);
outputStream.close();
//对象的反序列化
FileInputStream fileInputStream = new FileInputStream("E:/demo.txt");
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
Student stu = (Student)inputStream.readObject();
System.out.println(stu);
inputStream.close();
}
}
自定义一个学生类,要想通过对象输入输出流对自定义的类进行序列化和反序列化,就必须实现Serializable接口,实现这个接口的类默认会生成一个序列化编号,这个编号就相当于一个身份证号一样,若类发生改变会重新生成一个序列化编号,当我们从文件中读取对象信息时,就是通过这个序列化编号去寻找是哪一个类的对象.
正因为类发生变化,序列化编号也会跟着变化,那么如果我们需要修改类信息时,下一次就无法完成反序列化, 因为编译器会拿着这次修改的编号去文件中找,但是文件中还存储的是上一次序列化时产生的编号,显然两次编号不同,编译器会认为这不是同一个类,即不是我们要反序列化的类,就会报错
解决方法:
显示生成序列化编号,这样对于这个类来说,无论怎么修改,它的序列化编号都不发生改变,但切记修改类信息后需要重新序列化对象,否则反序列化的扔是上次未修改的对象
3. IDEA设置序列化编号提示
注意这是新版IDEA的操作,设置好后点击OK即可,这样在自定义的类中如果实现了Serializable接口,鼠标放到类名上就会显示
此时编译器就会自动生成一个序列化编号,这样无论怎么修改类信息,在对象进行序列化和反序列化时都不会报错,唯一需要注意的是,如果修改了类的信息,需要自己重新将对象序列化一次,才是修改后的对象信息,否则还是保存的上一次未修改的对象的信息