1.数据流

程序很少作为一个孤岛存在。程序通常以某种方式与“外部世界”交互。这可以通过从键盘读取数据、发送消息、从 Internet 下载页面,或者相反,将文件上传到远程服务器来实现。

我们可以用一个词来指代所有这些行为:程序与外界之间的数据交换。等等,这不仅仅是一个词。

当然,数据交换本身可以分为接收数据和发送数据两部分。例如,您使用Scanner对象从键盘读取数据——这就是接收数据。然后您使用命令在屏幕上显示数据System.out.println()——这就是发送数据。

在编程中,术语“流”用于描述数据交换。这个词是从哪里来的?

在现实生活中,你可以有一股水流,也可以有一股意识流。在编程中,我们有数据流

流是一种多功能工具。它们允许程序从任何地方接收数据(输入流)并向任何地方发送数据(输出流)。因此,有两种类型:

  • 输入流用于接收数据
  • 输出流用于发送数据

为了使流“有形”,Java 的创建者编写了两个类:InputStreamOutputStream.

该类InputStream有一个read()方法可以让您从中读取数据。该类OutputStream有一个write()方法可以让你向它写入数据。他们还有其他方法,但稍后会详细介绍。

字节流

我们在谈论什么样的数据?它采用什么格式?换句话说,这些类支持哪些数据类型?

这些是通用类,因此它们支持最常见的数据类型 — byte. AnOutputStream可以写入字节(和字节数组),而InputStream对象可以读取字节(或字节数组)。就是这样——它们不支持任何其他数据类型。

因此,这些流也被称为字节流

流的一个特点是它们的数据只能按顺序读取(或写入)。如果不读取流之前的所有数据,就无法从流的中间读取数据。

这就是通过类从键盘读取数据的方式Scanner:您逐行顺序地从键盘读取数据。我们读一行,然后读下一行,再读下一行,依此类推。恰当地,读取行的方法称为nextLine()

将数据写入 anOutputStream也是顺序发生的。一个很好的例子是控制台输出。您输出一行,然后是另一行。这是顺序输出。你不能输出第一行,然后是第十行,然后是第二行。所有数据仅按顺序写入输出流。

字符流

您最近了解到字符串是第二流行的数据类型,而且确实如此。许多信息以字符和整个字符串的形式传递。计算机擅长以字节形式发送和接收所有内容,但人类并不是那么完美。

考虑到这一事实,Java 程序员编写了另外两个类:ReaderWriter. 该类Reader与类类似InputStream,但其read()方法读取的不是字节,而是字符 ( char)。班级Writer对应班级OutputStream。就像Reader类一样,它适用于字符 ( char),而不是字节。

如果我们比较这四个类,我们会得到下图:

字节(字节) 字符(字符)
读取数据
InputStream
Reader
写入数据
OutputStream
Writer

实际应用

、和类本身不被任何人直接使用,因为InputStream它们不与任何可从中读取数据(或可写入数据)的具体对象相关联。但是这四个类有很多可以做很多事情的后代类。OutputStreamReaderWriter


2.InputStream

该类InputStream很有趣,因为它是数百个后代类的父类。它自己没有任何数据,但它有所有派生类都继承的方法。

一般情况下,流对象内部存储数据的情况很少见。流是读取/写入数据的工具,但不是存储。也就是说,也有例外。

InputStream该类及其所有子类的方法:

方法 描述
int read()
从流中读取一个字节
int read(byte[] buffer)
从流中读取字节数组
byte[] readAllBytes()
从流中读取所有字节
long skip(long n)
跳过n流中的字节(读取并丢弃它们)
int available()
检查流中剩余的字节数
void close()
关闭流

让我们简要介绍一下这些方法:

read()方法

该方法从流中read()读取一个字节并将其返回。您可能会对int返回类型感到困惑。选择此类型是因为它int是标准整数类型。的前三个字节int将为零。

read(byte[] buffer)方法

这是该方法的第二个变体read()InputStream它允许您一次从 all 中读取一个字节数组。将存储字节的数组必须作为参数传递。该方法返回一个数字——实际读取的字节数。

假设您有一个 10 KB 的缓冲区,并且您正在使用该类从文件中读取数据FileInputStream。如果文件仅包含 2 KB,则所有数据都将加载到缓冲区数组中,并且该方法将返回数字 2048(2 KB)。

readAllBytes()方法

一个很好的方法。它只是从中读取所有数据,InputStream直到用完并将其作为单字节数组返回。这对于读取小文件非常方便。大文件在物理上可能不适合内存,并且该方法将抛出异常。

skip(long n)方法

此方法允许您跳过对象的前 n 个字节InputStream。因为数据是严格按顺序读取的,所以此方法只是从流中读取前 n 个字节并将其丢弃。

返回实际跳过的字节数(如果流在n跳过字节之前结束)。

int available()方法

该方法返回流中剩余的字节数

void close()方法

close()方法关闭数据流并释放与之关联的外部资源。一旦流关闭,就不能再从中读取数据。

让我们编写一个示例程序来复制一个非常大的文件。我们不能使用该readAllBytes()方法将整个文件读入内存。例子:

代码 笔记
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStream用于从文件中
OutputStream读取 用于写入

文件 将数据读入的缓冲区
只要流中有数据

将数据读入缓冲区
将缓冲区中的数据写入第二个流

在此示例中,我们使用了两个类:是用于从文件读取数据FileInputStream的后代,以及用于将数据写入文件的后代。稍后我们将讨论第二类。InputStreamFileOutputStreamOutputStream

这里另一个有趣的点是real变量。当从文件中读取最后一个数据块时,它的数据量很可能少于 64KB。因此,我们需要输出的不是整个缓冲区,而是其中的一部分——第一个real字节。这正是write()方法中发生的事情。



3.Reader

该类Reader是该类的完整类比InputStream。唯一的区别是它使用字符 ( char),而不是字节。就像InputStream类一样,Reader类本身不会在任何地方使用:它是数百个子类的父类,并为所有子类定义通用方法。

Reader该类(及其所有后代类)的方法:

方法 描述
int read()
char从流中读取一个
int read(char[] buffer)
char从流中读取数组
long skip(long n)
跳过n chars流(读取并丢弃它们)
boolean ready()
检查流中是否还有剩余的东西
void close()
关闭流

这些方法与类中的方法非常相似InputStream,尽管存在细微差别。

int read()方法

char此方法从流中读取一个并返回它。类型char扩展为int,但结果的前两个字节始终为零。

int read(char[] buffer)方法

这是该方法的第二个变体read()Reader它使您可以一次从 a 中读取一个 char 数组。将存储字符的数组必须作为参数传递。该方法返回一个数字——实际读取的字符数。

skip(long n)方法

此方法允许您跳过对象的前 n 个字符Reader。它的工作原理与类的类似方法完全相同InputStream。返回实际跳过的字符数。

boolean ready()方法

true如果流中有未读字节则返回。

void close()方法

close()方法关闭数据流并释放与之关联的外部资源。一旦流关闭,就不能再从中读取数据。

为了比较,让我们编写一个复制文本文件的程序:

代码 笔记
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 128Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Reader用于从文件中
Writer读取 用于写入文件

我们将读取数据的缓冲区
只要流中有数据

将数据读入缓冲区
将缓冲区中的数据写入第二个流