什么是流
当我们需要把文件中的内容读取到运行中的程序中或者把程序中的数据写入到文件中时,程序与文件之间数据的通道我们把它形象的称为流。因为它们数据在程序和文件之间的传输就像水流一样。而IO指的是Input(输入流)和Output(输出流)。输入和输出是两种方向不同的流,这两个动作是对于运行在内存中的程序而言的,假设我们需要把文件中的内容读取到运行的程序中,这就需要输入流来操作,反之就是要用输出流来操作。
流的分类
流可以按照方向分为输入流和输出流,也可以按照方式分为字节流(按字节读取)和字符流(按字符读取)。
在Java中是把IO流分为了字节流和字符流,分为字节输入流——InputStream、字节输出流——OutputStream、字符输入流——Reader、字符输出流——Writer。不过这些都是抽象类,我们要学习的是他们的实现类FileInputStream、FileOutputStream、FileReader、FileWriter。
字节输出流OutputStream
OutputStream是字节输出流,它是一个抽象类,而FileOutputStream是它的实现类,用于将程序中的数据以字节为单位输送到指定文件。下面是它的常用方法:
方法 | 用法 | 说明 |
---|---|---|
FileOutputStream(File file) | FileOutputStream fos = new FileOutputStream(new File(“path/to/file”)); | 创建文件输出流对象,指定写入的文件 |
FileOutputStream(String name) | FileOutputStream fos = new FileOutputStream(“path/to/file”) | 创建文件输出流对象,指定写入的文件名 |
void write(int b) | fos.write(97); | 写入一个字节到输出流 |
void write(byte[] b) | byte[] data = {1,2,3}; fos.write(data); | 将字节数组写入输出流 |
void write(byte[] b, int off, int len) | 后面介绍 | 将字节数组off索引开始写入len个元素 |
void close() | fos.close(); | 关闭输出流,必须关闭才能完全写入文件 |
注意:
- 这里第一个和第二个方法为构造方法,它们其实都可以拥有第二个boolean类型的参数用于指定当前写入是否为续写,如果没有这个参数则默认为不续写,那么写入操作会先把文件原先内容清空,参数为true则为续写。
- 构造方法中的文件如果不存在则在写入时会自动创建,然后进行写入数据。
- write()方法的参数既可以是字符的ASCII码也可以是单引号括起来的字符。
- close方法不仅能关闭流释放资源,而且能保证数据完全被写入文件中。
用法演示
我们以写入a.txt文件为例。
代码:
public class Demo1 {
public static void main(String[] args) throws IOException {
//1. 创建流
OutputStream os = new FileOutputStream("a.txt");
//2. 写入数据
String str = "新的数据";
os.write(str.getBytes()); //参数是数据的字节数组
os.write('a'); //参数可以是字符
os.write(97); //参数可以是字符的ASCII码
//3. 关闭流
os.close();
}
}
写之前:
写出后:
可以看到,写入数据默认是不追加内容直接覆盖之前的内容的。
字节输入流InputStream的用法
IutputStream是字节输入流,它是一个抽象类,而FileIutputStream是它的实现类,用于以字节为单位将文件中的数据读入程序中。下面是它的常用方法:
方法 | 用法 | 说明 |
---|---|---|
FileInputStream(File file) | FileInputStream fis = new FileInputStream(new File(“path/to/file”)); | 创建文件输入流对象,读取指定文件 |
FileInputStream(String name) | FileInputStream fis = new FileInputStream(“path/to/file”); | 创建文件输入流对象,读取指定文件名 |
int read() | int b = fis.read(); | 读取一个字节并返回,文件结束返回-1 |
int read(byte[] b) | byte[] data = new byte[1024]; fis.read(data); | 读取b.length个字节到字节数组b中,文件结束返回-1 |
void close() | fis.close(); | 关闭输入流,释放资源 |
read()用法示例
循环读取并打印
上图是代码、读取的文件以及输出结果。
注意
-
不要使用字节流读取中文并解析,因为会出现乱码的情况,出现乱码的原因是文件中的中文的编码方式是utf-8,而utf-8中中文一个字是三个字节,如果你用字节流读取的话一次读取一个字节,这个字节的数据是一个负数,打印这个负数是使用ASCII的规则进行的,而ASCII中又没有负数表示的字符,因此出现乱码。读取中文并解析我们需要用字符流读取。
-
不要使用此方法读取较大的文件,因为此方法一次只读取一个字节会导致频繁读取硬盘导致文件读取速度很慢
read(byte[] bytes)用法示例
如果我们使用read()方法读取大一点的文件的数据时会发现非常费时间,这是我们就需要用read(byte[] bytes)读取。下面我们分别用这两个方法读取同一个文件看看时间上的差距。
由此可见使用字符数组的方式读取快得不是一点半点,我们是用此方法读取时,字符数组的长度一般设置为1024的整数倍。
此外这个方法如果循环读取的话对于数组中的数据一定要用读取到的长度加以限制,因为每次读取到的数据都会覆盖数组以前的数据,如果又一次读取的数据没有填满数组,那么剩下没填满的部分就是以前的数据,并没有被清空。
不加以长度限制会读取错误:
加上长度限制就不会了:
字符输出流Writer的用法
如果你已经了解了前面字节流的用法,那么你学下面的字符流的用法时会觉得很容易,因为它们的API风格基本相同。
Writer是抽象类,它的实现类是FileWriter,FileWriter写入的单位是字符而不是字节,当你写出的字符是三个字节大小时,它会一下写出三个字节。下面是它的常用方法
方法 | 用法 | 说明 |
---|---|---|
FileWriter(File file) | FileWriter fw = new FileWriter(new File(“file.txt”)); | 构造一个给定File的FileWriter |
FileWriter(String fileName) | FileWriter fw = new FileWriter(“file.txt”); | 构造一个给定文件名的FileWriter |
write(int c) | fw.write(97); | 写出单个字符(写入其ASCII值) |
write(char[] cbuf) | char[] buf = {‘a’,‘b’,‘c’}; fw.write(buf); | 写出字符数组 |
write(String str) | fw.write(“Hello”); | 写出字符串 |
flush() | fw.flush(); | 刷新缓冲区,将缓冲内容写出文件 |
close() | fw.close(); | 关闭写出流,同时会先刷新缓冲区 |
这里我们可以看到FileWriter的write参数可以是字符串,我们可以利用这个方法直接写入字符串而不需要转为字节数组,当然它的参数也可以是数组,但是这个数组不再是字节数组而是字符数组。
FileWriter的构造方法也是可以传入第二个boolean类型的参数指定写入内容是否为追加,默认是不追加的。
在Java中字符流是自带缓冲区的,每次读入和写出都会经过这个缓冲区,缓冲区大小为8KB。这个缓冲区可以减少访问硬盘的次数从而使读写效率更高。也正因为如此,我们在读写文件后要用flush方法清空缓冲区,把在缓冲区中还未使用的数据立即写出或读入。当然你如果想立即关闭流,则不必调用flush方法了,因为close方法会也会刷新缓冲区。
用法演示:
字符输入流Reader的用法
Reader的实现类是FileReader。它的读取单位是一个字符,下面是它的常见方法:
方法 | 用法 | 说明 |
---|---|---|
FileReader(File file) | FileReader fr = new FileReader(new File(“file.txt”)); | 创建一个给定File的FileReader |
FileReader(String fileName) | FileReader fr = new FileReader(“file.txt”); | 创建一个给定文件名的FileReader |
read() | int ch = fr.read(); | 读取单个字符并返回,文件结束返回-1 |
read(char[] buf) | char[] cbuf = new char[1024]; fr.read(cbuf); | 将字符读入数组buf中,结束返回-1 |
close() | fr.close(); | 关闭流,同时会先刷新缓冲区 |
使用FileReader就可以读取并解析中文了,这里的read()方法是一次读取一个字符,也可以传入字符数组作为读取数据的容器。使用字符流的read(char[] buf)可以自动把读取到的数据自动解码为字符放在字符数组,不像字节流那样需要自己强转为字符类型才行。
IO流的简化写法
try(一个或多个流对象){
//使用流
}catch (){
//捕获异常的处理
}
使用这种写法就不必自己手动关闭流了,try后面括号里多个流对象用分号;隔开。下面以字符输入流举个例子。
/**
*把a.txt的内容复制到b.txt中
*/
public class Demo6 {
public static void main(String[] args) {
//多个流之间用;隔开
try(Reader reader = new FileReader("a.txt");
Writer writer = new FileWriter("b.txt")){
int data = 0;
while ((data = reader.read()) != -1){
writer.write(data);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
//这里会自动关闭流不需要手动调用close了。
}
}
最后补充一下,大家手动调用close方法关闭流时的一些规则:
-
先关闭外层流,再关闭内层流。
例如用BufferedReader读取文件,先关闭BufferedReader,再关闭FileReader。 -
先关闭输出流,再关闭输入流。
例如同时使用FileOutputStream和FileInputStream,要先关闭FileOutputStream,后关闭FileInputStream。 -
先关闭处理流,再关闭节点流。
处理流是对节点流的包装,要先关闭处理流。
按这个顺序关闭流的原因主要有:
内层流或输入流需要依赖外层流或输出流,如果先关闭外层流则内层流会出现错误。
处理流在关闭时依赖节点流做清理工作,如刷新缓冲等,需要节点流处于开启状态。
输出流的关闭要晚于输入流,防止数据还没完全输出就关闭了输出流。
最后,如果你觉得本文还不错,对你有所帮助,请给咱点个赞,你的鼓励是咱前进的动力。如果你发现哪有错误,还请评论区指正一些,感谢感谢。