流
导航 用户界面 主题: ) |
Java 中最基本输入和输出 (System.in
和 System.out
字段,已在 基本 I/O 中使用) 是通过流完成的。流是表示数据源和数据目标的对象。作为数据源的流可以从中读取,而作为数据目标的流可以向其中写入。Java 中的流是长度不确定的字节的有序序列。流是有序且按顺序排列的,以便 Java 虚拟机能够理解并处理该流。流类似于水流。它们像通信中的电磁波一样,作为通信媒介存在。Java 流中的字节顺序或序列使虚拟机能够将其归类于其他流。
Java 具有各种内置流,这些流在 java.io
包中作为类实现,例如 System.in
和 System.out
的类。流可以被归类为输入流和输出流。所有 Java 流都源自输入流 (java.io.InputStream
) 和输出流 (java.io.OutputStream
) 类。它们是为其他流类准备的抽象基类。System.in
是输入流类衍生物,而 System.out
则是输出流类衍生物。两者都是用于直接与控制台进行交互的基本类,类似地,System.err
也遵循此规则。Java 还拥有用于在程序的不同部分甚至线程之间进行通信的流。还有一些类可以“过滤”流,将一种格式更改为另一种格式 (例如,类 DataOutputStream
,它将各种原始类型转换为字节流)。
流的一个特点是它们一次只处理一个离散的数据单元,并且不同的流处理不同类型的数据。例如,如果有一个表示字节目标的流,那么可以一次发送一个字节的数据到该目标。如果一个流是字节数据的源,那么可以一次读取一个字节的数据。由于这是访问流中数据的唯一方式,因此在后一种情况下,在实际到达流的末尾之前,我们并不知道何时已从流中读取了所有数据。在读取流时,通常必须在每次读取操作中检查每个数据单元,以查看是否已到达流的末尾 (对于字节流,特殊值为整数 -1 或 FFFF 十六进制)。
输入流为我们编写的 Java 应用程序/程序获取字节(例如文件、数组、键盘或显示器等)。InputStream
是一个抽象类,它代表字节数据的源。它有一个 read()
方法,该方法返回流中的下一个字节,还有一个 close()
方法,该方法应在程序完成对流的操作时由程序调用。read()
方法是重载的,可以接受一个字节数组来读取。它有一个 skip()
方法可以跳过一定数量的字节,还有一个 available()
方法,程序可以使用它来确定立即可读的字节数,因为并非所有数据都一定立即准备好。作为一个抽象类,它不能被实例化,但描述了输入流的一般行为。一些具体子类的例子是 ByteArrayInputStream
,它从字节数组读取,和 FileInputStream
,它从文件读取字节数据。
在 以下示例 中,我们在屏幕上多次打印 "Hello world!"。打印消息的次数存储在一个名为 source.txt
的文件中。此文件应只包含一个整数,并应放在与 ConfiguredApplication
类相同的文件夹中。
代码清单 9.1:输入流示例。
import java.io.File;
import java.io.FileInputStream;
public class ConfiguredApplication {
public static void main(String[] args) throws Exception {
// Data reading
File file = new File("source.txt");
FileInputStream stream = new FileInputStream(file);
StringBuffer buffer = new StringBuffer();
int character = 0;
while ((character = stream.read()) != -1) {
buffer.append((char) character);
}
stream.close();
// Data use
Integer readInteger = Integer.parseInt(buffer.toString());
for (int i = 0; i < readInteger ; i++) {
System.out.println("Hello world!");
}
}
}
|
close()
方法并不总是强制性的,但可以避免一些进程间并发冲突。但是,如果它在 read()
或 write()
(在同一个进程中)之前发生,它们将返回警告 Stream closed
。
该类开始使用 File
对象来识别文件名。File
对象由输入流用作流的源。我们创建了一个缓冲区和一个字符来准备数据加载。缓冲区将包含所有文件内容,字符将临时包含文件中存在的每个字符,一次一个。这是在 while{}
循环中完成的。循环的每次迭代都将从流复制一个字符到缓冲区。当流中不再存在字符时,循环结束。然后我们关闭流。代码的最后部分使用我们从文件中加载的数据。它被转换为字符串,然后转换为整数(因此数据必须是整数)。如果它有效,该整数将用于确定我们在屏幕上打印 "Hello world!" 的次数。为了可读性,没有定义 try/catch 块,但应捕获抛出的异常。
让我们尝试使用以下源文件
代码清单 9.2:source.txt
4 |
我们应该得到这个
ConfiguredApplication 的输出
$ java ConfiguredApplication Hello world! Hello world! Hello world! Hello world! |
如果显示 FileNotFoundException 或 IOException ,则源可能没有放在正确的文件夹中或其名称拼写错误。如果显示 NumberFormatException ,则文件的内容可能不是整数。 |
还有 Reader
,它是一个抽象类,代表字符数据的源。它类似于 InputStream
,只是它处理的是字符而不是字节(请记住,Java 使用 Unicode,因此一个字符是 2 个字节,而不是一个)。它的方法通常与 InputStream
的方法相似。具体子类包括 FileReader
等类,它从文件读取字符,以及 StringReader
,它从字符串读取字符。你还可以使用 InputStreamReader
类将 InputStream
对象转换为 Reader 对象,该类可以“包装”在 InputStream
对象周围(通过将其作为参数传递给它的构造函数)。它使用字符编码方案(程序员可以更改)将字节转换为 16 位 Unicode 字符。
输出流
[edit | edit source]输出流将字节流从我们的程序或应用程序定向到外部环境。OutputStream
是一个抽象类,它是 InputStream
的目标对应类。OutputStream
有一个 write()
方法,可用于将字节写入流。该方法是重载的,可以接受一个数组。close()
方法在应用程序完成对流的操作时关闭流,它还有一个 flush()
方法。流可能会等到有了一定数量的字节才将其一次性写入,以提高效率。如果流对象在写入之前缓冲了任何数据,则 flush()
方法将强制它写入所有这些数据。与 InputStream
一样,该类不能被实例化,但它有与 InputStream
相对应的具体子类,例如 ByteArrayOutputStream
、FileOutputStream
等。
在 以下示例 中,我们将当前时间存储在一个名为 log.txt
的已经存在的文件中,该文件位于与该类相同的文件夹中。
代码清单 9.2:输出流示例。
import java.io.File;
import java.io.FileOutputStream;
import java.util.Date;
public class LogTime {
public static void main(String[] args) throws Exception {
// Generate data
String timeInString = new Date().toString();
// Store data
File file = new File("log.txt");
FileOutputStream stream = new FileOutputStream(file);
byte[] timeInBytes = timeInString.getBytes();
stream.write(timeInBytes);
stream.flush();
stream.close();
}
}
|
这种情况比较简单,因为我们可以同时将所有数据放入流中。代码的第一部分生成一个包含当前时间的字符串。然后我们创建一个 File
对象,它识别输出文件,并为该文件创建一个输出流。我们将数据写入流,刷新它并关闭它。就这样。为了可读性,没有定义 try/catch 块,但应捕获抛出的异常。
为了从开头多次读取文本文件,应引入一个 FileChannel 变量,仅用于重新定位读取器。 |
现在让我们执行它
LogTime 执行
$ java LogTime |
我们应该得到这个内容
代码清单 9.4:log.txt
Tue Oct 8 10:50:44 CEUTC 2024 |
如果显示 FileNotFoundException 或 IOException ,则该文件可能没有创建或没有放在正确的文件夹中。 |
还有 Writer
,它是 OutputStream
的字符对应类,也是 Reader 的目标对应类,它也是一个抽象超类。特定的实现与 Reader 的实现相平行,例如 FileWriter
、StringWriter
和 OutputStreamWriter
,用于将常规 OutputStream
转换为读取器,以便它可以接受字符数据。
System.out
和 System.err
[edit | edit source]System
是 java.lang
包中的一个类,它有一些可供 Java 程序使用的静态成员。两个对控制台输出有用的成员是 System.out
和 System.err
。System.out 和 System.err 都是 PrintStream
对象。PrintStream
是 FilterOutputStream
的子类,FilterOutputStream
本身是 OutputStream
(上面讨论过)的子类,它的主要目的是将各种数据类型转换为字节流,这些字节流根据某种编码方案用字符表示该数据。
System.out
和 System.err
都将文本显示到控制台中,用户可以在其中读取它,但是这的确切含义取决于使用的平台和运行程序的环境。例如,在 BlueJay 和 Eclipse IDE 中,有一个特殊的“终端”窗口将显示此输出。如果程序在 Windows 中启动,则输出将发送到 DOS 提示符(通常这意味着你必须从命令行启动程序才能看到输出)。
System.out
和 System.err
的区别在于它们应该用于什么。System.out
应该用于正常的程序输出,System.err
应该用于通知用户程序中发生了某种错误。在某些情况下,这可能很重要。例如,在 DOS 中,用户可以将标准输出重定向到其他目的地(例如文件),但错误输出不会被重定向,而是显示在屏幕上。如果没有这种情况,用户可能永远无法知道发生了错误。
新的 I/O
[edit | edit source]J2SE 1.4 之前的 Java 版本只支持基于流的阻塞 I/O。这需要每个流处理一个线程,因为在活动的线程阻塞等待输入或输出时,无法进行其他处理。对于任何需要实现 Java 网络服务的人来说,这是一个主要的扩展性和性能问题。自 J2SE 1.4 引入 NIO (New I/O) 以来,通过引入非阻塞 I/O 框架解决了扩展性问题(尽管在 Oracle 实现的 NIO API 中存在许多开放问题)。
非阻塞 IO 框架虽然比最初的阻塞 IO 框架复杂得多,但允许单个线程处理任意数量的“通道”。该框架基于 Reactor 模式。
更多信息
[edit | edit source]有关 java.io
包内容的更多信息,请单击此链接在 Oracle 网站上查看 (http://docs.oracle.com/javase/7/docs/api/index.html).