# Java 标准 IO 与 Java NIO

> 原文： [https://howtodoinjava.com/java/io/difference-between-standard-io-and-nio/](https://howtodoinjava.com/java/io/difference-between-standard-io-and-nio/)

JDK 1.4 引入了[**新输入/输出（NIO）**](//howtodoinjava.com/category/java-7-features/nio/ "NIO")库。 从原始 I/O 遗留的地方接起，NIO 用标准 Java 代码提供了高速，面向块的 I/O。 通过定义用于保存数据的类，并通过按块处理该数据，NIO 可以利用低级优化的优势，而无需使用本机代码，原始 I/O 包就无法实现。

在本教程中，我将专注于确定最明显的区别，在决定在下一个项目中使用哪个区别之前，您必须知道这些区别。

## 回顾旧的 IO 机制

[**I/O（输入/输出）**](//howtodoinjava.com "java io") 是指计算机与世界其他地方之间的接口，或单个程序与计算机其余部分之间的接口。 单个程序通常会为他们完成大部分工作。 在 Java 编程中，直到最近才使用流隐喻来执行 I/O。 所有 I/O 都被视为单个字节通过一个称为流的对象一次移动。 流 I/O 用于联系外界。 它还在内部使用，用于将对象转换为字节，然后再转换为对象。

## NIO 介绍 

[**NIO**](//howtodoinjava.com/java-nio-tutorials/ "NIO Tutorials") 的创建是为了使 Java 程序员无需编写自定义本机代码即可实现高速 I/O。 NIO 将最耗时的 I/O 活动（即填充和清空缓冲区）移回到操作系统中，从而大大提高了速度。

如果以上介绍让您感到口渴，请不要担心，在我们前进的过程中您是否会感到更好。 让我们从发现差异开始。

## 识别 IO 和 NIO 之间的差异

##### 1）IO 流与 NIO 块

原始 I/O 库（可在`java.io.*`中找到）和 NIO 之间最重要的区别与数据的打包和传输方式有关。 如前所述，原始 I/O 处理流中的数据，而 NIO 处理块中的数据。

面向流的 I/O 系统一次处理一个或多个字节的数据。 输入流产生一个字节的数据，而输出流消耗一个字节的数据。 为流数据创建过滤器非常容易。 将几个过滤器链接在一起也是相对简单的，这样每个过滤器都能发挥自己的作用，相当于一个单一的复杂处理机制。 重要的是字节不会在任何地方缓存。 此外，您不能在流中的数据中来回移动。 如果需要来回移动从流中读取的数据，则必须先将其缓存在缓冲区中。

面向块的 I/O 系统按块处理数据。 每个操作一步就产生或消耗一个数据块。 通过块处理数据可能比通过（流式传输）字节处理数据快得多。 您可以根据需要在缓冲区中来回移动。 这使您在处理过程中更具灵活性。 但是，您还需要检查缓冲区是否包含您需要的所有数据，以便对其进行完全处理。 并且，您需要确保在将更多数据读入缓冲区时，不要覆盖尚未处理的缓冲区中的数据。 但是面向块的 I/O 缺少面向流的 I/O 的一些优雅和简单性。

**阅读更多：** [**3 种使用 Java NIO 读取文件的方法**](//howtodoinjava.com/java-7/nio/3-ways-to-read-files-using-java-nio/ "3 ways to read files using Java NIO")

##### 2）同步与异步 IO

Java IO 的各种流正在阻塞或同步。 这意味着，当线程调用`read()`或`write()`时，该线程将被阻塞，直到有一些数据要读取或数据被完全写入为止。 在此期间，线程将处于阻塞状态。 这被认为是在现代语言中引入多线程的一个很好的坚实理由。

在异步 IO 中，线程可以请求将某些数据写入通道，但不等待将其完全写入。 然后线程可以继续运行，同时执行其他操作。 通常，这些线程将空闲时间花费在未阻塞 IO 调用上的时间，通常同时在其他通道上执行 IO。 也就是说，单个线程现在可以管理输入和输出的多个通道。

同步程序通常不得不诉诸于轮询或创建许多线程来处理大量连接。 使用异步 I/O，您可以在任意数量的通道上侦听 I/O 事件，而无需轮询且无需额外的线程。

异步 I/O 中的中心对象称为选择器。 选择器是您对各种 I/O 事件感兴趣的地方，它是告诉您何时发生这些事件的对象。 因此，我们需要做的第一件事是创建一个选择器：

```java
Selector selector = Selector.open();

```

稍后，我们将在各种通道对象上调用` register()`方法，以使我们对那些对象内发生的 I/O 事件感兴趣。 `register()`的第一个参数始终是选择器。

**阅读更多：[如何在 Java NIO 中定义路径](//howtodoinjava.com/java-7/nio/how-to-define-path-in-java-nio/ "How to define Path in java NIO")**

##### 3）IO 与 NIO API

猜测使用 NIO 时 API 调用与使用 IO 时看起来不同的猜测是没有根据的。 在 NIO 中，而不是从例如如果是`InputStream`，则必须首先将数据读入缓冲区，然后再对其进行处理。

使用标准 IO 的示例代码

```java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class WithoutNIOExample
{
    public static void main(String[] args)
    {
        BufferedReader br = null;
        String sCurrentLine = null;
        try
        {
            br = new BufferedReader(
            new FileReader("test.txt"));
            while ((sCurrentLine = br.readLine()) != null)
            {
                System.out.println(sCurrentLine);
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            try
            {
                if (br != null)
                br.close();
            } catch (IOException ex)
            {
                ex.printStackTrace();
            }
        }
    }
}

```

使用 NIO 的示例代码

```java
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class ReadFileWithFixedSizeBuffer
{
    public static void main(String[] args) throws IOException
    {
        RandomAccessFile aFile = new RandomAccessFile
                ("test.txt", "r");
        FileChannel inChannel = aFile.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(inChannel.read(buffer) > 0)
        {
            buffer.flip();
            for (int i = 0; i < buffer.limit(); i++)
            {
                System.out.print((char) buffer.get());
            }
            buffer.clear(); // do something with the data and clear/compact it.
        }
        inChannel.close();
        aFile.close();
    }
}

```

## 总结

NIO 允许您仅使用一个（或更少）线程来管理多个通道，但是代价是解析数据可能比使用标准 IO 从阻塞流中读取数据时要复杂得多。

如果您需要同时管理数千个打开的连接（每个连接仅发送少量数据），例如聊天服务器，则在 NIO 中实现该服务器可能是一个优势。 同样，如果您需要保持与其他计算机的大量开放连接，例如在 P2P 网络中，使用单个线程来管理所有出站连接可能是一个优势。

如果您只有很少的连接且带宽很高，那么一次发送大量数据，则应该选择标准 IO 服务器实现。

**祝您学习愉快！**