InputStream和Reader
InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以它们的方法是所有输入流都可使用的方法。
idea 查看他们的 父子关系
Reader 的子类
InputStream 的子类会更多。
在InputStream里包含如下三个方法。
- int read():从输入流中读取单个字节(相当于从水管中取出一滴水),返回所读取的字节数据(字节数据可直接转换为int类型)。
- int read(byte[] b):从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
- int read(byte[] b, int off, int len):从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数。
在Reader里包含如下三个方法。
- int read():从输入流中读取单个字符
- int read(char[] cbuf):从输入流中最多读取cbuf.length个字符的数据,并将其存储在字符数组cbuf中,返回实际读取的字符数。
- int read(char[] cbuf, int off, int len):从输入流中最多读取len个字符的数据,并将其存储在字符数组cbuf中,放入数组cbuf中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数。
InputStream和Reader所提供的方法功能基本是一样的。
InputStream和Reader都是将输入数据抽象成水管,所以程序既可以通过read()方法每次读取一个“水滴”,也可以通过read(char[]
cbuf)或read(byte[] b)方法来读取多个“水滴”。当使用数组作为read()方法的参数时,可以理解为使用一个“竹筒”到水管中取水。
read(char[]cbuf)方法中的数组可理解成一个“竹筒”,程序每次调用输入流的read(char[] cbuf)或read(byte[] b)方法,就相当于用“竹筒”从输入流中取出一筒“水滴”,程序得到“竹筒”里的“水滴”后,转换成相应的数据即可;
程序多次重复这个“取水”过程,直到read(char[] cbuf)或read(byte[] b)方法返回−1,即表明到了输入流的结束点
InputStream 和 Reader 都是抽象类,本身不能创建实例,但他们分别有一个可用于读取文件的输入流:
FileInputStream 和 FileReader
他们都是节点流------会直接和指定文件关联。
下面程序示范了使用 FileInputStream 来读取自身的效果
public class FileInputStreamTest {
public static void main(String[] args) {
FileInputStream in = null;
try {
// 在类文件右键获取路径
in = new FileInputStream("src\\com\\rrz\\FileInputStreamTest.java");
int read;
while ((read = in.read()) > 0) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert in != null;
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class FileInputStreamTest {
public static void main(String[] args) {
FileInputStream in = null;
try {
// 在类文件右键获取路径
in = new FileInputStream("src\\com\\rrz\\FileInputStreamTest.java");
int hasRead;
byte[] bytes = new byte[1024];
while ((hasRead = in.read(bytes)) > 0) {
System.out.print(new String(bytes, 0, hasRead));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert in != null;
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意:上面程序创建了一个长度为 1024 的 字节数组来读取该文件,实际上该 java 源文件的长度还不到1024 字节,也就是说,程序只需要执行一次 read ()方法即可读取全部内容。但如果创建较小长度的字节数组,程序运行时在输出中文注释时可能出现乱码—这是因为本文件保存时采用的 GBK 编码方式,在这种方式下,每个中文字符占 2 字节,如果 read()方法读取时只读到了 半个中文字符,这将导致乱码。
上面程序最后使用了 fis.close()来关闭该文件输入流,与 JDBC 编程一样,程序里打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源。java 7改写了所有的 IO 资源类,他们都实现了 AutoCloseable 接口,因此都可以通过自动关闭资源的 try 语句来关闭这些 IO 流。
public class FileReaderTest {
public static void main(String[] args) {
try (FileReader in = new FileReader("src\\com\\rrz\\FileReaderTest.java")) {
int hasRead;
char[] chars = new char[32];
while ((hasRead = in.read(chars)) > 0) {
System.out.print(new String(chars, 0, hasRead));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面的FileReaderTest.java程序与前面的FileInputStreamTest.java并没有太大的不同,程序只是将字符数组的长度改为32,这意味着程序需要多次调用read()方法才可以完全读取输入流的全部数据。程序最后使用了自动关闭资源的try语句来关闭文件输入流,这样可以保证输入流一定会被关闭。
除此之外,InputStream和Reader还支持如下几个方法来移动记录指针。
- void mark(int readAheadLimit):在记录指针当前位置记录一个标记(mark)。
- boolean markSupported():判断此输入流是否支持mark()操作,即是否支持记录标记。
- void reset():将此流的记录指针重新定位到上一次记录标记(mark)的位置。
- long skip(long n):记录指针向前移动n个字节/字符。
上一节:流的概念模型
下一节:OutputStream 和 Writer