文章目录
学习日记(IO 流详细内容上篇)
一、IO 流概述
IO 流也成为输入、输出流,是用来读写数据的。
- I 表示 input,是数据从硬盘文件读入到内存的过程,称之为输入,负责读;
- O 表示 output,是内存程序中的数据从内存写出到磁盘文件的过程,称之为输出,负责写。
流的分类:字节输入流、字节输出流、字符输入流、字符输出流。
- 字节输入流:磁盘文件/网络中的数据(以字节的方式)—>(读入到)内存;
- 字节输出流:内存中的数据(以字节的方式)—>(写入到)磁盘文件/网络;
- 字符输入流:磁盘文件/网络中的数据(以字符的方式)—>(读入到)内存;
- 字符输出流:内存中的数据(以字符的方式)—>(写入到)磁盘文件/网络;
其中,字节流可以读写任何文件,但更适合读写音视频文件;字符流更适合读写文本文件。
二、文件字节输入流(FileInputStream)
读取文件内容的步骤:
- 创建一个文件字节输入流管道与源文件接通,如:
InputStream is = new FileInputStream("基础语法\\src\\data.txt");
; - 调用方法读取字节。
1. 每次读取一个字节
方法名 | 说明 |
---|---|
public abstract int read() throws IOException | 每次读取一个字节返回,返回值类型为 int,如果字节已经读完,则返回 -1 |
注意:因为 read 方法的返回值为字节,要想看到对应的字符,需要强制类型转化。
存在的问题:性能较慢;读取中文字符输出无法避免乱码问题(因为每次读取只能是一个字节,而中文字符一般用两个或三个字节表示)。
2. 每次读取一个字节数组
方法名 | 说明 |
---|---|
public int read(byte b[]) throws IOException | 每次读取一个字节数组返回,返回值类型为 int,如果字节已经读完,则返回 -1 |
注意:read 方法返回 int 类型的值为每次读取的字节个数,而字节数组中存放着每个读取的字节;第三个参数 len 表示长度。
存在的问题:读取的性能得到了提升,但读取中文字符输出无法避免乱码问题。
3. 一次读完全部字节
- 方案一:定义一个和文件大小一样大的字节数组,然后用读取字节数组的方法,一次性读取完成。
- 方案二:用
readAllBytes
方法直接把文件的全部数据读取到一个字节数组中,如:byte[] buffer = is.readAllBytes();
,然后将字节数组解码为字符,注意:该方法从 JDK 9 开始。
总结:一次性读完全部字节可以保证字节输入流读取中文内容输出不乱码,如果文件过大,定义的字节数组可能引起内存溢出。
三、文件字节输出流(FileOutputStream)
写字节数据到文件的步骤:
- 创建一个文件字节输出流管道与目标文件接通,如:
OutputStream os = new FileOutputStream("基础语法\\src\\data2.txt");
; - 调用方法写入数据。
分为三种:写一个字节进去、写一个字节数组进去、写一个字节数组的一部分进去。
方法名 | 说明 |
---|---|
public abstract void write(int b) throws IOException | 写一个字节进去 |
public void write(byte b[]) throws IOException | 写一个字节数组进去 |
public void write(byte b[], int off, int len) throws IOException | 写一个字节数组的一部分进去 |
注意:在第三个方法中,第二个参数是起始位置(索引,从 0 开始),第三个参数是长度,表示要写几个字节。
补充知识:
- 若要输出换行,则:
os.write("\r\n".getBytes());
。 - 创建一个文件字节输出流管道与目标文件接通时,如果文件不存在,会自动创建文件,注意加上文件的后缀名。
OutputStream os = new FileOutputStream("基础语法\\src\\data2.txt");
表示每次写的时候都会先清空之前的数据,然后再写入数据。若想在原先的数据中继续写入数据,则应该追加管道,即增加一个参数,如:OutputStream os = new FileOutputStream("基础语法\\src\\data2.txt", true);
。- 如果要写入中文字符,不能用”写入一个字节“这种方法,而应该用”写入字节数组“这种方法,先用
getBytes
方法将字符串变为字节数组,再写入。 - 在全部写完数据后,要用
os.flush();
和os.close();
来刷新数据和关闭流(释放资源,关闭流包括了刷新数据)。 - 刷新数据后可以继续使用流,关闭流后不能再使用流。
四、文件拷贝
支持一切文件的复制,因为所有的文件都是由字节组成的,只要前后文件格式、编码一致就没有问题。
- 对于小文件的拷贝,直接一次读完全部字节。
需求:把 data3 文件(156 字节)拷贝到 E:\桌面\一些文件 中。
- 对于大文件的拷贝,用每次读取一个字节数组的方法(常用)。
需求:将 E:\Do.docx 文件(226 KB,232,087 字节)拷贝到 E:\桌面\一些文件 下。
五、释放资源的两种方式
- try - catch - finally
- try - with - resource
1. try - catch - finally
特点:被 finally 控制的语句最后一定会执行,除非 JVM 退出。
作用:finally 代码块是最终一定要执行的,可以在代码执行完毕的最后用于释放资源。
2. try - with - resource
资源对象:实现了 Closeable/AutoCloseable 接口的类对象。
特点和作用:自动释放资源、代码简洁。
package com.residue.IOstream;
import java.io.*;
public class CopyDemo02 {
public static void main(String[] args) {
//需求:将 E:\Do.docx 文件拷贝到 E:\桌面\一些文件 下
try (
InputStream is = new FileInputStream("E:\\Do.docx");
OutputStream os = new FileOutputStream("E:\\桌面\\一些文件\\Do.docx");
) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len); //写一个字节数组
os.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
六、文件字符输入流(FileReader)
读取文件内容的步骤:
- 创建一个字符输入流管道与源文件接通,如:
Reader fr = new FileReader("基础语法\\src\\data3.txt");
; - 调用方法读取字符。
1. 每次读取一个字符
方法名 | 说明 |
---|---|
public int read() throws IOException | 每次读取一个字符返回,返回值类型为 int,如果字符已经读完,则返回 -1 |
注意:因为 read 方法的返回值为字符的编号,要想看到对应的字符,需要强制类型转化。
字符流的好处:如果代码和文件编码一致,读取中文字符不会出现乱码。
问题:性能较慢。
2. 每次读取一个字符数组
方法名 | 说明 |
---|---|
public int read(char cbuf[]) throws IOException | 每次读取一个字符数组返回,返回值类型为 int,如果字符已经读完,则返回 -1 |
注意:read 方法返回 int 类型的值为每次读取的字符个数,而字符数组中存放着每个读取的字符;第三个参数 len 表示长度。
每次读取一个字符数组的优势:读取的性能得到了提升。
七、文件字符输出流(FileWriter)
写字符数据到文件的步骤:
- 创建一个文件字符输出流管道与目标文件接通,如:
Writer fw = new FileWriter("基础语法\\src\\data5.txt");
; - 调用方法写入数据。
分为五种:写一个字符进去、写一个字符串进去、写一个字符数组进去、写一个字符串的一部分进去、写一个字符数组的一部分进去。
方法名 | 说明 |
---|---|
public void write(int c) throws IOException | 写一个字符进去 |
public void write(String str) throws IOException | 写一个字符串进去 |
public void write(char cbuf[]) throws IOException | 写一个字符数组进去 |
public void write(String str, int off, int len) throws IOException | 写一个字符串的一部分进去 |
public void write(char cbuf[], int off, int len) throws IOException | 写一个字符数组的一部分进去 |
注意:其他注意点和文件字节输出流的注意点相同。
八、总结
字节流和字符流如何选择使用:
- 字节流适合做一切文件的拷贝,如音视频、文本等;
- 字节流不适合读取中文内容输出;
- 字符流适合做文本文件的操作,如读、写。
注意:
- 读的时候同时只能有一个 read,否则读的内容错误。
-
文件字节输入流中,每次读取一个字节,返回值为 int,表示字节的大小;每次读取一个字节数组,返回值也为 int,但是表示每次读取的字节个数。
-
需要加 if 语句判断流是否为 null,否则,如果在流产生前就出现异常,会报空指针异常。
- 不建议 finally 中加 return 语句。
- 资源对象是指实现了 Closeable/AutoCloseable 接口的类对象。
- 字符和对应的字节(编号)相互转换用类型转换。
- 将字符串变为字符数组:调用
toCharArray
方法,如:char[] chars = "你好啊帅哥!".toCharArray();
。 - 将字符串变为字节数组:调用
getBytes
方法,如:byte[] bytes = "今天星期四".getBytes();
。 - 将字节数组或字符数组变为字符串:使用 String 的构造器。