`

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

    博客分类:
  • Java
阅读更多

 

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

Sky:

 

◆  关于缓冲区的更多内容

概  述

到目前为止,您已经学习了使用缓冲区进行日常工作所需要掌握的大部分内容。

我们的例子没怎么超出标准的读/写过程种类,

在原来的 I/O 中可以像在 NIO 中一样容易地实现这样的标准读写过程。

本节将讨论使用缓冲区的一些更复杂的方面,比如缓冲区分配、

包装和分片。我们还会讨论 NIO 带给 Java 平台的一些新功能。

您将学到如何创建不同类型的缓冲区以达到不同的目的,

如可保护数据不被修改的 只读 缓冲区,和直接映射到底层操作系统缓冲区的

直接 缓冲区。我们将在本节的最后介绍如何在 NIO 中创建内存映射文件。

缓冲区分配和包装

在能够读和写之前,必须有一个缓冲区。要创建缓冲区,

您必须 分配 它。我们使用静态方法 allocate() 来分配缓冲区:

  1. ByteBuffer buffer = ByteBuffer.allocate( 1024 ); 

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

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

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

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

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

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

缓冲区分片

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

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

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

  1. ByteBuffer buffer = ByteBuffer.allocate( 10 ) 

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

  1. for (int i=0; i<buffer.capacity(); ++i) {  
  2.      buffer.put( (byte)i );  

现在我们对这个缓冲区 分片 ,以创建一个包含槽 3 到槽 6 的子缓冲区。

在某种意义上,子缓冲区就像原来的缓冲区中的一个 窗口 。

窗口的起始和结束位置通过设置 position 和 limit 值来指定,然后调用 Buffer 的 slice() 方法:

  1. buffer.position( 3 );  
  2. buffer.limit( 7 );  
  3. ByteBuffer slice = buffer.slice(); 

片 是缓冲区的 子缓冲区 。不过, 片段 和 缓冲区 共享同一个底层数据数组,

我们在下一节将会看到这一点。

缓冲区份片和数据共享

我们已经创建了原缓冲区的子缓冲区,并且我们知道缓冲区和子缓冲区共享同一个底层数据数组。

让我们看看这意味着什么。

 

我们遍历子缓冲区,将每一个元素乘以 11 来改变它。例如,5 会变成 55。

  1. for (int i=0; i<slice.capacity(); ++i) {  
  2.      byte b = slice.get( i );  
  3.      b *= 11;  
  4.      slice.put( i, b );  

最后,再看一下原缓冲区中的内容:

  1. buffer.position( 0 );  
  2. buffer.limit( buffer.capacity() );  
  3.  while (buffer.remaining()>0) {  
  4.      System.out.println( buffer.get() );  

结果表明只有在子缓冲区窗口中的元素被改变了:

$ java SliceBuffer 
0 
1 
2 
33 
44 
55 
66 
7 
8 
9

缓冲区片对于促进抽象非常有帮助。可以编写自己的函数处理整个缓冲区

,而且如果想要将这个过程应用于子缓冲区上,您只需取主缓冲区的一个片,

并将它传递给您的函数。这比编写自己的函数来取额外的参数以指定要对缓冲区的哪一部分进行操作更容易。

只读缓冲区

只读缓冲区非常简单 ― 您可以读取它们,但是不能向它们写入。

可以通过调用缓冲区的 asReadOnlyBuffer() 方法,

将任何常规缓冲区转换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区(并与其共享数据),

只不过它是只读的。

只读缓冲区对于保护数据很有用。在将缓冲区传递给某个对象的方法时,

您无法知道这个方法是否会修改缓冲区中的数据。创建一个只读的缓冲区可以 保证 该缓冲区不会被修改。

不能将只读的缓冲区转换为可写的缓冲区。

直接和间接缓冲区

另一种有用的 ByteBuffer 是直接缓冲区。 直接缓冲区 是为加快 I/O 速度,

而以一种特殊的方式分配其内存的缓冲区。

实际上,直接缓冲区的准确定义是与实现相关的。Sun 的文档是这样描述直接缓冲区的:

给定一个直接字节缓冲区,Java 虚拟机将尽最大努力直接对它执行本机 I/O 操作。

也就是说,它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后),

尝试避免将缓冲区的内容拷贝到一个中间缓冲区中(或者从一个中间缓冲区中拷贝数据)。

您可以在例子程序 FastCopyFile.java 中看到直接缓冲区的实际应用,

这个程序是 CopyFile.java 的另一个版本,它使用了直接缓冲区以提高速度。

还可以用内存映射文件创建直接缓冲区。

内存映射文件 I/O

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。

内存映射文件 I/O 是通过使文件中的数据神奇般地出现为内存数组的内容来完成的

。这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。

一般来说,只有文件中实际读取或者写入的部分才会送入(或者 映射 )到内存中。

内存映射并不真的神奇或者多么不寻常。

现代操作系统一般根据需要将文件的部分映射为内存的部分,从而实现文件系统。

Java 内存映射机制不过是在底层操作系统中可以采用这种机制时,提供了对该机制的访问。

尽管创建内存映射文件相当简单,但是向它写入可能是危险的。

仅只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。

修改数据与将数据保存到磁盘是没有分开的。

将文件映射到内存

了解内存映射的最好方法是使用例子。在下面的例子中

,我们要将一个 FileChannel (它的全部或者部分)映射到内存中。

为此我们将使用 FileChannel.map() 方法。下面代码行将文件的前 1024 个字节映射到内存中:

  1. MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE,     01024 ); 

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. RandomAccessFile raf = new RandomAccessFile( "usefilelocks.txt""rw" );  
  2. FileChannel fc = raf.getChannel();  
  3. FileLock lock = fc.lock( start, end, false ); 

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

  1. lock.release(); 

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

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

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

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

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

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

• 只使用排它锁。

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

分享到:
评论

相关推荐

    Java NIO的介绍及工作原理

    Java NIO的介绍及工作原理Java NIO的介绍及工作原理

    Java NIO原理 图文分析及代码实现

    Java NIO原理 图文分析及代码实现

    java NIO技巧及原理

    java NIO技巧及原理解析,java IO原理,NIO框架分析,性能比较

    java NIO和java并发编程的书籍

    java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java...

    JavaNIO chm帮助文档

    Java NIO系列教程(一) Java NIO 概述 Java NIO系列教程(二) Channel Java NIO系列教程(三) Buffer Java NIO系列教程(四) Scatter/Gather Java NIO系列教程(五) 通道之间的数据传输 Java NIO系列教程(六)...

    java NIO原理和使用

    java nio 附带例子 以及原理 java nio 附带例子 以及原理 java nio 附带例子 以及原理 java nio 附带例子 以及原理

    Java NIO英文高清原版

    Java NIO英文高清原版

    java nio 包读取超大数据文件

    Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据...

    Java NIO 中文 Java NIO 中文 Java NIO 中文文档

    Java NIO 深入探讨了 1.4 版的 I/O 新特性,并告诉您如何使用这些特性来极大地提升您所写的 Java 代码的执行效率。这本小册子就程序员所面临的有代表性的 I/O 问题作了详尽阐述,并讲解了 如何才能充分利用新的 I/O ...

    java NIO 视频教程

    Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。 Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,...

    java NIO 中文版

    讲解了 JavaIO 与 JAVA NIO区别,JAVA NIO设计理念,以及JDK中java NIO中语法的使用

    java nio 实现socket

    java nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socket

    Java NIO工作原理的全面分析

    JDK 1.4 中引入的新输入输出 (NIO) 库在标准 Java 代码中提供了高速的、面向块的 I/O。本实用教程从高级概念到底层的编程细节,非常详细地介绍了 NIO 库。您将学到诸如缓冲区和通道这样的关键 I/O 元素的知识,并...

    JavaNIO的原理.pdf

    JavaNIO的原理.pdf

    Java NIO原理解析

    Java NIO原理解析jdk供的无阻塞I/O(NIO)有效解决了多线程服务器存在的线程开销问题,但在使用上略显得复杂一些。在NIO中使用多线程,主要目的已不是为了应对每个客户端请求而分配独立的服务线程,而是通过多线程...

    java nio中文版

    java NIO是 java New IO 的简称,在 jdk1.4 里提供的新 api 。 Sun 官方标榜的特性如下: – 为所有的原始类型提供 (Buffer) 缓存支持。 – 字符集编码解码解决方案。 – Channel :一个新的原始 I/O 抽象。 – 支持...

    Java Nio selector例程

    java侧起server(NioUdpServer1.java),基于Java Nio的selector 阻塞等候,一个android app(NioUdpClient1文件夹)和一个java程序(UI.java)作为两个client分别向该server发数据,server收到后分别打印收到的消息...

    java NIO.zip

    java NIO.zip

    java nio 读文件

    java nio 读文件,java nio 读文件

Global site tag (gtag.js) - Google Analytics