NIO新功能Top 10(上)

NIO是Java New IO的简称,JDK 1.4 里提供的API。虽然如今已不能称作“新功能”,但是作为基础了解一下也十分有益。文章分上下两篇介绍,上篇对NIO出现的背景、文件锁、正则表达式和缓冲区视图进行了介绍,下篇将会介绍字节擦拭、直接缓冲区、内存映射文件等内容。

NIO出现的背景

新I/O?为什么我们需要新I/O?老的I/O有什么问题吗? (关于NIO和java.io的详细比较,可以参见对比Java.nio 和 Java.io一文)

java.io包提供的类没有任何问题,它们在职责范围内表现得非常好。然而有许多事情传统的Java I/O不能处理,比如非阻塞模式、文件锁、读选择、分散聚集等等。今天,大多数正规的操作系统都提供了这些功能(一些非主流操作系统也支持)。它们不再是可有可无的功能,而是建立高速、可扩展、健壮的应用不可或缺的,在企业级应用领域尤其如此。

NIO为Java平台引入了一组强大的新功能。尽管”N”代表新的(”New”),但是NIO并不是原来I/O类的替代者。它减少了对流式模型(Streaming Model)的关注,为I/O服务建模提供了另一种选择。NIO专注于提供一致、可移植的API,在访问各种I/O服务时尽可能地减小开销提升效率。NIO扫除了许多障碍,使得Java在I/O性能要求很高的场合也能够跟本地编译语言平等竞争。

碍于篇幅,在这篇文章我不会解释缓冲区、通道、选择器(selector)以及其他NIO的概念。我的书《Java NIO》已经阐述了这些。在这里,我想列出一些之前Java不能做但是NIO可以做的事情。如果你需要一点背景知识,请访这个页面(JDK文档的一部分内容)这里列出了一些J2SE1.4 Javadoc简要的大纲和链接。

10:文件锁

文件锁是一个大多数程序员不会经常使用的功能。然而,对那些真正需要用到它的人却是不可或缺的。NIO出现以前,如果要在Java应用中设置或检查文件锁除了调用本地函数(native method)别无它法。文件锁因其与操作系统(甚至是文件系统)绑定而臭名昭著,任何相关代码的移植都充斥着危险。

NIO文件锁基于FileChannel类构建。现在,只要在操作系统层支持文件锁任何平台都可以很轻易地创建、测试和管理文件锁。通常在集成非java应用程序时,文件锁充当访问共享数据文件的媒介。图1和图2(摘自我的书中)假定写进程(writer process)是一个不可替换的遗留软件。通过NIO,新编写的Java读取软件(reader application)能够采用相同的锁定规范与先前存在的非Java软件无缝集成。

读进程持有共享锁

图1:读进程持有共享锁

写进程持有排它锁

图2:写进程持有排它锁

文件锁通常在文件和进程级别操作,不适合用作JVM内线程之间的协调。操作系统一般不会区分同一个进程中不同线程的持锁权。这意味着同一个JVM中所有线程拥有同样的锁。文件锁主要是用来集成非Java应用或者不同的JVM。

虽然你可能从来不需要使用文件锁,现在NIO可以成为你的一个选择。在Java中添加基于文件的锁进一步消除了在企业级应用中使用Java的障碍,在需要与其他软件一起协作时作用更加明显。

9:建立在String类之上的正则表达式

正则表达式(java.util.regex)是NIO的一部分。我知道,它们既不“新”也不是“I/O”,但标准正则表达式库是JSR51的一部分,因此让我们继续吧。

正则表达式在Java中并不新(好几个附加包已经推出了很长时间了),但是现在它们基于J2SE的版本。按照Jeffrey E. F. Friedl最近的更新《学习正则表达式》一书中的阐述,J2SE1.4中的正则表达式引擎是最快和最好的——知道一下再好不过。

将正则表达式引擎集成到JDK中的一个比较好的“副作用”是JDK中其他的基础类可以使用它。在J2SE1.4中,String类扩展了如下与正则表达式相关的新方法:

