导读
- 本文将从什么是IO流、IO流的分类以及常用类的基本使用方法分别进行叙述。
- 本文将着重描述流式部分的结构,而对于非流式部分(如File类)无正面描述。
1 概述
- Java的IO流用于实现数据的输入和输出操作,如文件读写、网络传输等,I、O分别是Input(输入)和Output(输出)的缩写。
- 流是一种抽象,将输入设备、输出设备抽象为流,即流本质是一组有顺序的,有起点和终点的字节集合(byte[])。
- Java把所有的传统的流类型都放在了java.io包中,本质是对byte[]数组的封装。
2 分类
按照不同的分类方式,流可以进行不同的划分:
2.1 以流的方向为导向:输入流、输出流
- 输入流:只能从中读取数据,而不能向其写入数据。
- 输出流:只能向其写入数据,而不能向其读取数据。
输入流主要是InputStream和Reader作为基类,输出流主要是OutputStream和Writer作为基类。
注意:InputStream、Reader、OutputStream、Writer都是抽象类,无法直接创建实例。
输入输出不是相对于我们所用的机器而言,而是相对于程序来说的,而程序运行于内存中,即相对于内存来说,因此:
输入是将硬盘或其他存储设备的数据读入内存;输出是指将内存中的数据写入存储设备。
2.2 以流的操作单元为导向:字符流、字节流
- 字节流:一次读入或读出8位二进制数(即1字节):1 byte = 8 bit
- 字符流:一次读入或读出16位二进制数(即1字符):1 char = 2 byte = 16bit (Java默认UTF-16编码)
字节流主要是InputStream和OutputStream作为基类,字符流主要是Reader和Writer作为基类。
字节流和字符流用法非常相似,除了操作单元不同,还有如下如下区别:
①字节流在操作的时候本身不会用到缓冲区(内存),而字符流会用到缓冲区
下面给出证明:字节流在操作文件时,即使不关闭资源(close()方法),文件也能输出,而如果是字符流不用close()的话,不会输出任何内容,并且可以使用flush()强制刷新缓冲区,这时才能在不调用close()方法的情况下输出内容。
②字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符(如汉字等),而字符流可以处理
下面给出证明:Reader的read()方法返回类型为int ,作为整数读取的字符,两个字节为16为二进制,范围在 0 到 65535 之间 ,如果已到达流的末尾,则返回 -1。
InputStream的read()虽然也返回int,但由于此类是面向字节流的,一个字节为8位二进制,所以返回 0 到 255 范围内的 int 字节值,如果因为已经到达流末尾而没有可用的字节,则返回值 -1。因此对于不能用0-255来表示的值就得用字符流来读取,如汉字等。
根据字节流和字符流的性质,得到它们的适用范围:
字符流(Reader、Writer):中文,操作字符、字符串或字符数组。
字节流(InputStream、OutputStream):音频文件、图片、歌曲,所有的硬盘上保存文件或进行传输的时候,操作字节和字节数组或二进制对象。
2.3 以流的功能为导向:节点流、处理流
- 节点流:用于从/向一个特定的IO设备读/写数据的流,称为节点流。
- 处理流:用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读写功能。
2.3.1 节点流
类型 | 字节流 | 字符流 |
---|---|---|
File | FileInputStream FileOutputStream | FileReader FileWriter |
Array | ByteArrayInputStream ByteArrayOutputStream | CharArrayReader CharArrayWriter |
String | —— | StringReadr StringWriter |
Pipe | PipedInputStream PipedOutputStream | PipedReader PipedWriter |
- File:对文件进行读、写操作 :FileReader、FileWriter、FileInputStream、FileOutputStream。
- Array:从/向内存数组读/写数据:CharArrayReader与 CharArrayWriter、ByteArrayInputStream与ByteArrayOutputStream。
- String:从/向内存字符串读/写数据:StringReader、StringWriter、StringBufferInputStream。
- Pipe:实现管道的输入和输出(进程间通信): PipedReader与PipedWriter、PipedInputStream与PipedOutputStream。
2.3.2 处理流
类型 | 字节流 | 字符流 |
---|---|---|
Buffering | BufferedInputStream BufferedOutputStream | BufferedReader BufferedWriter |
Filtering | FilterInputStream FilterOutputStream | FilterReader FilterWriter |
Converting between Bytes and Characters | —— | InputStreamReader OutputStreamWriter |
Object Serialization | ObjectInputStream ObjectOutputStream | —— |
Data Conversion | DataInputStream DataOutputStream | —— |
Counting | LineNumberInputStream | LineNumberReader |
Peeking Ahead | PushbackInputStream | PushbackReader |
Printing | PrintStream | PrintWriter |
- Buffering缓冲流:在读入或写出时,对数据进行缓存,以减少I/O的次数:BufferedReader与BufferedWriter、BufferedInputStream与BufferedOutputStream。
- Filtering 过滤流:在数据进行读或写时进行过滤:FilterReader与FilterWriter、FilterInputStream与FilterOutputStream。
- Converting between Bytes and Characters 转换流:按照一定的编码/解码标准将字节流转换为字符流,或进行反向转换:InputStreamReader、OutputStreamWriter。
- Object Serialization 对象流 :ObjectInputStream、ObjectOutputStream。
- Data Conversion数据流: 按基本数据类型读、写(如:boolean、byte、int、float):DataInputStream、DataOutputStream。
- Counting计数流: 在读入数据时对行记数 :LineNumberReader、LineNumberInputStream。
- Peeking Ahead预读流: 通过缓存机制,进行预读 :PushbackReader、PushbackInputStream。
- Printing打印流: 包含方便的打印方法 :PrintWriter、PrintStream。
3 用法
对于具体IO类来说,一般应用步骤都是如下:
1、构建输入/输出流
2、读/写数据
3、关闭流(如果是jdk1.7以上,则可以使用try-with-resource的方式来代替第三步,即在try关键字后跟一对圆括号,圆括号可以声明、初始化一个或多个资源,此处的资源指的是那些必须在程序结束时关闭的资源,包括但不限于IO连接、数据库连接、网络连接等实现Closeable或AutoCloseable接口的类)
3.1 基类的使用(InputStream/Reader和OutputStream/Writer)
InputStream/Reader和OutputStream/Writer是所有输入输出流的抽象基类,本身并不能创建实例来执行输入输出,但它们是所有输入输出流的模板,它们的方法是所有输入输出流都可以使用的方法。
InputStream/Reader中:
在InputStream中包含如下3个方法:
- int read():从输入流中读取单个字节,返回读取字节数据对应的int类型。
- int read(byte[] buf):从输入流中最多读取buf.length个字节的数据,并将其存储在字节数组buf中,返回实际读取的字节数。
- int read(byte[] buf, int off, int len):从输入流中最多读取len个字节的数据,并将其从off位置开始存储在字节数组buf中,返回实际读取的字节数。
在Reader中包含如下3个方法:
- int read():从输入流中读取单个字符,返回读取字节数据对应的int类型。
- int read(char[] buf):从输入流中最多读取buf.length个字符的数据,并将其存储在字符数组buf中,返回实际读取的字符数。
- int read(char[] buf, int off, int len):从输入流中最多读取len个字符的数据,并将其从off位置开始存储在字符数组buf中,返回实际读取的字符数。
如果达到了流的末尾,将返回-1。
OutputStream/Writer中:
两个流都提供如下三个方法:
- void write(int c):将指定的字节/字符输出到输出流中,其中c既可以代表字节,也可以代表字符。
- void write(byte[]/char[] buf):将字节数组/字符数组中的数据输出到输出流中。
- void write(byte[]/char[] buf, int off, int len):将字节数组/字符数组从off位置开始,长度为len的字节/字符输出到输出流中。
因为字符流可以直接以字符作为操作单位,所以Writer可以用字符串来替代字符数组,即Writer中还包含如下两个方法:
- void write(String str):将str字符串里包含的字符输出到输出流中。
- void write(String str, int off, int len):将str字符串里从off位置开始,长度为len的字符输出到输出流中。
3.2 文件流的使用(FileInputStream/FileReader和FileOutputStream/FileWriter)
我们可以按之前所述三步(1构建输入/输出流;2读/写数据;3关闭流)进行简单的文件流操作,实现读取文件内容、输出内容到文件。
下面给出字节流的demo,字符流用法类似,不再赘述。
public class FileIODemo {
//使用FileInputStream来实现从硬盘里的文件读取内容输入到内存中
public static void readFile() {
//使用try-with-resource的方式来创建文件字节输入流,无需手动关闭
//将所指定文件内容输入到内存(控制台)中去
try (InputStream fileInputStream = new FileInputStream("D:\\IOText1.txt")) {
//创建一个可以容纳1024个字节的字节数组,将使用read(byte[] buf)将文件内容读取到字节数组buf中,一次最多能读1024个字节的数据
byte[] buf = new byte[1024];
//len为使用buf一次读取的实际字节数,特别地,如果读到了文件的末尾,返回-1
int len = -1;
//每当buf读满的时候将所读打印出来,直到文件读完为止
while ((len = fileInputStream.read(buf)) != -1) {
String fileContent = new String(buf, 0, len);
System.out.println(fileContent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//使用FileOutputStream来实现将内存中的内容输出到硬盘里的文件中
public static void writeFile() {
//使用try-with-resource的方式来创建文件字节输出流,无需手动关闭
//将所指定内存中的内容输出到制定文件中去
try (OutputStream fileOutputStream = new FileOutputStream("D:\\IOText2")) {
//通过String类的getBytes()方法来获取该字符串的字节数组形式,再使用write(byte[] buf)的方法将该字符串输出到要保存的文件中
String fileContent = "Hello World!";
fileOutputStream.write(fileContent.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3 缓冲流的使用(BufferedInputStream/BufferedReader和BufferedOutputStream/BufferedWriter)
缓冲流是一种处理流,需要与一个已创建的流进行连接和封装才能使用。(典型的装饰器模式)
使用构造函数套接的方式来创建缓冲流,如下所示:
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream("D:\\IOText"));
缓冲流可以将数据流从数据源中处理完毕都存入内存缓冲区,然后统一一次性与底层IO进行操作,可以有效降低程序直接操作IO的频率,提高IO执行速度。
以下为缓冲字符流的使用demo:
public class BufferingIODemo {
public static void readFile() {
try (BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\IOText1.txt"))) {
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void writeFile() {
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\IOText2.txt"))) {
String fileContent = "Hello World!";
bufferedWriter.write(fileContent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.4 转换流的使用(InputStreamReader/OutputStreamWriter)
转换流是字符流和字节流的桥梁,使得我们可以对读取到的字节数据经过特定的制定编码转换为字符数据,同样可以对字符数据经过特定的编码转换为字节数据。
我们在以下情况常用转换流:
- 源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁,提高对文本操作的便捷。
- 一旦操作文本涉及到具体的指定编码表时,必须使用转换流。
限于篇幅仅对第一点给出demo:
public class ConvertingIODemo {
//字节流转换为字符流
public static void convertingByteToChar() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\IOText1.txt"))) {
String line = null;
while ((line = br.readLine()) != null) {
if(line.equals("finish")){
break;
}
bw.write(line);
bw.newLine();
bw.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
//字符流转化为字节流
public static void convertingCharToByte() {
try (BufferedReader br = new BufferedReader(new FileReader("IOText2er.txt"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out))) {
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}