【翻译】Java I/O vs Java NIO

这是一篇2009年的博客但是现在来看依然挺不错,所以翻译了一下分享出来。
原文链接地址:https://blogs.oracle.com/slc/entry/javanio_vs_javaio 译文如下:

首先我要提到的是这篇文章不是java.io和java.nio的操作手册,也不是如何使用java.io和java.nio的技术文档。它的目的仅在于比较这两个包,用最简单的方式来阐述它们区别与特性。java.nio展示了新的流通信,带来了新的buffer、文件流以及新的socket特性。

java.io概述

java.io这个包被用来做系统的输入和输出。流(Stream)支持许多不同类型的数据,包括bytes,原始数据类型,本地字符以及对象。一个流是指一个数据序列。应用程序通过输入流从数据源中读取数据,通过输出流将数据写到或者发送到目的地。我们的程序使用字节流完成字节的输入和输出,而所有的字节流都继承自InputStream和OutputStream。

关于InputStream和OutputStream

InputStream和OutpuStream操作一般来说会有一个循环,这个循环一边从输入流中读入数据一边向输出流一次一个字节写出数据。你可以使用Buffered I/O stream来提高性能(这里的性能提高主要是因为这样的一次调用往往会触发磁盘访问,网络活动或者是一些其他的非常昂贵的操作)。buffered input stream 从内存中读取数据,这样的内存区域被称为Buffer,而本地的输入API(native I/O API)只有在Buffer为空的情况下才被调用,同样的,buffered output stream向缓冲区中写入数据,而本地的输出API只有在Buffer满的情况下被调用。这些buffered API包装了unbuffered streams它们是BufferedInputStream, BufferedOutputStream。

文件I/O

上一段讲了流,流提供了一种简单的读写数据的模型。流可以被用在各种不同的数据源和数据目标上,这也包括磁盘文件。但是流却并不支持所有的磁盘文件操作,下面的链接就给出了非流方式的文件I/O,主要下面两种:
File, File这个类用来帮助检查文件和目录并且是和平台无关的。
Random access files, 随机访问文件支持非连续的磁盘文件访问。

java.net.socket

一个套接字(Socket)是运行在网络上的两个程序之间的双向通信通道的一个端点。Socket用来表示客户端和服务端的连接。java.net这个包提供了两个类,一个是Socket另一个是ServerSocket,它们分别实现了客户单和服务端的连接。

客户端与服务端通信需要知道服务端服务器的名字以及服务端所监听的端口。客户端去连接服务端,如果一切正常,服务端会接受客户端的连接。一旦服务端接受了连接,服务端会新建一个socket并且绑定服务端的端口而这个socket的另一端则连接的是客户端的地址和端口。它需要新的socket来保证它既能够监听原有的端口,又能够满足已连接上的客户端。

服务端等待客户端连接的时候是保持阻塞的:serverSocket.accpet()是一个阻塞的指令,服务端会一直等待客户端连接期间运行服务端的线程不能执行其它操作。正应为如此一个Server如果想要同时只执行多个任务需要实现多线程的server,即为每一个server创建的socket都创建一个线程。

NIO API

I/O的性能一直是现代应用程序最关键的方面。操作系统一直在改进I/O的性能。JVM在各种不同操作系统环境下提供了一个统一的操作环境,正因为这样Java开发变得跟容易更简单,而且操作系统的实现也被隐藏起来。为了提高性能你可能需要编写直接访问操作系统特性的代码,但是这不是最好的解决方式,因为你的代码将会依赖特定的操作系统。java.nio提供了新的装备去解决这个问题。它提供了新的高性能I/O特性,并且支持绝大多数商业操作系统。

JDK1.4的NIO包提供了一组新的抽象模型去完成I/O操作

java.nio 概述

java.nio是一个新的包它为Java平台实现了新的I/O接口。NIO API包括如下几个特性:

  • 原始数据类型的Buffer
  • 字符集编、解码器
  • Perl风格的正则匹配功能
  • 通道(Channels),新的原始I/O的抽象模型
  • 支持锁和内存映射(Memory Mapping)的文件接口
  • 为了编写高性能的server提供了支持多路复用、无阻塞的I/O设施