package java.lang;
public final class String
implements java.io.Serializable, Comparable, CharSequence
{
  // This is a partial API listing
  public boolean matches (String regex)
  public String [] split (String regex)
  public String [] split (String regex, int limit)
  public String replaceFirst (String regex, String replacement)
  public String replaceAll (String regex, String replacement)
}

这些方法非常有用,因为你能在当前使用的字符串上直接调用它们。相比实例化Pattern和Matcher对象,在字符串对象上直接调用并检查结果,你能够很容易地像这样测试。通常,这种情况出错可能性更小而且可读性更好。

public static final String VALID_EMAIL_PATTERN =
"([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]"
+ "{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))"
+ "([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)";
...
if (emailAddress.matches (VALID_EMAIL_PATTERN)) {
  addEmailAddress (emailAddress);
} else {
  throw new IllegalArgumentException (emailAddress);
}

相比StringTokenizer, split()方法也更容易上手,它有两个优点:它对目标字符串应用了一个正则表达式(可能有些复杂),一次调用就得到了所有结果字符串而不是像StringTokenizer一样写一堆取得token的循环,你可以这样做:

String [] tokens = lineBuffer.split ("\\s*,\\s*");

这个方法将lineBuffer(包含了用逗号分割的值)分割成子串并且以类型安全的数组返回它们。这个正则表达式允许逗号前后有0个或多个白色字符。你也可以限制String分割的次数,在这种情况下,返回的最后一个子串就是还没有分割完剩下来的输入串。

8:缓冲区视图

Buffer家族类图

图3:Buffer家族类图

NIO引入了缓冲区,这是一组在java.nio包中相关的类(见图3)。缓冲区一眼看去就像计算机科学101课上定义的那样,它们是一组封装了固定大小原生类型数组及其相应状态信息的简单对象。基本上就是这样。

缓冲区主要用来作为从通道发送或者接收数据的容器。通道是低级I/O服务的管道,他们是面向字节的;所以他们只能操作ByteBuffer对象。

那么我们用其他缓冲区类型干什么呢?(注:指的是CharBuffer,DoubleBuffer,IntBuffer等)可以从头创建或者包装一个类型合适的数组来生成非字节缓冲区实例,这些方式非常有用,但是这样的缓冲区不能用于I/O。(注,指的是视图缓冲区不能直接与channel互相访问)当然,还有第三种方式来创建基于ByteBuffer的非字节缓冲区视图。

例如,假设你有一个存储16比特Unicode(这里指的是UTF-16编码,不是普通文件中使用的UTF-8编码)字符的文件。如果你读了一块文件到字节缓冲区内,你可以像这样创建它们的字符缓冲区视图。

CharBuffer charBuffer = byteBuffer.asCharBuffer();

上面这段代码创建了一个带有CharBuffer行为的ByteBuffer视图。如图4所示,缓冲区中的每对字节组成一个16bit char字符(图中奇数字节的数组并没有包括在视图中,这里我们假设你从偶字节缓冲区开始)。

一个ByteBuffer的CharBuffer视图

图4:一个ByteBuffer的CharBuffer视图

接着你可以用CharBuffer对象在数据上迭代(用相对get()方法),用绝对get()方法随机访问,或者将数据拷贝到char数组并把它传递给一个与缓冲无关的对象。

ByteBuffer类也有特殊方法可以访问独立原始值。例如访问缓冲区中4字节作为int型,你可以这么做:

int fileSize = byteBuffer.getInt();

这样就从缓冲区提取出4字节并将它们变成32bit的int值,更酷的是这4字节不需要与特殊地址边界对齐。如果下层的硬件不允许不对齐的内存访问,ByteBuffer实现就会自动按照要求组装字节(或者调用put()方法拆开)。

原文链接: onjava 翻译: ImportNew.com - 李维
译文链接: http://www.importnew.com/4760.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: 李维

——我是程序员。 ——哦,程先生! ——客气了,叫我序员就好。(新浪微博:@idiot_fox

查看李维的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部