跳转到内容

75% developed
来自维基教科书,开放的书籍,开放的世界

导航 用户界面 主题: v  d  e )


Java 中最基本输入和输出 (System.inSystem.out 字段,已在 基本 I/O 中使用) 是通过流完成的。流是表示数据源和数据目标的对象。作为数据源的流可以从中读取,而作为数据目标的流可以向其中写入。Java 中的流是长度不确定的字节的有序序列。流是有序且按顺序排列的,以便 Java 虚拟机能够理解并处理该流。流类似于水流。它们像通信中的电磁波一样,作为通信媒介存在。Java 流中的字节顺序或序列使虚拟机能够将其归类于其他流。

Java 具有各种内置流,这些流在 java.io 包中作为类实现,例如 System.inSystem.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 类相同的文件夹中。

Computer code 代码清单 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 块,但应捕获抛出的异常。

让我们尝试使用以下源文件

Computer code 代码清单 9.2:source.txt

4

我们应该得到这个

Standard input or output ConfiguredApplication 的输出
$ java ConfiguredApplication
Hello world!
Hello world!
Hello world!
Hello world!
Warning 如果显示 FileNotFoundExceptionIOException,则源可能没有放在正确的文件夹中或其名称拼写错误。
如果显示 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 相对应的具体子类,例如 ByteArrayOutputStreamFileOutputStream 等。

以下示例 中,我们将当前时间存储在一个名为 log.txt 的已经存在的文件中,该文件位于与该类相同的文件夹中。

Computer code 代码清单 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 块,但应捕获抛出的异常。

Warning 为了从开头多次读取文本文件,应引入一个 FileChannel 变量,仅用于重新定位读取器。

现在让我们执行它

Standard input or output LogTime 执行
$ java LogTime

我们应该得到这个内容

Computer code 代码清单 9.4:log.txt

Tue Oct 8 10:50:44 CEUTC 2024

Warning 如果显示 FileNotFoundExceptionIOException,则该文件可能没有创建或没有放在正确的文件夹中。

还有 Writer,它是 OutputStream 的字符对应类,也是 Reader 的目标对应类,它也是一个抽象超类。特定的实现与 Reader 的实现相平行,例如 FileWriterStringWriterOutputStreamWriter,用于将常规 OutputStream 转换为读取器,以便它可以接受字符数据。

System.outSystem.err

[edit | edit source]

Systemjava.lang 包中的一个类,它有一些可供 Java 程序使用的静态成员。两个对控制台输出有用的成员是 System.outSystem.err。System.out 和 System.err 都是 PrintStream 对象。PrintStreamFilterOutputStream 的子类,FilterOutputStream 本身是 OutputStream(上面讨论过)的子类,它的主要目的是将各种数据类型转换为字节流,这些字节流根据某种编码方案用字符表示该数据。

System.outSystem.err 都将文本显示到控制台中,用户可以在其中读取它,但是这的确切含义取决于使用的平台和运行程序的环境。例如,在 BlueJay 和 Eclipse IDE 中,有一个特殊的“终端”窗口将显示此输出。如果程序在 Windows 中启动,则输出将发送到 DOS 提示符(通常这意味着你必须从命令行启动程序才能看到输出)。

System.outSystem.err 的区别在于它们应该用于什么。System.out 应该用于正常的程序输出,System.err 应该用于通知用户程序中发生了某种错误。在某些情况下,这可能很重要。例如,在 DOS 中,用户可以将标准输出重定向到其他目的地(例如文件),但错误输出不会被重定向,而是显示在屏幕上。如果没有这种情况,用户可能永远无法知道发生了错误。


Clipboard

待办事项
说明 print() 方法是如何工作的,强调该方法不会拆分行,但可以通过 \n 转义序列来实现。谈谈它如何处理不同的数据类型,然后介绍 println() 方法作为一种方便的方法,它会自动添加 \n 字符。


新的 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).


Clipboard

待办事项
添加一些类似于 变量 中的练习


华夏公益教科书