基本原理:在创建流对象时,会创建一个内置的默认大小的缓冲区数组临时存储数据,通过缓冲区读写,减少系统底层IO次数,从而提高读写的效率。(故缓冲流又称为高效流)
4个基本的FileXxx流对应的缓冲流同分为4种:
注意!!:缓冲流的使用方式与非缓冲流的是一致的,只是构造方法有所不同
传递的流对象时字节输入/输出流的超类,即所有的字节输入/输出流都能用缓冲流。
字节缓冲输出流:调用缓冲输出流的write方法输出数据不是直接输出到目标文件,而是先输出到到缓冲区数组中。等缓冲区数组满了/调用了flush/close方法才会将缓冲区数组的数据通过OutputStream输出到目标文件中。
注意!!:数据先通过BufferedOutputStream存到缓冲区,而从缓冲区写出到目标文件还是调用OutputStream的write方法。
字节缓冲输入流:调用输入流的read方法不会直接从目标文件中读取到内存,而是先读取到缓存区数组中。
注意!!:数据先利用InputStream的read方法读取到缓冲区,然后才通过BufferedInputStream读入内存。
字节缓冲流的缓冲区本质是一个字节数组,默认大小为8192字节
示例代码如下:
//创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
//创建字节缓冲输出流
BufferedOutputStream bis = new BufferedOutputStream(new FileOutputStream("bos.txt"));
字节缓冲输入流范例代码:
public class Notes01 {
public static void main(String[] args) {
//创建字节缓冲输入流对象
//可用新特性,也可以抛异常,下面以新特性为例
try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.txt"))){
//先用一个个字节读入
/*
单字节读入固定套路
*/
int len = -1;
while ((len = bis.read()) != -1) {
System.out.println((char)len); //只是为了在控制台看到读取效果
}
//以一个字节数组读入
/*
字节数组读入固定套路
*/
byte[] buf = new byte[1024];
int leng = -1;
while ((leng = bis.read(buf)) != -1) {
System.out.println(new String(buf,0,leng)); //只是为了在控制台看到读取效果
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
字节缓冲输出流范例代码
public class Notes01Out {
public static void main(String[] args) {
try(BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.txt",true))) {
//以一个个字节输出
bos.write(97);
//以一个字节数组输出
bos.write("你好吗\r\n".getBytes());
//刷新缓冲区保存数据
bos.flush();
//关闭流,关闭缓冲流同时也会关闭字节输出流
//因为新特性,建流放在try()里,不手动关流系统也会在执行完后自动关流
//bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public BufferedReader(Reader in) : 创建一个新的缓冲字符输入流
public BufferedWriter(Writer out) : 创建一个新的缓冲字符输出流
示例代码如下:
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
传递的流对象时字符输入/输出流的超类,即所有的字符输入/输出流都能用缓冲流。
字符缓冲输出流:调用缓冲输出流的write方法输出数据不是直接输出到目标文件,而是先输出到到缓冲区数组中。等缓冲区数组满了/调用了flush/close方法才会将缓冲区数组的数据通过Writer输出到目标文件中。
注意!!:数据先通过BufferedWriter存到缓冲区,而从缓冲区写出到目标文件还是调用Writer的write方法。
字符缓冲输入流:调用输入流的read方法不会直接从目标文件中读取到内存,而是先读取到缓存区数组中。
注意!!:数据先利用Reader的read方法读取到缓冲区,然后才通过BufferedReader读入内存。
字符缓冲流的缓冲区本质是一个字符数组,默认大小为8192字符(即8192*2个字节)
字符缓冲流的基本方法与普通字符流一致,特有方法如下
public String readLine()
: 读一行文字,读取到末尾返回null。public void newLine()
: 输出一个换行符,根据系统属性定义符号。字符缓冲输入流范例代码:
public class Notes02_BufferedReader {
public static void main(String[] args) {
//创建流对象
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))){
/*
固定套路
*/
String line = null;
while ((line = br.readLine()) != null) { //直接使用特有方法,当然也可以用read方法,套路与FileReader相同
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符缓冲输出流范例代码:
public class Notes02_BufferedWriter {
public static void main(String[] args) {
//创建流对象
try (BufferedWriter bw = new BufferedWriter(new FileWriter("test.txt"))){
//单字符输出
bw.write(97);
//字符数组输出
char[] chars = {'再','见'};
bw.write(chars);
//字符串输出
bw.write("中国人");
} catch (IOException e) {
e.printStackTrace();
}
}
}
常见的码表:
ASCII:美国码表,只包含字母、数字、美标点符号等,每个字符占一个字节,有128种字符映射关系;
GBK:兼容ASCII和GB2312。ASCII表的内容占一个字节,中文两个字节(第一个为负数,第二个可正可负),有128*256个字符映射关系;
Unicode:国际码表,所有字符占两个字节,有65536个字符映射关系;
UTF-8:基于Unicode,根据字符内容选择字节存储,英文占一个字节,中文占三个字节;
因为Windows默认GBK而IDEA默认UTF-8,会有乱码的可能。
InputStreamReader继承Reader,是字节流转换为字符流的桥梁。读取字节时使用指定的码表解码。
InputStreamReader(InputStream in) : 创建一个使用默认字符集的字符流
InputStreamReader(InputStream in, String charsetName) : 创建一个使用指定字符集的字符流
示例代码如下:
//创建字节输入流的转换流
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
//创建指定码表的字节输入流的转换流
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
public class Notes03_InputStreamReader {
public static void main(String[] args) {
//使用转换流转换字节输入流
try(InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"),"gbk")){
char[] chars = new char[1024];
int len = -1;
//System.out.println(len);
while((len = isr.read(chars)) != -1)
System.out.println(new String(chars,0,len));
} catch (IOException e) {
e.printStackTrace();
}
//使用转换流转换字符输入流
/*
由于FileReader自身的原因,只能用默认码表读取文件数据
*/
}
}
OutputStreamWriter继承Writer,字符流转换字节流的桥梁。使用指定的字符集将字符编码为字节。
OutputStreamWriter(OutputStream out)
: 创建一个使用默认字符集的字符流
OutputStreamWriter(OutputStream out, String charsetName)
: 创建一个使用指定字符集的字符流
示例代码如下:
//创建字节输入流的转换流
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream ("out.txt"));
//创建指定码表的字节输入流的转换流
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream ("out.txt") , "GBK");
public class Notes03_OutputStreamWriter {
public static void main(String[] args) {
//使用转换流转换字节输出流
try(OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("test.txt"),"gbk")){
//输出字符
osw.write('你');
//输出字符串
osw.write("nihao");
} catch (IOException e) {
e.printStackTrace();
}
}
}
对象序列化:将对象转换为字节并保持到文件中的过程。需要使用对象输出流ObjectOutputStream。
对象反序列化:将保持在文件中的字节读取出来并转换为对象的过程。需要使用对象输入流ObjectInputStream。
将Java对象的原始数据类型写出到文件,实现对象的持久存储
示例代码如下:
FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
对象序列化必须满足的两个条件:
注意!!:序列化之后的文件必定是乱码形式,但只要反序列化时能正确获得对象,那就没有任何影响。
1)使用static,将隐私数据静态化,数据不会在序列化中显示。但是使用static修饰的数据最好是类共有的数据(比如相同的国籍、民族),不推荐使用
2)使用transient关键字,修饰成员变量,保证该变量的值不会被序列化到文件中
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
1)序列号:一个类在实现Serializable接口后,在编译时系统都会为类随机生成一个序列号,并跟随其.class文件一起传输。如果类修改后重新编译,则系统又会随机生成另一个序列号跟随重新编译后的.class文件
2)方式一:序列号一致
思路:找到对象的class文件,进行反序列化操作,但要求对象必须是可以找到class文件的类
调用读取对象方法:
public final Object readObject () : 读取一个对象。返回的是一个Object对象,如果要获得指定类型对象则需要强转
3)方式二:序列号冲突
思路:如果类重新编译,则会出现序列化和反序列化时的序列号不一致的问题。解决方案是:将类的序列号进行固定,不让系统随机生成序列号,则重新编译后序列号仍可保持一致
自定义序列号:
private static final long serialVersionUID = 自定义序列号
之后与方式一操作相同
注意!!:不管方式一还是方式二都建议自定义序列号
示例代码:
/*
读入对象
*/
public class Notes04_ObjectInputStream {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.txt"))) {
Student stu = (Student)ois.readObject(); //使用独有方法,如果未知数据类型,则需要用Object接收
System.out.println(stu);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Student implements Serializable{
private String name;
private int age;
public transient String id;// 学号
// 自定义序列号
private static final long serialVersionUID = 123456789L;
/*
此处省略getter/setter,以及toString()
*/
}
/*
在另外一个类写出对象
*/
public class Notes04_ObjectOutputStream {
public static void main(String[] args)throws Exception{
// 创建学生对象
Student stu = new Student("小明",20);
stu.idCard = "1501266";
// 创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt"));
// 将对象保存到流关联的目标文件中
oos.writeObject(stu);
// 关闭流
oos.close();
}
}
作用是为了方便原样输出各种数据类型的值
特点:只有输出流,没有输入流
分类:字节打印流(PrintStream)
字符打印流(PrintWriter)
示例代码如下:
PrintStream ps = new PrintStream("ps.txt");
PrintWriter ps = new PrintWriter("ps.txt");
字节流输入流:InputStream 所有字节输入流的父类
- FileInputStream:字节非缓冲输入流,效率低,不推荐直接使用
- BufferedInputStream:字节缓冲输入流,效率高,推荐使用
- ObjectInputStream:对象输入流,当需要从文件中读取自定义对象时使用
字节输出流:OutputStream 所有字节输出流的父类
- FileOutputStream:字节非缓冲输出流 效率低,不推荐直接使用
- BufferedOutputStream:字节缓冲输出流,效率高,推荐使用
- ObjectOutputStream:对象输出流,当需要保存自定义对象到文件中时使用
- PrintStream:字节打印流,当希望原样输出各种数据类型的值时使用
字符流输入流:Reader 所有字符输入流的父类
- FileReader:字符非缓冲输入流,效率低,不推荐直接使用
- BufferedReader:字符缓冲输入流,效率高,推荐使用
- InputStreamReader:字符转换输入流,当需要修改默认码表去读数据时使用,否则不推荐使用
字符输出流:Writer 所有字符输出流的父类
- FileWriter:字符非缓冲输出流 ,效率低,不推荐直接使用
- BufferedWriter:字符缓冲输出流,效率高,推荐使用
- OutputStreamWriter:字符转换输出流,当需要修改码表去输出数据时使用
- PrintWriter:字符打印流,当希望原样输出各种数据类型的值时使用
如何选择流
如果清楚要操作的文件类型是文本文件,则强烈推荐字符流。
如果不清楚要操作的文件类型是什么类型的,则只能选择字节流