输入/输出体系

处理流的用法

使用处理流的典型思路是,使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层IO设备、文件进行交互。

实际识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,它就一定是处理流。
所有节点流都是直接以物理IO节点作为构造器参数的。

使用PrintStream处理流来包装OutputStream的例子:

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 PrintStreamTest
{
public static void main(String[] args)
{
try (
var fos = new FileOutputStream("test.txt");
var ps = new PrintStream(fos))
{
// 使用PrintStream执行输出
ps.println("普通字符串");
// 直接使用PrintStream输出对象
ps.println(new PrintStreamTest());
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}

提示:由于PrintStream类的输出功能非常强大,通常如果需要输出文本内容,都应该包装成PrintStream后进行输出。

注意

使用处理流包装了底层节点流后,关闭输入/输出资源时,只要关闭最上层的处理流即可,系统会自动关闭被该处理流包装的节点流。

##输入/输出体系

Java输入/输出流体系常用流分类

分类字节输入流字节输出流字符输入流字符输出流
抽象基类InputStreamOutputStreamReaderWriter
访问文件FileInputStreamFileOutputStreamFileReaderFileWriter
访问数组ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
访问字符串StringReaderStringWriter
缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
转换流InputStreamReaderOutputStreamWriter
对象流ObjectInputStreamObjectOutputStream
抽象基类FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
打印流PrintStreamPrintWriter
推回输入流PushbackInputStreamPushbackReader
特殊流DataInputStreamDataOutputStream

注意

字节流的功能比字符流的功能强大,因为计算机的所有数据都是二进制的,但问题是,如果使用字节流来处理文本文件,需要使用合适的方式把字节转换成字符,增加了编程难度。所以一个规则是:如果进行输入输出的数是文本文件,则考虑使用字符流,如果是二进制内容,考虑字节流。

计算机的文件分类

计算机文件通常分为二进制文件和文本文件两类,所有能用记事本打开并且可以看到字符内容的文件都是文本文件,反之则是二进制文件。实际上,计算机里所有文件都是二进制文件,文本文件只是二进制的一种特例。当二进制的内容刚好能被正常解析成文本,该二进制文件就变成了文本文件。如果要看到正常的文本内容,则必须在打开文件时与保存文件时使用相同的字符集,Windows系统简体中文默认使用GBK字符集,Linux系统默认使用UTF-8字符集。

转换流

输入/输出体系提供了两个转换流InputStreamReader 、OutputStreamWriter,用于将字节流转换为字符流,一个将字节输入流转换为字符输入流,一个将字节输出流转换为字符输出流。

Java没有提供将字符流转换为字节流的类。

Java使用System.in代表标准输入,但这个标准输入流是InputStream类的实例,使用不方便,而且键盘输入都是文本内容,所以可以使用InputStreamReader将其转换为字符输入流,普通的Reader读取输入内容依然不太方便,可以将Reader再次包装成BufferedReader,利用BufferedReader的readLine()方法可以一次性读取一行。

如下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

import java.io.*;

public class KeyinTest
{
public static void main(String[] args)
{
try (
// 将Sytem.in对象转换成Reader对象
var reader = new InputStreamReader(System.in);
// 将普通Reader包装成BufferedReader
var br = new BufferedReader(reader))
{
String line = null;
// 采用循环方式来一行一行的读取
while ((line = br.readLine()) != null)
{
// 如果读取的字符串为"exit",程序退出
if (line.equals("exit"))
{
System.exit(1);
}
// 打印读取的内容
System.out.println("输入内容为:" + line);
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}

推回输入流

有两个特殊的类,就是PushbackInputStream和PushbackReader,他们都提供了三个方法:

void unread(byte[]/char[] buf)

功能:将一个字节/字符数组推回到缓冲区里,从而允许重复读取内容。

参数:

  • byte[]/char[] buf:需要推回的字节/字符数组;

返回值:无


void unread(byte[]/char[] buf, int off, int len)

功能:将一个字节/字符数组从off开始,长度为len字节/字符的内容推回到缓冲区里,从而允许重复读取内容。

参数:

  • byte[]/char[] buf:需要推回的字节/字符数组;
  • off:需要推回内容的开始位置;
  • len:推回内容的长度

返回值:无


void unread(int b)

功能:将一个字节/字符推回到缓冲区里,从而允许重复读取内容。

参数:

  • b:需要推回的字节/字符;

返回值:无


这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread()方法时,系统会把指定内容推回该缓冲区,而推回输入流每次调用read()方法时,总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容以后,才会从原输入流读取。

创建推回输入流实例需要指定缓冲区大小,默认缓冲区长度为1,如果推回缓冲区内容超过缓冲区大小,会引发异常。

下面程序找出程序的"new PushbackReader"字符串,找到后,程序打印目标字符串之前的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

import java.io.*;

public class PushbackTest
{
public static void main(String[] args)
{
try (
// 创建一个PushbackReader对象,指定推回缓冲区的长度为64
var pr = new PushbackReader(new FileReader(
"PushbackTest.java"), 64))
{
var buf = new char[32];
// 用以保存上次读取的字符串内容
var lastContent = "";
var hasRead = 0;
// 循环读取文件内容
while ((hasRead = pr.read(buf)) > 0)
{
// 将读取的内容转换成字符串
var content = new String(buf, 0, hasRead);
var targetIndex = 0;
// 将上次读取的字符串和本次读取的字符串拼起来,
// 查看是否包含目标字符串, 如果包含目标字符串
if ((targetIndex = (lastContent + content)
.indexOf("new PushbackReader")) > 0)
{
// 将本次内容和上次内容一起推回缓冲区
pr.unread((lastContent + content).toCharArray());
// 重新定义一个长度为targetIndex的char数组
if (targetIndex > 32)
{
buf = new char[targetIndex];
}
// 再次读取指定长度的内容(就是目标字符串之前的内容)
pr.read(buf, 0, targetIndex);
// 打印读取的内容
System.out.print(new String(buf, 0, targetIndex));
System.exit(0);
}
else
{
// 打印上次读取的内容
System.out.print(lastContent);
// 将本次内容设为上次读取的内容
lastContent = content;
}
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}



输入/输出体系
https://zhaoquaner.github.io/2022/05/11/Java/输入输出/输入输出体系/
更新于
2022年5月22日
许可协议