NIO入门系列之第5章:关于缓冲区的更多内容

简介:

第5章 关于缓冲区的更多内容

5.1  概述

到目前为止,您已经学习了使用缓冲区进行日常工作所需要掌握的大部分内容。我们的例子没怎么超出标准的读/写过程种类,在原来的 I/O中可以像在 NIO 中一样容易地实现这样的标准读写过程。

本节将讨论使用缓冲区的一些更复杂的方面,比如缓冲区分配、包装和分片。我们还会讨论 NIO 带给 Java 平台的一些新功能。您将学到如何创建不同类型的缓冲区以达到不同的目的,如可保护数据不被修改的只读缓冲区,和直接映射到底层操作系统缓冲区的直接缓冲区。我们将在本节的最后介绍如何在 NIO 中创建内存映射文件。


5.2  缓冲区分配和包装

在能够读和写之前,必须有一个缓冲区。要创建缓冲区,您必须分配它。我们使用静态方法 allocate() 来分配缓冲区:

1
ByteBuffer buffer = ByteBuffer.allocate(  1024  );

allocate() 方法分配一个具有指定大小的底层数组,并将它包装到一个缓冲区对象中在本例中是一个 ByteBuffer

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

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

本例使用了 wrap() 方法将一个数组包装为缓冲区。必须非常小心地进行这类操作。一旦完成包装,底层数据就可以通过缓冲区或者直接访问。


5.3  缓冲区分片

slice() 方法根据现有的缓冲区创建一种子缓冲区。也就是说,它创建一个新的缓冲区,新缓冲区与原来的缓冲区的一部分共享数据。

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

1
ByteBuffer buffer = ByteBuffer.allocate(  10  );

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

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

现在我们对这个缓冲区分片,以创建一个包含槽 3 到槽 6 的子缓冲区。在某种意义上,子缓冲区就像原来的缓冲区中的一个窗口

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

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

片是缓冲区的子缓冲区。不过,片段缓冲区共享同一个底层数据数组,我们在下一节将会看到这一点。

5.4  缓冲区份片和数据共享

我们已经创建了原缓冲区的子缓冲区,并且我们知道缓冲区和子缓冲区共享同一个底层数据数组。让我们看看这意味着什么。

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
$ java SliceBuffer
0
1
2
33
44
55
66
7
8
9

缓冲区片对于促进抽象非常有帮助。可以编写自己的函数处理整个缓冲区,而且如果想要将这个过程应用于子缓冲区上,您只需取主缓冲区的一个片,并将它传递给您的函数。这比编写自己的函数来取额外的参数以指定要对缓冲区的哪一部分进行操作更容易。

5.5  只读缓冲区

只读缓冲区非常简单您可以读取它们,但是不能向它们写入。可以通过调用缓冲区的 asReadOnlyBuffer() 方法,将任何常规缓冲区转换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区(并与其共享数据),只不过它是只读的。

只读缓冲区对于保护数据很有用。在将缓冲区传递给某个对象的方法时,您无法知道这个方法是否会修改缓冲区中的数据。创建一个只读的缓冲区可以保证该缓冲区不会被修改。

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


5.6  直接和间接缓冲区

另一种有用的ByteBuffer 是直接缓冲区。直接缓冲区是为加快 I/O 速度,而以一种特殊的方式分配其内存的缓冲区。

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

给定一个直接字节缓冲区,Java 虚拟机将尽最大努力直接对它执行本机 I/O 操作。也就是说,它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后),尝试避免将缓冲区的内容拷贝到一个中间缓冲区中(或者从一个中间缓冲区中拷贝数据)


您可以在例子程序FastCopyFile.java 中看到直接缓冲区的实际应用,这个程序是 CopyFile.java 的另一个版本,它使用了直接缓冲区以提高速度。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import  java.io.*;
import  java.nio.*;
import  java.nio.channels.*;
public  class  FastCopyFile
{
   static  public  void  main( String args[] )  throws  Exception {
     if  (args.length< 2 ) {
       System.err.println(  "Usage: java FastCopyFile infile outfile"  );
       System.exit(  1  );
     }
     String infile = args[ 0 ];
     String outfile = args[ 1 ];
     FileInputStream fin =  new  FileInputStream( infile );
     FileOutputStream fout =  new  FileOutputStream( outfile );
     FileChannel fcin = fin.getChannel();
     FileChannel fcout = fout.getChannel();
     ByteBuffer buffer = ByteBuffer.allocateDirect(  1024  );
     while  ( true ) {
       buffer.clear();
       int  r = fcin.read( buffer );
       if  (r==- 1 ) {
         break ;
       }
       buffer.flip();
       fcout.write( buffer );
     }
   }
}

