学习IO流的目的:
实现数据的持久化。
什么是IO:
- Input:输入
- Output:输出
- 输入输出称为IO操作
IO流的作用:
- 输入:将文件中的数据读取到内存中。
- 输出:将内存中的数据保存到文件中。
IO流分类:
字节流:以字节为单位读写数据。
- 字节输入流:InputStream 所有字节输入流的父类 。
- 字节输出流:OutputStream 所有字节输出流的父类。
字符流:以字符为单位读写数据。
- 字符输入流:Reader 所有字符输入流的父类。
- 字符输出流:Writer 所有字符输出流的父类。
IO流关闭资源的原则:
先开后关,后开先关。
如何判断流是字节流还是字符流:
如果是以Stream结尾的流,就是字节流,否则都是字符流。
字节流和字符流的区别:
- 字节流:可以操作任意类型的文件 。
- 字符流:只能用来操作文本文件。
字节流和字符流的选择:
- 如果明确清楚要操作的文件类型是文本文件,则强烈推荐使用字符流 。
- 如果不清楚要操作的文件类型是什么类型,则只能选择字节流。
IO流的使用步骤:
- 创建流关联目标文件。
- 调用流的write或read方法读写数据。
- 调用流的close方法关闭流释放资源。
数据的本质:
- 计算机只识别0和1。
- 所有的数据(文本文件,视频,图片,音频)在内存中存储都是0和1组成。
- 所有数据在传输过程中都是以0和1进行传输(二进制)。
- 数据的本质:二进制。
- 编码:将字符转换为对应的二进制数据的过程。
- 解码:将二进制数据转换为对应的字符的过程。
OutputStream类概述:
- Output:输出。
- Stream: 字节流。
- 是一个字节输出流,以字节为单位输出数据到文件中。
- 是一个抽象类,不能直接创建该类的对象,只能创建子类对象。
- 是所有字节输出流的父类。
OutputStream类常用子类:
- FileOutStream
- BufferedOutputStream
- ObjectOutputStream
- PrintStream
OutputStream类常用方法:
void close()
- 关闭流释放资源 。
void write(byte[] b)
- 输出一个字节数组。
void write(byte[] b, int off, int len)
- 输出一个字节数组的一部分内容。
- off:数组的起始索引。
- len:要输出的字节个数。
void write(int b)
- 输出一个字节。如果数字超过一个字节的范围,比如两个字节。则忽略前面的字节,只取最后那个字节所对应的ASII码。
FileOutputStream类常用构造方法:
FileOutputStream(String name)
- 根据文件路径字符串创建字节输出流对象。
- 如果目标文件不存在,则会创建该文件。
FileOutputStream(File file)
- 根据文件对象创建字节输出流对象。
- 如果目标文件不存在,则会创建该文件。
- 关联的目标文件必须是普通文件,不能是文件夹,否则直接抛出异常。
追加输出和换行输出数据:
FileOutputStream(File file, boolean append)
FileOutputStream(String name, boolean append)
- 根据文件对象或文件路径字符串创建字节输出流对象。
- append:true 表示追加输出数据,不清空当前文件的内容。
- append:false 表示不追加输出数据,清空当前文件的内容。
- 如果目标文件不存在,则会先创建该文件。
- 根据文件对象创建对象时,关联的目标文件必须是普通文件,不能是文件夹,否则直接抛出异常。
字节输出流的使用步骤:
- 创建字节输出流对象并关联目标文件。
- 调用字节输出流对象的write方法输出数据。
- 调用字节输出流对象的close方法关闭流释放资源
换行符:
- \r 作用:回车
- \n 作用:换行,光标移到下一行的行首。
- \r\n 作用:换行,光标移到下一行的行首。 (强烈推荐使用)
- 当下面没有内容的时候,回车跟换行的作用是一样的,但是当下面有内容时,作用就不一样。
- window自带的记事本不能识别 \r 和 \n 。只能识别 \r\n ,为了适配任何系统,所以推荐使用 \r\n 。
追加和换行写出数据 示例代码:
public class FileOutputStreamDemo03 {
public static void main(String[] args)throws Exception{
// 创建字节输出流对象并关联目标文件
FileOutputStream fos = new FileOutputStream("b.txt",true);
// 将字符串输出到流关联目标文件中
fos.write("abcde\r\nabcde".getBytes());
// 关闭流释放资源
fos.close();
}
}
InputStream类概述:
- Input:输入。
- Stream:字节流。
- 是一个字节输入流,以字节为单位读取数据。
- 是一个抽象类,也不能创建给类对象,只能创建子类对象。
- 是所有字节输入流的父类。
InputStream类常用子类:
- FileInputStream
- BufferedInputStream
- ObjectInputStream
InputStream类常用方法:
void close()
- 关闭流释放资源.
int read()
- 读取一个字节,返回读取到的字节数.
- 如果读取到文件末尾,则返回值为‐1.
int read(byte[] b)
- 读取一个字节数组,将读取到内容存储到指定的字节数组中。
- 返回实际读取到的字节个数.。
- 如果读取到文件末尾,则返回值为‐1。
int read(byte[] b, int off, int len)
- 读取一个字节数组,将读取到内容存储到指定的字节数组中。
- off:字节数组的起始索引。
- len:存储字节的个数。
FileInputStream类常用构造方法:
FileInputStream(String name)
FileInputStream(File file)
- 根据文件路径字符串或文件对象创建字节输入流对象。
- 如果目标文件不存在,则直接抛出异常。
- 关联的目标文件必须是普通文件,不能是文件夹,否则直接抛出异常。
如何把输入字符数组的字符转成字符串输出:
String (byte[] b )
String(byte[] b , int off , int length)
- 用这两个String的构造方法,可以把输入字符数组的字符转成字符串。
了解:
- 字符流仅能文本文件进行操作 。
- GBK编码表中,一个中文字符占3个字节 。
- UTF-8编码表中,一个中文字符占2个字节。
Reader类概述:
- 是字符输入流,也是一个抽象类,不能创建对象,只能使用子类。
- 以字符为单位读取数据。
- 是所有字符输入流的父类。
Reader类常用子类:
- FileReader
- BufferedReader
- InputStreamReader
Reader类常用方法:
void close
- 关闭流释放资源。
int read()
- 读取一个字符,返回实际读取的字符数。
- 读取到文件末尾返回‐1。
int read(char[] cbuf)
- 读取一个字符数组,将读取到的字符存储到字符数组中。
- 返回实际读取到的字符个数。
- 读取到文件末尾返回‐1。
int read(char[] cbuf, int off, int len)
- 读取一个字符数组,将读取到内容存储到指定的字符数组中。
- off:字符数组的起始索引。
- len:存储字符的个数。
FileReader类常用构造方法:
FileReader(String name)
FileReader(File file)
- 根据文件路径字符串或文件对象创建字符输入流对象。
字符输入流使用步骤:
- 创建字符输入流关联目标文件。
- 调用read方法:读取字符,字符数组。
- 调用close方法关闭流释放资源。
Writer类概述:
- 是字符输出流,也是一个抽象类,不能创建对象,只能使用子类。
- 以字符为单位输出数据。
- 是所有字符输出流的父类。
Writer类常用子类:
- FileWriter
- BufferedWriter
- OutputStreamWriter
- PrintWriter
Writer类常用方法:
void close()
- 关闭流,释放资源。
void write(char[] cbuf)
- 输出字符数组。
void write(char[] cbuf, int off, int len)
- 输出字符数组的一部分。
void write(int c)
- 输出一个字符。
字节流没有,字符流独有的方法:
void write(String str)
- 输出字符串。
void write(String str, int off, int len)
- 输出字符串的一部分。
FileWriter类常用构造方法:
FileWriter(String name)
FileWriter(File file)
- 默认是不追加,会清空文件内容。
FileWriter(String name,boolean append)
FileWriter(File file,boolean append)
- 根据文件路径字符串和文件对象创建字符输出流对象。
- append:true 追加
- false:不追加
缓冲流概述:
缓冲流又称为高效流。
- 缓冲流的使用方式和非缓冲流使用方式是一样的,区别在构造方法不一样而已。
缓冲流的分类:
- 字节缓冲输出流:BufferedOutputStream
- 字节缓冲输入流:BufferedInputStream
- 字符缓冲输出流:BufferedWriter
- 字符缓冲输入流:BufferedReader
缓冲流的原理:
- 利用缓冲区数组临时存储多个数据,等缓冲区数组满了或调用了close方法时,一次调用或减少底层资源的调用次数从而提高读写速度。
图解:
BufferedOutputStream类概述:
- 继承OutputStream,是字节缓冲输出流,可以往任意类型的文件输出数据。
BufferedOutputStream类构造方法:
BufferedOutputStream(OutputStream out)
- 根据字节输出流创建字节缓冲输出流对象。
- 传递谁就提高谁的效率。
- 注意:缓冲输出流的构造方法没有追不追加一说,如果要想可以追加输出,则传递的参数FileOutputStream可以追加。
- 缓冲区数组默认size:8192
BufferedOutputStream类使用注意事项:
- 调用缓冲流的write方法输出数据不是直接输出到目标文件中,而是先输出到缓冲区数组中,等缓冲区数组满了或调用了flush或close方法才会将缓冲区数组的数据通过FileOutputStream输出到目标文件中。
void flush()
- 刷新缓冲区。
close和flush方法的区别:(面试题)
- flush刷新缓冲区,flush方法调用之后流还可以继续使用。
- close关闭流释放资源,close方法调用之后流就不能继续使用了,close会调用flush方法先刷新缓冲区,然后再关闭流释放资源。
BufferedInputStream类概述:
继承InputStream,字节缓冲输入流,可以读取任意类型文件的数据。
BufferedInputStream类构造方法:
BufferedInputStream(InputStream in)
- 根据字节输入流创建字节缓冲输入流对象。
- 传递谁就提高谁的效率。
BufferedInputStream类使用注意事项:
- 利用字节缓冲输入流读取数据时不是直接从目标文件中读取,而是从缓冲区数组中读取,如果缓冲区数组中没有数据可读了,则会利用FileInputStream流从目标文件中读取数据到 缓冲区数组中,一次从目标文件中读取8192字节到数组中。
BufferedWriter类概述:
- 继承Writer,是字符缓冲输出流,只能往文本文件中输出数据。
BufferedWriter类构造方法:
BufferedWriter(Writer writer)
- 根据字符输入流创建字符缓冲输入流对象。
- 传递谁就提高谁的效率。
BufferedWriter类特有方法:
void newLine()
- 输出一个换行符。
BufferedReader类概述:
- 继承Reader,字符缓冲输入流,只能读取文本文件的内容。
BufferedReader类构造方法:
BufferedReader(Reader r)
- 根据字符输出流创建字符缓冲输出流对象。
- 传递谁就提高谁的效率。
BufferedReader类特有方法:
String readLine()
- 读取一行数据,读取到文件末尾返回null。
码表的概述:
- 码表其实就是一个字符和其对应的二进制相互映射的一张表。
- 这张表中规定了字符和二进制的映射关系。
- 不同的码表所容纳的字符映射也是不同的。
- GBK编码表中,一个中文字符占3个字节 。
- UTF-8编码表中,一个中文字符占2个字节。
乱码的原因:
因为文本在存储时使用的码表和读取时使用的码表不一致造成的。
转换流概述:
本质就是字符流。
转换流存在的意义:
可以指定码表来读写数据。
转换流的分类:
- 字符转换输入流: InputStreamReader
- 字符转换输出流: OutputStreamWriter
InputStreamReader类概述:
- 继承Reader,字符转换输入流,只能读取文本文件的内容。
- 字节流通向字符流的桥梁(字节转字符)。
InputStreamReader类构造方法:
InputStreamReader(InputStream in)
- 根据字节输入流创建字符转换输入流,默认的编码表:utf8。
InputStreamReader(InputStream in, String charsetName)
- 根据字节输入流创建字符转换输入流。
- charsetName:用来指定编码的名称,常用的编码表名:gbk和utf8。
字符转换输入流的转换流程:
- 先由字节输入流从目标文件中读取数据,读出来的是一堆二进制数据。
- 然后将读取到的二进制数据交个字符转换流,由字符转换流查询指定的码表将二进制数据转换为对应的字符。
OutputStreamWriter类概述:
- 继承Writer, 字符转换输出流,只能往文本文件中输出数据。
- 是字符流通向字节流的桥梁(字符转换字节)。
OutputStreamWriter类构造方法:
OutputStreamWriter(OutputStream out) 默认的编码表:utf8
OutputStreamWriter(OutputStream out,String charsetName)
- 根据字节输出流创建字符转换输出流对象。
- charsetName:用来指定编码的名称,常用的编码表名:gbk和utf8。
字符转换输出流的转换流程:
- 先由字符转换输出流查询指定的码表将字符转换为二进制数据。
- 然后将二进制数据交给字节输出流输出到目标文件中。
序列化流的作用:
- 可以直接将对象保存到文件中。
- 可以直接从文件中读取对象。
序列化流的分类:
- ObjectOutputStream:对象输出流
- ObjectInputStream:对象输入流
对象的序列化:
- 将对象以流的形式保存到文件中的过程则称为对象的序列化。
- 实现对象序列化操作需要使用对象输出流:ObjectOutputStream。
对象的反序列化:
- 将文件中的对象以流的形式读取出来的过程则称为对象的反序列化。
- 实现对象的反序列化操作需要使用对象输入流:ObjectInputStream。
对象序列化注意事项:
- 对象序列化操作只会保存对象相关的数据,属于类的数据不会被保存,因为static修饰的成员变量是属于类的 所以在保存对象时静态成员变量的值不会被保存。
ObjectOutputStream类构造方法:
ObjectOutputStream(OutputStream out)
- 根据字节输出流创建对象输出流对象。
ObjectOutputStream类成员方法:
void writeObject(Object obj)
- 保存对象到流关联的文件中。
若类要想被序列化,则必须实现Serializable接口。
Serializable接口概述:
- 是一个标志性接口,只要实现了该接口就具备序列化能力。
- 只有实现了该接口的类的对象,才可以通过对象输出流保存到文件中。
若类不实现该接口,用对象输出流保存在文件文件中,则会报错:
java.io.NotSerializableException: 对象不支持序列话操作。
若希望类的某项数据不被保存,有两种方法:
- 用static修饰变量。
- 用transient修饰变量。
transient关键字作用:
- 用来修饰成员变量的,能够保证该成员变量的值不被序列化到文件中。
transient关键字的格式:
修饰符 transient 数据类型 成员变量名;
ObjectInputStream类构造方法:
ObjectInputStream(InputStream in)
- 根据字节输入流创建对象输入流。
ObjectInputStream类成员方法:
Object readObject();
- 从流关联的目标文件中读取对象。
示例代码:
public class ObjectInputStreamDemo01 {
public static void main(String[] args)throws Exception{
// 创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.txt"));
// 读取对象数据
Student stu = (Student)ois.readObject(); // new Student() Student.class
System.out.println(stu);
// 关闭流
ois.close();
}
}
如果实现Serializable接口的类被序列化保存到文件中,如果对类进行修改,再对类反序列化,则会报错。
- java.io.InvalidClassException : 序列化冲突。
- 一旦类实现了Serializable接口,每当类重新编译系统都会为类随机生成一个序列号。
- 序列号是一个长整型的数据。
序列号冲突解决方案:
自定义序列号,格式如下:
- private static final long serialVersionUID = 132456789765L;
若想存储多个对象,则可以把多个对象弄到ArrayList里,再序列化ArrayList对象。
示例代码:
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
public class Demo1 {
public static void main(String[] args) throws Exception {
Student s1 = new Student("aa",15);
Student s2 = new Student("bb",16);
Student s3 = new Student("cc",19);
ArrayList list = new ArrayList<>();
Collections.addAll(list,s1,s2,s3);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("stu.txt")));
oos.writeObject(list);
oos.close();
}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
public class Demo2 {
public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("stu.txt")));
ArrayList list = (ArrayList)ois.readObject();
for (Student student : list) {
System.out.println(student);
}
}
}
打印流存在的意义:
为了方便原样输出各种数据类型的值。
打印流的特点:
- 只有输出流,没有输入流。
- 能够原样输出各种数据类型的值。
打印流的分类:
- 字节打印流:PrintStream 可以往任意类型的文件打印数据。
- 字符打印流:PrintWriter 只能往文本文件中打印数据。
打印流常用方法:
print(数据类型 变量名)
- 将数据输出到目标文件中,不换行。
println(数据类 变量名)
- 将数据输出到目标文件中,自动换行。
构造方法:
public PrintWriter(String fileName)
- 使用指定的文件名创建一个新的打印流。
构造方法:
public PrintStream
- 使用指定的文件名创建一个新的打印流。
- System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。
- 我们可以用System.setOut(PrintStream p)改变sout的流向。
示例代码:
public static void test() throws IOException {
// 调用系统的打印流,控制台直接输出97
System.out.println(97);
PrintStream consolePs = System.out;
// 创建打印流,指定文件的名称
PrintStream ps = new PrintStream("ps.txt");
// 设置系统的打印流流向,输出到ps.txt
System.setOut(ps);
// 调用系统的打印流,ps.txt中输出97
System.out.println(97);
// 设置系统的打印流流向
System.setOut(consolePs);
System.out.println(98);
}
字节流输入流:InputStream 所有字节输入流的父类:
- FileInputStream:字节非缓冲输入流,效率低,不推荐直接使用 。
- BufferedInputStream:字节缓冲输入流,效率高,推荐使用 。
- ObjectInputStream:对象输入流,当需要从文件中读取自定义对象时使用。
字节输出流:OutputStream 所有字节输出流的父类:
- FileOutputStream:字节非缓冲输出流 效率低,不推荐直接使用 。
- BufferedOutputStream:字节缓冲输出流,效率高,推荐使用 。
- ObjectOutputStream:对象输出流,当需要保存自定义对象到文件中时使用 。
- PrintStream:字节打印流,当希望原样输出各种数据类型的值时使用 。
字符流输入流:Reader 所有字符输入流的父类:
- FileReader:字符非缓冲输入流,效率低,不推荐直接使用 。
- BufferedReader:字符缓冲输入流,效率高,推荐使用 。
- InputStreamReader:字符转换输入流,当需要修改默认码表去读数据时使用,否则不推荐使用。
字符输出流:Writer 所有字符输出流的父类:
- FileWriter:字符非缓冲输出流 ,效率低,不推荐直接使用。
- BufferedWriter:字符缓冲输出流,效率高,推荐使用 。
- OutputStreamWriter:字符转换输出流,当需要修改码表去输出数据时使用 。
- PrintWriter:字符打印流,当希望原样输出各种数据类型的值时使用。