首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

全面解读Java NIO工作原理(三)

2012-08-28 
全面解读Java NIO工作原理(3)?JDK 1.4 中引入的新输入输出 (NIO) 库在标准 Java 代码中提供了高速的、面向

全面解读Java NIO工作原理(3)

?

JDK 1.4 中引入的新输入输出 (NIO) 库在标准 Java 代码中提供了高速的、面向块的 I/O。本实用教程从高级概念到底层的编程细节,非常详细地介绍了 NIO 库。您将学到诸如缓冲区和通道这样的关键 I/O 元素的知识,并考察更新后的库中的标准 I/O 是如何工作的。您还将了解只能通过 NIO 来完成的工作,如异步 I/O 和直接缓冲区。

Sky:

  • 1024?);?

    allocate() 方法分配一个具有指定大小的底层数组,

    并将它包装到一个缓冲区对象中 ― 在本例中是一个 ByteBuffer。

    您还可以将一个现有的数组转换为缓冲区,如下所示:

    1. byte?array[]?=?new?byte[1024]; ?ByteBuffer?buffer?=?ByteBuffer.wrap(?array?);?

    本例使用了 wrap() 方法将一个数组包装为缓冲区。必须非常小心地进行这类操作。

    一旦完成包装,底层数据就可以通过缓冲区或者直接访问。

    缓冲区分片

    slice() 方法根据现有的缓冲区创建一种 子缓冲区 。也就是说,

    它创建一个新的缓冲区,新缓冲区与原来的缓冲区的一部分共享数据。

    使用例子可以最好地说明这点。让我们首先创建一个长度为 10 的 ByteBuffer:

    1. 10?)?

    然后使用数据来填充这个缓冲区,在第 n 个槽中放入数字 n:

    1. for?(int?i=0;?i<buffer.capacity();?++i)?{ ??????buffer.put(?(byte)i?); ?
    2. 3?); ?buffer.limit(?7?); ?
    3. for?(int?i=0;?i<slice.capacity();?++i)?{ ??????byte?b?=?slice.get(?i?); ?
    4. 11; ??????slice.put(?i,?b?); ?
    5. 0?); ?buffer.limit(?buffer.capacity()?); ?
    6. while?(buffer.remaining()>0)?{ ??????System.out.println(?buffer.get()?); ?
    7. 0,?1024?);?

    map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。

    因此,您可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行行映射。

    ◆? 分散和聚集

    概? 述

    分散/聚集 I/O 是使用多个而不是单个缓冲区来保存数据的读写方法。

    一个分散的读取就像一个常规通道读取,只不过它是将数据读到一个缓冲区数组中而

    不是读到单个缓冲区中。同样地,一个聚集写入是向缓冲区数组而不是向单个缓冲区写入数据。

    分散/聚集 I/O 对于将数据流划分为单独的部分很有用,这有助于实现复杂的数据格式。

    分散/聚集 I/O

    通道可以有选择地实现两个新的接口: ScatteringByteChannel 和 GatheringByteChannel。一个 ScatteringByteChannel 是一个具有两个附加读方法的通道:

    ? long read( ByteBuffer[] dsts );

    ? long read( ByteBuffer[] dsts, int offset, int length );

    这些 long read() 方法很像标准的 read 方法,只不过它们不是取单个缓冲区而是取一个缓冲区数组。

    在 分散读取 中,通道依次填充每个缓冲区。填满一个缓冲区后,它就开始填充下一个。

    在某种意义上,缓冲区数组就像一个大缓冲区。

    分散/聚集的应用

    分散/聚集 I/O 对于将数据划分为几个部分很有用。例如,您可能在编写一个使用消息对象的网络应用程序,

    每一个消息被划分为固定长度的头部和固定长度的正文。您可以创建一个刚好可以容纳头部的缓冲区

    和另一个刚好可以容难正文的缓冲区。当您将它们放入一个数组中并使用分散读取来向它们读入消息时,

    头部和正文将整齐地划分到这两个缓冲区中。

    我们从缓冲区所得到的方便性对于缓冲区数组同样有效。因为每一个缓冲区都跟踪自己还可以接受多少数据,

    所以分散读取会自动找到有空间接受数据的第一个缓冲区。在这个缓冲区填满后,它就会移动到下一个缓冲区。

    聚集写入

    聚集写入 类似于分散读取,只不过是用来写入。它也有接受缓冲区数组的方法:

    ? long write( ByteBuffer[] srcs );

    ? long write( ByteBuffer[] srcs, int offset, int length );

    聚集写对于把一组单独的缓冲区中组成单个数据流很有用。为了与上面的消息例子保持一致,

    您可以使用聚集写入来自动将网络消息的各个部分组装为单个数据流,以便跨越网络传输消息。

    从例子程序 UseScatterGather.java 中可以看到分散读取和聚集写入的实际应用。

    ◆? 文件锁定

    概? 述

    文件锁定初看起来可能让人迷惑。它 似乎 指的是防止程序或者用户访问特定文件。

    事实上,文件锁就像常规的 Java 对象锁 ― 它们是 劝告式的(advisory) 锁。

    它们不阻止任何形式的数据访问,相反,它们通过锁的共享和获取赖允许系统的不同部分相互协调。

    您可以锁定整个文件或者文件的一部分。如果您获取一个排它锁,那么其他人就不

    能获得同一个文件或者文件的一部分上的锁。如果您获得一个共享锁,那么其他人可

    以获得同一个文件或者文件一部分上的共享锁,但是不能获得排它锁。

    文件锁定并不总是出于保护数据的目的。例如,您可能临时锁定一个文件以保证特定的写操作成为原子的,而不会有其他程序的干扰。

    大多数操作系统提供了文件系统锁,但是它们并不都是采用同样的方式。有

    些实现提供了共享锁,而另一些仅提供了排它锁。事实上,

    有些实现使得文件的锁定部分不可访问,尽管大多数实现不是这样的。

    在本节中,您将学习如何在 NIO 中执行简单的文件锁过程,

    我们还将探讨一些保证被锁定的文件尽可能可移植的方法。

    锁定文件

    要获取文件的一部分上的锁,您要调用一个打开的 FileChannel 上的 lock() 方法。

    注意,如果要获取一个排它锁,您必须以写方式打开文件。

    1. new?RandomAccessFile(?"usefilelocks.txt",?"rw"?); ?FileChannel?fc?=?raf.getChannel(); ?
    2. false?);?

    在拥有锁之后,您可以执行需要的任何敏感操作,然后再释放锁:

    1. lock.release();?

    在释放锁后,尝试获得锁的其他任何程序都有机会获得它。

    本小节的例子程序 UseFileLocks.java 必须与它自己并行运行。

    这个程序获取一个文件上的锁,持有三秒钟,然后释放它。如果同时运行这个程序的多个实例

    ,您会看到每个实例依次获得锁。

    文件锁定可能是一个复杂的操作,特别是考虑到不同的操作系统是以不同的方式实现锁这一事实。

    下面的指导原则将帮助您尽可能保持代码的可移植性:

    ? 只使用排它锁。

    ? 将所有的锁视为劝告式的(advisory)。

  • 热点排行