在sun公司的网站上你可以找到很多关于java.nio的资料,我将从nio的方面阐述java.io和java.nio之间的差异。这里说明一下,java.nio并不是用来代替java.io,而是java.io的扩展。nio的诞生产生了一些新的类和接口(参见http://java.sun.com/j2se/1.4.2/docs/guide/nio/

NIO最重要的一点能够提供非阻塞的模型,这是区别与传统IO的,那什么是非阻塞模型?

非阻塞模型

I/O流的数据必须以顺序的形式访问。各种外设,打印机端口以及网络连接设备都是流。

一般来说流要比阻塞设备要慢,但也不绝对,而流设备一般都是有间断的输入。大部分操作系统都允许流以非阻塞的方式运行,它允许进程检查流输入是否是可用的,如果不可用程序也可以继续执行。这样就使进程在有输入的时候处理输入在没有输入的时候可以处理其他的任务。操作系统也能够监视一组流并且分别出哪些流现在是可用的,这样一个进程就能用同样的代码和一个线程处理多个流,因为流处于可用状态的几率增大了。这个特性被广泛用在网络服务器处理大规模网络连接上。

Buffers

按照从简单到复杂的顺序,第一个值得被提起的改进就是java.nio中的Buffer类.这些buffer提供了一种将原始数据存储在内存中的机制。一个Buffer对象是一个包含固定大小数据的容器,这个容器中的数据可以读写。

所有的Buffer都可以读,但是值得注意的是并不是所有Buffer都可以写。所有的buffer类都实现了isReadOnly()接口来说明他是否允许修改buffer中的内容。

Channels

Buffers是和Channel一起配合使用的。Channel是I/O传输的通道,而buffer是数据传输的源和目标。你想要传输的数据将会放在buffer中,然后又传递给channel,另一方面,Channel将从Buffer中取走你提供的数据。

一个Channel就像是一个管道,他能够有效的将数据从buffer传递到通道另一端的实体。Channel就像是一个关口,只需要很少的代价就可以访问操作系统的本地I/O,而buffer就像是通道内部的两个端点用来接收和发送数据。

Channel可以以阻塞和非阻塞两种模式运行。非阻塞的Channel不会将线程至于睡眠状态。它要么很快的执行操作要么返回一个结果表示没什么可做的了。只有流式(stream-oriented)的channel才可以以非阻塞的方式运行。在java.nio的通道家族中有FileChannel,ServerSocketChannel以及SocketChannel这三种通道。有不同的通道来分别处理文件和Socket的管理。

FileChannel

FileChannel是可读写的通道,它们总是阻塞的并且不能以非阻塞的方式运行。流式I/O的非阻塞原理对文件操作来说并没有太大的意义,因为文件I/O是一种完全不一样的I/O形式。(译者自言自语:这个地方并不太理解,感觉不是那么显然,而且Java7中引入了AsynchronousFileChannel)
FileChannel对象不能直接被创建,一个FileChannel实例可以通过调用文件对象(RandomAccessFile,FileInputStream,FileOutputStream)的getChannel()方法获取。getChannel()方法返回的FileChannel对象,这个对象与这个文件相连并且包含与文件对象同样的访问权限。FileChannel是线程安全的,多个线程可以同时调用同一个对象上的方法,但不是所有的操作都是多线程的,影响Channel位置和文件大小的操作都是单线程的。

SocketChannel

SocketChannel和FileChannel不一样:socketChannle可以以非阻塞的方式运行,并且他是可选择的(selectable)。它不再需要为每一个socket连接指定一个线程,使用新的NIO类,少数的几个线程就可管理成百上千的活动socket连接,而且几乎没有性能损失。使用Selector可以使得socket channel尽量处于可用状态。
Socket Channel有三种类型:SocketChannel,ServerSocketChannel以及DatagramChannel。SocketChannel和DatagramChannel是可读写的,ServerSocketChannel监听连接并且创建新的SocketChannel对象,所有的socket channel在初始化的时候都会创建一个socket对象。通过socket()方法可以获取到这个socket对象。所有的socket channel都会有一个对应的socket对象但不是所有的socket都会有一个socket channel。如果你用传统的方式(直接实例化)创建了一个socket对象,调用他的getSocketChannel方法只会返回null。

Socket Channel可以以非阻塞方式运行。传统的socket的阻塞特质对于Java应用的可扩展性来说是一个最大的障碍。非阻塞I/O是相对复杂和高性能应用的基础,你可以通过调用configureBlocking()方法设置channel的阻塞模式。

非阻塞的socket通常被用在服务端,因为他使的同时管理多个socket变得简单。

Selector

Selectors提供了能够让channel有条件的选择(readiness selection),这就使得多路复用I/O变得可能。为了方便理解selector的特性,我将利用下面的例子来帮助解释。

假设你在一个火车站(没有selector),这里有3个站台(channels),每一个站台都有一辆火车(buffer)到达。每一个站台都有一个控制台来接收火车(worker thread)。这就是没有选择器的。现在看看有选择器的将有什么不同,有3个站台(channel),每一个站台都会有火车到达(buffer),每一个站台有一个提示器指示“火车已到达”(selection key),这种情况下我只需要为3个站台设置一个控制台。它来检查指示器(selector.select())来找出是否有火车已到达并且去接站。

通过上面的例子我们就非常容易理解选择器的优势:只需要一个线程就可以完成多任务的处理。除了这个你还可以获取更多的好处,想象火车控制台检查指示器:他可以等着火车到达不做任何事情(阻塞模式selector.select()),但他也可以在等待火车到达时去售票(selector.selectNow)。

IO/NIO

NIO让I/O相对于传统I/O来说更快。在一个程序中I/O操作占据了大部分的处理时间。比如说一个程序利用socket来拷贝文件,这时候使用nio更可能获取更好的性能,应为NIO比I/O API更接近操作系统。数据越大性能差距越是明显,NIO也有一些传统IO API没有的特性来操作流。但是这并不意味着NIO会替换掉IO。因为NIO只是增加了功能,它扩展了本地I/O API,引入了新的东西让程序员操作流数据变得更强大。

Meta

Published: Oct. 12, 2012 Author: ivan Comments:   Word Count: 35
Bookmark and Share

Next: A Guide for Git starter

Previous: 利用Dropbox备份Blog数据

Tags

I/O java NIO translate

Article Links

  1. Java.nio vs Java.io (SpiderLogic)
  2. Error Page 404
Comments powered by Disqus