1.缓冲流
1.1 概念
基本原理:在创建流对象时,会创建一个内置的默认大小的缓冲区数组临时存储数据,通过缓冲区读写,减少系统底层IO次数,从而提高读写的效率。(故缓冲流又称为高效流)
4个基本的FileXxx流对应的缓冲流同分为4种:
- 字节缓冲流:BufferedInputStream,BufferedOutputStream;
- 字符缓冲流:BufferedReader,BufferedWriter。
注意!!:缓冲流的使用方式与非缓冲流的是一致的,只是构造方法有所不同
1.2 字节缓冲流
1.2.1构造方法
- public BufferedInputStream(InputStream in) : 创建一个新的字节缓冲输入流
- public BufferedOutputStream(OutputStream out) : 创建一个新的字节缓冲输出流
1.2.2 字节缓冲流的特点:
- 传递的流对象时字节输入/输出流的超类,即所有的字节输入/输出流都能用缓冲流。
- 字节缓冲输出流:调用缓冲输出流的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"));
//创建字节缓冲输入流
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 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 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();
}
}
}
1.3 字符缓冲流
1.3.1构造方法
- public BufferedReader(Reader in) : 创建一个新的缓冲字符输入流
- public BufferedWriter(Writer out) : 创建一个新的缓冲字符输出流
示例代码如下:
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
1.3.2 字符缓冲流的特点:
- 传递的流对象时字符输入/输出流的超类,即所有的字符输入/输出流都能用缓冲流。
- 字符缓冲输出流:调用缓冲输出流的write方法输出数据不是直接输出到目标文件,而是先输出到到缓冲区数组中。等缓冲区数组满了/调用了flush/close方法才会将缓冲区数组的数据通过Writer输出到目标文件中。
注意!!:数据先通过BufferedWriter存到缓冲区,而从缓冲区写出到目标文件还是调用Writer的write方法。
- 字符缓冲输入流:调用输入流的read方法不会直接从目标文件中读取到内存,而是先读取到缓存区数组中。
注意!!:数据先利用Reader的read方法读取到缓冲区,然后才通过BufferedReader读入内存。
- 字符缓冲流的缓冲区本质是一个字符数组,默认大小为8192字符(即8192*2个字节)
1.3.2 特有的方法
字符缓冲流的基本方法与普通字符流一致,特有方法如下
- BufferedReader:
public String readLine()
: 读一行文字,读取到末尾返回null。 - BufferedWriter:
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_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();
}
}
}
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();
}
}
}
2.转换流
2.1 字符编码
- 编码:字符按某种规则存储到计算机中。
- 解码:将存储在计算机中的二进制数据按某种规则解析显示出来。
- 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。
- 编码表:字符与其对应的二进制相互映射的表
常见的码表:
ASCII:美国码表,只包含字母、数字、美标点符号等,每个字符占一个字节,有128种字符映射关系;
GBK:兼容ASCII和GB2312。ASCII表的内容占一个字节,中文两个字节(第一个为负数,第二个可正可负),有128*256个字符映射关系;
Unicode:国际码表,所有字符占两个字节,有65536个字符映射关系;
UTF-8:基于Unicode,根据字符内容选择字节存储,英文占一个字节,中文占三个字节;
2.2 转换流的存在意义
- 产生乱码的原因:文本存储使用的码表与读取时使用的码表不一致
因为Windows默认GBK而IDEA默认UTF-8,会有乱码的可能。
- 意义:指定码表读写数据。
2.3 InputStreamReader类
2.3.1概念
InputStreamReader继承Reader,是字节流转换为字符流的桥梁。读取字节时使用指定的码表解码。
2.3.2 构造方法
- 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");
//创建字节输入流的转换流
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
//创建指定码表的字节输入流的转换流
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
2.3.3 InputStreamReader类的特点
- 只能读入文本文件的内容
- 传递的流对象时字节输入流的超类,即所有的字节输入流都能用转换流。
2.3.4 InputStreamReader转换流程
- 先由字节输入流从目标文件中读取数据(读入的是二进制数据),然后将读取的数据交给字符转换流,由字符转换流查询指定码表将二进制数据转换为对应的字符
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自身的原因,只能用默认码表读取文件数据
*/
}
}
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自身的原因,只能用默认码表读取文件数据
*/
}
}
2.4 OutputStreamWriter类
2.4.1概念
OutputStreamWriter继承Writer,字符流转换字节流的桥梁。使用指定的字符集将字符编码为字节。
2.4.2 构造方法
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");
//创建字节输入流的转换流
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream ("out.txt"));
//创建指定码表的字节输入流的转换流
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream ("out.txt") , "GBK");
2.4.3 OutputStreamWriter类的特点
- 只能写出到文本文件
- 传递的流对象时字节输出流的超类,即所有的字节输出流都能用转换流。
2.4.4 OutputStreamWriter转换流程
- 先由字符转换流查询指定码表将字符转换为二进制数据,然后将二进制数据交给字节输出流输出到目标文件中
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();
}
}
}
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();
}
}
}
3.序列化流
3.1序列化
3.1.1概念
- 对象序列化:将对象转换为字节并保持到文件中的过程。需要使用对象输出流ObjectOutputStream。
- 对象反序列化:将保持在文件中的字节读取出来并转换为对象的过程。需要使用对象输入流ObjectInputStream。
3.2 对象输出流(ObjectOutputStream类)
3.2.1 概念
将Java对象的原始数据类型写出到文件,实现对象的持久存储
3.2.2 构造方法
- public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。
示例代码如下:
FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
3.2.3 序列化操作
对象序列化必须满足的两个条件:
- 该类必须实现java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类的对象在序列化过程中会抛出NotSerializableException(对象不支持序列化操作);
- 写出对象方法
public final void writeObject (Object obj) : 将指定的对象写出。
注意!!:序列化之后的文件必定是乱码形式,但只要反序列化时能正确获得对象,那就没有任何影响。
3.2.4 对象序列化注意事项
1)使用static,将隐私数据静态化,数据不会在序列化中显示。但是使用static修饰的数据最好是类共有的数据(比如相同的国籍、民族),不推荐使用
2)使用transient关键字,修饰成员变量,保证该变量的值不会被序列化到文件中
- transient 使用格式
- 修饰符 transient 数据类型 变量名;
3.3 对象输入流(ObjectInputStream类)
3.3.1 概念
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
3.3.2 构造方法
- public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
3.3.3 反序列化操作
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();
}
}
/*
读入对象
*/
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();
}
}
4.打印流
4.1 作用及分类
- 作用是为了方便原样输出各种数据类型的值
- 特点:只有输出流,没有输入流
- 分类:字节打印流(PrintStream)
字符打印流(PrintWriter)
4.2 构造方法
- public PrintStream(String fileName) : 使用指定的文件名创建一个新的字节打印流。
- public PrintWriter(String fileName) : 使用指定的文件名创建一个新的字符打印流。
示例代码如下:
PrintStream ps = new PrintStream("ps.txt");
PrintWriter ps = new PrintWriter("ps.txt");
PrintStream ps = new PrintStream("ps.txt");
PrintWriter ps = new PrintWriter("ps.txt");
4.3 常用方法
- print(数据类型 变量名):不换行输出数据到目标文件
- println(数据类型 变量名):自动换行输出数据到目标文件
- 同样有write()方法,print方法里面包含write()
5 各种IO流的选取
字节流输入流:InputStream 所有字节输入流的父类
- FileInputStream:字节非缓冲输入流,效率低,不推荐直接使用
- BufferedInputStream:字节缓冲输入流,效率高,推荐使用
- ObjectInputStream:对象输入流,当需要从文件中读取自定义对象时使用
字节输出流:OutputStream 所有字节输出流的父类
- FileOutputStream:字节非缓冲输出流 效率低,不推荐直接使用
- BufferedOutputStream:字节缓冲输出流,效率高,推荐使用
- ObjectOutputStream:对象输出流,当需要保存自定义对象到文件中时使用
- PrintStream:字节打印流,当希望原样输出各种数据类型的值时使用
字符流输入流:Reader 所有字符输入流的父类
- FileReader:字符非缓冲输入流,效率低,不推荐直接使用
- BufferedReader:字符缓冲输入流,效率高,推荐使用
- InputStreamReader:字符转换输入流,当需要修改默认码表去读数据时使用,否则不推荐使用
字符输出流:Writer 所有字符输出流的父类
- FileWriter:字符非缓冲输出流 ,效率低,不推荐直接使用
- BufferedWriter:字符缓冲输出流,效率高,推荐使用
- OutputStreamWriter:字符转换输出流,当需要修改码表去输出数据时使用
- PrintWriter:字符打印流,当希望原样输出各种数据类型的值时使用
如何选择流
如果清楚要操作的文件类型是文本文件,则强烈推荐字符流。
如果不清楚要操作的文件类型是什么类型的,则只能选择字节流
字节流输入流:InputStream 所有字节输入流的父类
- FileInputStream:字节非缓冲输入流,效率低,不推荐直接使用
- BufferedInputStream:字节缓冲输入流,效率高,推荐使用
- ObjectInputStream:对象输入流,当需要从文件中读取自定义对象时使用
字节输出流:OutputStream 所有字节输出流的父类
- FileOutputStream:字节非缓冲输出流 效率低,不推荐直接使用
- BufferedOutputStream:字节缓冲输出流,效率高,推荐使用
- ObjectOutputStream:对象输出流,当需要保存自定义对象到文件中时使用
- PrintStream:字节打印流,当希望原样输出各种数据类型的值时使用
字符流输入流:Reader 所有字符输入流的父类
- FileReader:字符非缓冲输入流,效率低,不推荐直接使用
- BufferedReader:字符缓冲输入流,效率高,推荐使用
- InputStreamReader:字符转换输入流,当需要修改默认码表去读数据时使用,否则不推荐使用
字符输出流:Writer 所有字符输出流的父类
- FileWriter:字符非缓冲输出流 ,效率低,不推荐直接使用
- BufferedWriter:字符缓冲输出流,效率高,推荐使用
- OutputStreamWriter:字符转换输出流,当需要修改码表去输出数据时使用
- PrintWriter:字符打印流,当希望原样输出各种数据类型的值时使用
如何选择流
如果清楚要操作的文件类型是文本文件,则强烈推荐字符流。
如果不清楚要操作的文件类型是什么类型的,则只能选择字节流