15.3 字节流和字符流 15.3.1 InputStream和Reader

15.3 字节流和字符流

字节流和字符流的操作方式几乎完全一样,区别只是操作的数据单元不同而已:

  • 字节流操作的数据单元是字节,
  • 字符流操作的数据单元是字符。

15.3.1 InputStream和Reader

InputStreamReader是所有输入流的**抽象基类**,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以它们的方法是所有输入流都可使用的方法。

InputStream常用方法

InputStream里包含如下三个方法。

方法 描述
abstract int read() 从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型)。
int read(byte[] b) 从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
int read(byte[] b, int off, int len) 从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数。

Reader常用方法

Reader里包含如下三个常用方法。

方法 描述
int read() 从输入流中读取单个字符,返回所读取的字符数据(字符数据可直接转换为int类型)
int read(char[] cbuf) 从输入流中最多读取cbuf.length个字符的数据,并将其存储在字符数组cbuf中,返回实际读取的字符数。
abstract int read(char[] cbuf, int off, int len) 从输入流中最多读取len个字符的数据,并将其存储在字符数组cbuf中,放入数组cbuf中时,并不是从数组起点开始,而是从of‘位置开始,返回实际读取的字符数。

对比InputStreamReader所提供的方法,就不难发现这两个基类的功能基本是一样的InputStreamReader都是将输入数据抽象成如图15.5所示的水管,所以程序既可以通过read()方法每次读取一个“水滴”,也可以通过read(char[] cbuf)read(byte[] b)方法来读取多个“水滴”。
当使用数组作为read()方法的参数时,可以理解为使用一个“竹筒”到如图15.5所示的水管中取水,如图15.8所示。
这里有一张图片
read(char[] cbuf)方法中的数组可理解成一个“竹筒”,程序每次调用输入流的read(cha[] cbuf)read(byte[] b)方法,就相当于用“竹筒”从输入流中取岀一筒“水滴”,程序得到“竹筒”里的“水滴”后,转换成相应的数据即可;
程序多次重复这个“取水”过程,直到最后。

如何判断已经读取完毕

程序如何判断取水取到了最后呢?直到read(char[] cbuf)read(byte[] b)方法返回-1,即表明到了输入流的结束点。

FileInputStreamFileReader

正如前面提到的,InputStreamReader都是抽象类,本身不能创建实例,但它们分别有一个用于读取文件的输入流:FileInputStreamFileReader,它们都是节点流——会直接和指定文件关联。

程序示例 读取java文件自身

下面程序示范了使用FileInputStream来读取自身的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;

public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
// 创建字节输入流
FileInputStream fis = new FileInputStream("FileInputStreamTest.java");
// 创建一个长度为1024的“竹筒”
byte[] bbuf = new byte[1024];
// 用于保存实际读取的字节数
int hasRead = 0;
// 使用循环来重复“取水”过程
while ((hasRead = fis.read(bbuf)) > 0) {
// 取出“竹筒”中水滴(字节),将字节数组转换成字符串输入!
System.out.print(new String(bbuf, 0, hasRead));
}
// 关闭文件输入流,放在finally块里更安全
fis.close();
}
}

上面程序中的while循环就是使用FileInputStream循环“取水”的过程,运行上面程序,将会输出上面程序的源代码。
上面程序创建了一个长度为1024的字节数组来读取该文件,实际上该Java源文件的长度还不到1024字节,也就是说,程序只需要执行一次read()方法即可读取全部内容。
但如果创建较小长度的字节数组,程序运行时在输岀中文注释时就可能出现乱码,这是因为本文件保存时采用的是GBK编码方式,在这种方式下,每个中文字符占2字节,如果read()方法读取时只读到了半个中文字符,这将导致乱码

文件IO资源要显示关闭

上面程序最后使用了fis.close();来关闭该文件输入流,与JDBC编程一样,程序里打开的文件IO资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源。

Java7后可使用自动关闭资源的try来关闭IO流

Java7改写了所有的IO资源类,它们都实现了AutoCloseable接口,因此都可通过自动关闭资源的try语句来关闭这些IO流。

程序示例:FileReader读取java文件本身

下面程序使用FileReader来读取文件本身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;

public class FileReaderTest {
public static void main(String[] args) {
try (
// 创建字符输入流
FileReader fr = new FileReader("FileReaderTest.java")) {
// 创建一个长度为32的“竹筒”
char[] cbuf = new char[32];
// 用于保存实际读取的字符数
int hasRead = 0;
// 使用循环来重复“取水”过程
while ((hasRead = fr.read(cbuf)) > 0) {
// 取出“竹筒”中水滴(字符),将字符数组转换成字符串输入!
System.out.print(new String(cbuf, 0, hasRead));
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

上面的FileReaderTest.java程序与前面的FileInputStreamTest.java并没有太大的不同,程序只是将字符数组的长度改为32,这意味着程序需要多次调用read()方法才可以完全读取输入流的全部数据。
程序最后使用了自动关闭资源的try语句来关闭文件输入流,这样可以保证输入流一定会被关闭。

移动记录指针的方法

除此之外,InputStreamReader还支持如下几个方法来移动记录指针。

方法 描述
void mark(int readAheadLimit) 在记录指针当前位置记录一个标记(mark)。
boolean markSupported() 判断此输入流是否支持mark()操作,即是否支持记录标记。
void reset() 将此流的记录指针重新定位到上一次记录标记(mark)的位置。
long skip(long n) 记录指针向前移动n个字节/字符。

InputStream其他方法

方法 描述
int available() Returns an estimate of the number of bytes that can be read(or skipped over) from this input stream without blocking by the next invocation of a method for this input stream.
void close() Closes this input stream and releases any system resources associated with the stream.
byte[] readAllBytes() Reads all remaining bytes from the input stream.
int readNBytes(byte[] b, int off, int len) Reads the requested number of bytes from the input stream into the given byte array.
long transferTo(OutputStream out) Reads all bytes from this input stream and writes the bytes to the given output stream in the order that they are read.

Reader其他方法

方法 描述
abstract void close() Closes the stream and releases any system resources associated with it.
int read(CharBuffer target) Attempts to read characters into the specified character buffer.
boolean ready() Tells whether this stream is ready to be read.