一、什么是“流”
1. 流的特性
在理解Java IO流这一较为抽象的概念时,可以通过类比自然界或物理中的流帮助理解。通过类比可以发现流具备以下三种特性:
载物性
:流中承载需要传输的数据;方向性
:流必定是从一端流向另一端,且同一个流不可能拥有两个方向;连续性
:流是连续不断的,且其承载的数据也具备连续性;
除了上面两个类比得到的特性,Java中的IO流还有一个独有的特性,即流的一端必定是程序(数据创造者/数据接收者),而另一端则是数据来源(数据被读出的空间)或数据目的地(数据被写入的空间)。
2. 流的类型
2.1. 基于数据流向分类
由于IO流存在方向性,所以根据数据的流向不同可以将IO流分为一下三类。需要注意的是,这里在进行流向判定时是以程序为主体进行判断。
a. 输入流
输入流又可以称为“读取流”,即通过输入流可以让程序从目标数据源中读取数据。
从上图中可以看到,对于输入流来说,CPU/外部存储为数据传输的来源,而应用程序则为数据传输的目的地,整个流程可以看做是应用程序从CPU/外部存储中读取数据的过程。
b. 输出流
输出流又可以称为“写入流”,即通过输出流可以让程序将数据写入到目标数据空间中。
从上图可以看到,对于输出流来说,应用程序为数据传输的来源,而CPU/外部存储则为数据传输的目的地,整个流程可以看做是应用程序向CPU/外部存储中写入数据的过程。
c. 合并流
合并流的概念并不是JDK提出的原生概念,而是后续使用者根据JDK给出的使用方式提出的概念。合并流本质上是通过链式结构将数据传输方向相同但是功能不尽相同的流合并在一起,实现不同的组合输入/输出效果。
在上图中可以看到,合并流的作用之一就是将逐字节的读取方式变更为合并成更大数据块的读取方式,提高读取的效率(输入流的合并流类似)。为了实现这一功能,Java IO类库中提供了BufferedInputStream
这一缓冲流来提升对于文件数据读取的效率(写入操作则通过BufferedOutputStream
来实现)。
除了上面提供的功能外,合并流的另一个作用是可以将两个数据流向相同的流合并进行对应的数据传输,在Java IO中提供了SequenceInputStream/SequenceOutputStream
来实现上述功能。这里我们通过一个简单的例子来熟悉一下SequenceInputStream
的使用。
public static void main(String[] args) {
InputStream inputStreamOne = new ByteArrayInputStream("This is a test.".getBytes());
InputStream inputStreamTwo;
try {
inputStreamTwo = new FileInputStream("/Users/brucebat/Desktop/test.sh");
SequenceInputStream sequenceInputStream = new SequenceInputStream(inputStreamTwo, inputStreamOne);
StringBuilder combineText = new StringBuilder();
int n;
while ((n = sequenceInputStream.read()) != -1) {
combineText.append((char) n);
}
System.out.println(combineText);
} catch (Exception e) {
e.printStackTrace();
}
}
上面的代码执行结果为:
代码语言:javascript复制test(){
echo 'This is a function.'
}
name='brucebat'
echo $name
test
This is a test.
进程已结束,退出代码为 0
从上面的代码可以看到,这里在使用SequenceInputStream
进行输入流合并时,并一定要求作为数据源的流对象类型相同,只需要保证是两个都是输入流即可。
2.2. 基于数据类型分类
根据IO流中承载和传输的数据类型不同,可以将IO流分为字节流和字符流。
a. 字节流
从名称当中就可以看出,在字节流当中数据传输和操作的基本单位是字节(byte,即8bit),而在Java IO类库中从属于字节流的类均会以“Stream”结尾。在实际场景中,字节流一般用于处理二进制数据、拷贝文件以及读写网络资源(由于TCP是基于字节流的传输层协议,而HTTP、FTP则是基于TCP的应用层协议)。
b. 字符流
和计算机相比,开发者更能适应于针对字符的处理逻辑而非针对二进制数据的处理逻辑。由此,在Java IO类库中提供了字符流来实现以字符(Character,一般为16bit,根据操作系统和字符集不同会存在一定不同)为基本单位的数据传输逻辑。但这种操作本质上是将字节按照指定的字符集映射为字符数据进行对应数据处理逻辑,所以字符流一般用于处理文本类数据,而无法进行其他类型数据资源处理,因为在字节->字符->字节
的过程中有很大可能出现二进制数据组合错误导致内容混乱的问题。
除了上面所说的数据处理基本单位和使用场景不同,在Java中对于字节流和字符流的处理逻辑也不尽相同,在进行字符流数据读取时会使用缓存,而字节流则不会,即当重复读取同一个文件时,字符流会读取缓存中的数据,而字节流则会每次重新读取一遍文件。
二、为什么需要“流”
从上面对于IO流的介绍中我们不难看出IO流有这样一些好处:
- 抽象出了统一的数据操作模型,这让我们在进行不同类型数据资源操作时有了统一标准的API进行处理;
- IO流提供了连续逐个/块的数据读写方式,避免了一次性处理整个大文件时可能出现的内存占用过高的问题;
三、总结
通过对IO流这一概念的学习,我们不难发现高级语言(无论是Java语言亦或是C语言)的魅力——抽象。通过抽象能力,实现了机器处理逻辑到人脑处理逻辑的升华。这是我们在学会如何使用Java IO流之外,更应努力学习和掌握的技巧。