5.7  内存映射文件 I/O

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

内存映射文件 I/O 是通过使文件中的数据神奇般地出现为内存数组的内容来完成的。这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。一般来说,只有文件中实际读取或者写入的部分才会送入(或者映射)到内存中。

内存映射并不真的神奇或者多么不寻常。现代操作系统一般根据需要将文件的部分映射为内存的部分,从而实现文件系统。Java 内存映射机制不过是在底层操作系统中可以采用这种机制时,提供了对该机制的访问。

尽管创建内存映射文件相当简单,但是向它写入可能是危险的。仅只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。



5.8  将文件映射到内存

了解内存映射的最好方法是使用例子。在下面的例子中,我们要将一个 FileChannel (它的全部或者部分)映射到内存中。为此我们将使用FileChannel.map() 方法。下面代码行将文件的前 1024 个字节映射到内存中:

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

map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,您可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行行映射。



本文转自 梦朝思夕 51CTO博客,原文链接:http://blog.51cto.com/qiangmzsx/1410775

相关文章
|
4月前
|
编解码 网络协议
Netty基础篇:NIO中缓冲区设置太小
Netty基础篇:NIO中缓冲区设置太小
|
4月前
|
存储 网络协议 Java
NIO - 基础入门之通道和缓冲区
NIO - 基础入门之通道和缓冲区
49 0
|
8月前
|
Java
NIO的基本概念和缓冲区
NIO的基本概念和缓冲区
39 0
|
9月前
|
Java
Netty入门到超神系列-BIO、NIO、AIO的认识
I/O概述 I/O其实是Input,Output的缩写,意思是输入和输出,比如:把磁盘中的一个文件读入Java内存中,站在Java内存的角度来看就是输入即input, 可以使用Java提供的输入流如:InputStream#read 来实现。如果是把一个段文本内容从Java内存中保存到磁盘上的某个文件,站在Java内存的角度来说就是输出,可以使用Java提供的输出流:OutputStream#write() 来实现。再比如把一个数据通过网络发送给其他服务器就是输出,或者从其他服务器接收一段数据就是输入。 I/O也是有很多种的,比如Java支持三种:BIO,NIO,AIO
264 0
|
9月前
|
存储 网络协议 Java
Netty入门到超神系列-Java NIO 三大核心(selector,channel,buffer)
选择器,也叫多路复用器,Java的NIO通过selector实现一个线程处理多个客户端链接,多个channel可以注册到同一个Selector,Selector能够监测到channel上是否有读/写事件发生,从而获取事件和对事件进行处理,所以Selector切到哪个channel是由事件决定的。当线程从某个客户端通道未读取到数据时,可以把空闲时间用来做其他任务,性能得到了提升。
100 0
|
9月前
|
弹性计算 Java API
Netty入门到超神系列-Java NIO 三大核心(selector,channel,buffer)
理解Selector 和 Channel Selector 选择器,也叫多路复用器,可以同时处理多个客户端连接,多路复用器采用轮询机制来选择有读写事件的客户端链接进行处理。 通过 Selector ,一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。 由于它的读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。
190 0
|
9月前
|
Java
Netty入门到超神系列-Java NIO零拷贝实战
这一章我们来操作一下NIO的零拷贝,这里我会先写代码样式一下传统IO数据拷贝场景下的耗时,然后再对比NIO场景下的考别耗时,通过耗时差异就能看到NIO零拷贝和传统IO拷贝的区别了。
79 0
|
网络协议 安全 Java
即时通讯技术文集(第9期):Java NIO和Netty入门系列 [共19篇]
为了更好地分类阅读52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第9 期。
196 0
即时通讯技术文集(第9期):Java NIO和Netty入门系列 [共19篇]
Java Nio (三):直接缓冲区 和 非直接缓冲区
Java Nio (三):直接缓冲区 和 非直接缓冲区
Java Nio (三):直接缓冲区 和 非直接缓冲区
J3
|
Java API
详解,NIO中的缓冲区
NIO 的出现就是为了解决传统 IO 上的不足,而 NIO 三大组件中的缓冲区就是提高效率的组件之一。 在 NIO 中缓冲区是占据着非常重要的地位,因为数据就放在缓冲区中,对数据的 CRUD 操作都是对缓冲区的操作,所以缓冲区操作的对于否都直接关系到最终结果的正确性。 下面就开始了解它把!
J3
127 0
详解,NIO中的缓冲区