吃透Java基础十二:IO

一、什么是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、视频文件等等,字符流用来处理文本文件,可以看做是特殊的二进制文件,使用了某种编码,人可以阅读。

吃透Java基础十二:IO

根据数据流向不同分类

  • 输入流: 程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道
  • 输出流:程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。

二、源码分析

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. 输出结果如下图所示。


吃透Java基础十二:IO


运行输出:


吃透Java基础十二:IO


2、字节数组的输入输出

ByteArrayOutputStream :字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。

ByteArrayInputStream :字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。


吃透Java基础十二:IO


输出:

[1, 2, 3]

3、对象的输入输出

java.io.ObjectOutputStream是实现序列化的关键类,它可以将一个对象转换成二进制流通过write()方法写入到OutputStream输出流中。然后可以通过ObjectInputStream的read()方法将对应的InputStream二进制流还原成对象。


吃透Java基础十二:IO


输出

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作为控制台的输出与输入。

单个字符输入


吃透Java基础十二:IO


整行字符输入:


吃透Java基础十二:IO


运行输出:

请输入一行字符:
bobo
输入的内容为:bobo 

5、二进制文件的输入输出


吃透Java基础十二:IO


输出:


吃透Java基础十二:IO


6、文本文件的输入输出

用FileWriter和FileReader读取文本文件:


吃透Java基础十二:IO


输出:


吃透Java基础十二:IO


使用字节流和字符流的转换类 InputStreamReader 和 OutputStreamWriter 可以指定文件的编码,使用 Buffer 相关的类来读取文件的每一行。


吃透Java基础十二:IO


运行输出:

编码方式为:GBK
波波测试文件的读写


分享到:


相關文章: