一、什么是IO流
Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。
按数据来源(去向)分类:
- 文件:FileInputStream、FileOutputStream、FileReader、FileWriter
- 数组:字节数组(byte[]):ByteArrayInputStream、ByteArrayOutputStream
- 字符数组(char[]):CharArrayReader、CharArrayWriter
- 管道操作:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter
- 基本数据类型:DataInputStream、DataOutputStream
- 缓冲操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
- 对象序列化反序列化:ObjectInputStream、ObjectOutputStream
- 打印:PrintStream、PrintWriter
- 转换:InputStreamReader、OutputStreWriter
根据处理数据类型不同分类
- 字节流:数据流中最小的数据单元是字节。
- 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。
字节流读取单个字节,字符流读取单个字符,字节流用来处理二进制文件如:图片、MP3、视频文件等等,字符流用来处理文本文件,可以看做是特殊的二进制文件,使用了某种编码,人可以阅读。
根据数据流向不同分类
- 输入流: 程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道
- 输出流:程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。
二、源码分析
IO 类虽然很多,但最基本的是 4 个抽象类:InputStream、OutputStream、Reader、Writer。最基本的方法也就是一个读 read() 方法、一个写 write() 方法。方法具体的实现还是要看继承这 4 个抽象类的子类,毕竟我们平时使用的也是子类对象。
字节输入流:InputStream
public abstract class InputStream implements Closeable {
//读取一个字节数据,并返回读到的数据,如果返回-1,表示读到了输入流的末尾。
public abstract int read() throws IOException;
//将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
//将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。
//off指定在数组b中存放数据的起始偏移位置;len指定读取的最大字节数。
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
//在输入流中跳过n个字节,并返回实际跳过的字节数。
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
//返回在不发生阻塞的情况下,可读取的字节数。
public int available() throws IOException {
return 0;
}
//关闭输入流,释放和这个流相关的系统资源。
public void close() throws IOException {}
//标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,
//可以使用 markSupport() 方法判断
public synchronized void mark(int readlimit) {}
//返回到上一个标记。
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
//测试当前流是否支持mark和reset方法。如果支持,返回true,否则返回false。
public boolean markSupported() {
return false;
}
}
字节输出流:OutputStream
public abstract class OutputStream implements Closeable, Flushable {
//往输出流中写入一个字节。
public abstract void write(int b) throws IOException;
//往输出流中写入数组b中的所有字节。
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
//往输出流中写入数组b中从偏移量off开始的len个字节的数据。
public void write(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
//强制刷新,将缓冲中的数据写入
public void flush() throws IOException {
}
//关闭输出流,释放和这个流相关的系统资源。
public void close() throws IOException {
}
}
```
**字符输入流Reader**
```java
public abstract class Reader implements Readable, Closeable {
//读取字符到字符缓存中
public int read(java.nio.CharBuffer target) throws IOException {
int len = target.remaining();
char[] cbuf = new char[len];
int n = read(cbuf, 0, len);
if (n > 0)
target.put(cbuf, 0, n);
return n;
}
//读取一个字符,返回值为读取的字符
public int read() throws IOException {
char cb[] = new char[1];
if (read(cb, 0, 1) == -1)
return -1;
else
return cb[0];
}
//读取一系列字符到数组cbuf[]中,返回值为实际读取的字符的数量
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
//读取len个字符,从数组cbuf[]的下标off处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现
abstract public int read(char cbuf[], int off, int len) throws IOException;
//跳过指定长度的字符数量
public long skip(long n) throws IOException {
if (n < 0L)
throw new IllegalArgumentException("skip value is negative");
int nn = (int) Math.min(n, maxSkipBufferSize);
synchronized (lock) {
if ((skipBuffer == null) || (skipBuffer.length < nn))
skipBuffer = new char[nn];
long r = n;
while (r > 0) {
int nc = read(skipBuffer, 0, (int)Math.min(r, nn));
if (nc == -1)
break;
r -= nc;
}
return n - r;
}
}
//告诉此流是否已准备好被读取。
public boolean ready() throws IOException {
return false;
}
//判断当前流是否支持标记流
public boolean markSupported() {
return false;
}
//标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,
//可以使用 markSupport() 方法判断
public void mark(int readAheadLimit) throws IOException {
throw new IOException("mark() not supported");
}
//返回到上一个标记。
public void reset() throws IOException {
throw new IOException("reset() not supported");
}
//关闭输入流,释放和这个流相关的系统资源。
abstract public void close() throws IOException;
}
字符输出流Writer
public abstract class Writer implements Appendable, Closeable, Flushable {
//将整型值c的低16位写入输出流
public void write(int c) throws IOException {
synchronized (lock) {
if (writeBuffer == null){
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
//将字符数组cbuf[]写入输出流
public void write(char cbuf[]) throws IOException {
write(cbuf, 0, cbuf.length);
}
//将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流
abstract public void write(char cbuf[], int off, int len) throws IOException;
//将字符串str中的字符写入输出流
public void write(String str) throws IOException {
write(str, 0, str.length());
}
//将字符串str 中从索引off开始处的len个字符写入输出流
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
char cbuf[];
if (len <= WRITE_BUFFER_SIZE) {
if (writeBuffer == null) {
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
cbuf = writeBuffer;
} else { // Don't permanently allocate very large buffers.
cbuf = new char[len];
}
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}
//追加写入一个字符序列
public Writer append(CharSequence csq) throws IOException {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}
//追加写入一个字符序列的一部分,从 start 位置开始,end 位置结束
public Writer append(CharSequence csq, int start, int end) throws IOException {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}
//追加写入一个 16 位的字符
public Writer append(char c) throws IOException {
write(c);
return this;
}
//强制刷新,将缓冲中的数据写入
abstract public void flush() throws IOException;
//关闭输出流,流被关闭后就不能再输出数据了
abstract public void close() throws IOException;
}
三、运用场景
1、字节流与字符流的转换
- InputStreamReader:将一个字节流中的字节解码成字符。
- OutputStreamWriter:将写入的字符编码成字节后写入一个字节流。
字节流和字符流转换看一下例子就行了:
1. 创建一个文本文件:bobo.txt,然后写入内容:波波。
2. 用FileInputStream读出文本内容然后以字节数组的形式缓存在fileInputStream里面。
3. 用InputStreamReader 把fileInputStream缓存的字节数组读取到定义的charArray的字符数组里面,并且输出。
4. 用OutputStreamWriter把字符数组charArray里面的字符写到InputStreamReader的字节数组缓存起来,然后输出。
5. 输出结果如下图所示。
运行输出:
2、字节数组的输入输出
ByteArrayOutputStream :字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
ByteArrayInputStream :字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。
输出:
[1, 2, 3]
3、对象的输入输出
java.io.ObjectOutputStream是实现序列化的关键类,它可以将一个对象转换成二进制流通过write()方法写入到OutputStream输出流中。然后可以通过ObjectInputStream的read()方法将对应的InputStream二进制流还原成对象。
输出
false
list:[1, 2, 3] list1:[1, 2, 3]
4、控制台的输入输出
- System.out:标准的输出流,out是System类里面的静态变量,其类型是PrintStream,PrintStream继承于FilterOutputStream,也是输出类OutputStream的子类。
- System.in:标准的输入流,in是System类里面的静态变量,其类型是
InputStream。
我们常用的System.out和System.in作为控制台的输出与输入。
单个字符输入
整行字符输入:
运行输出:
请输入一行字符:
bobo
输入的内容为:bobo
5、二进制文件的输入输出
输出:
6、文本文件的输入输出
用FileWriter和FileReader读取文本文件:
输出:
使用字节流和字符流的转换类 InputStreamReader 和 OutputStreamWriter 可以指定文件的编码,使用 Buffer 相关的类来读取文件的每一行。
运行输出:
编码方式为:GBK
波波测试文件的读写