Java nio中HeapByteBuffer同MappedByteBuffer简单比较

原创文章,转载请指明出处并保留原文url地址

近期在研究hadoop中,在hadoop的高版版本中主要采用的是nio网络系统,因此将nio在温习一遍,顺便总结一下,陆续还会总结hadoop用到的其他相关知识。

一.Nio中ByteBuffer简介

JDK1.4 中加入了一个新的包:NIO(java.nio.*)。这个库的功能是异步io方式,同时也增增加了对异步套接字的支持。其实在其他语言中,包括在最原始的SOCKET实现(BSD SOCKET),这是一个早有的功能:异步回调读/写事件,通过选择器动态选择感兴趣的事件等等。

在NIO中,数据的读写操作始终是与缓冲区相关联的.读取时通道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区。缓冲区是定长的,基本上它只是一个列表,它所有元素都是基本数据类型,ByteBuffer是最常用的缓冲区,它提供了读写其他数据类型的方法,且通道的读写方法只接收ByteBuffer.因此ByteBuffer的用法是有必要牢固掌握的.
下图是ByteBuffer的类型结构关系

wps_clip_image-10992[3][1]

从上图可以了解大致分为两类bytebuffer:HeapByteBuffer类型、MappedByteBuffer类型。

1. HeapByteBuffer在java的堆内存中开辟缓存区空间,对这个空间进行读写操作,特点简单易行。 缺点堆内存有限,不能开辟太大,例如有1G的视频文件需要传输,能否在堆内存中开辟空间?显然不能的。

2. MappedByteBuffer将文件直接映射到内存的一种缓存区方法,例如我们有1G的视频文件需要传输,我们就可以做一个MappedByteBuffer对象,然后根据需要读取那个部分就直接读取了, 操作系统会帮助我们做好剩下的工作(操作系统没有在内存中装载这1g的空间,是需要那个内存页就加载那个页面,不主要则不加载),具体情况我们稍后在解释。

二.Buffer工作过程简介

1. Buffer的逻辑概念

wps_clip_image-24822[3][1]

如上图,在Buffer中,有三个重要的概念:容量(capacity)、界限(limit)和位置(position)。

l 容量(capacity):缓冲区的 容量(capacity) 表示可以储存在缓冲区中的最大数据容量。缓冲区的容量不可能为负值,在创建后也不能改变;

l 界限(limit):第一个不应该被读出或者写入的缓冲区位置索引;

l 位置(position):用于指明下一个可以被读出的或者写入的缓冲区位置索引。在从通道读取时,position 变量跟踪已经写了多少数据。更准确地说,它指定了下一个字节将放到数组的哪一个元素中。

下面是一个操作buffer的例子, 相关状况,详情参加上面图形

CharBuffer buff = CharBuffer.allocate(9); //1  // 创建一个缓冲区,参考上图左上区域

buff.put('a'); //2                       // 添加一个数据到缓存区,上图的上右区域

buff.put('b'); //3                       // 在添加一个数据,注意position的变化

buff.put('c'); //4                       // 注意只有postion的变化,其他都没变化

buff.flip(); //5                           // 注意 limit及postion的变化

System.out.println(buff.get()); //6      // 注意只有 postion的变化

buff.clear(); //7                      // 注意postion及limit的变化

注意:整个过程中 那个变量一直没有变化?为什么?

2. Buffer的常用操作

l int capacity():返回缓冲区的capacity大小;

l Buffer clear():"清空"缓冲区:它将limit设置成和capacity一样,而将position设置成0。注意这个方法并不会将缓冲区中的数据清除掉;

l Buffer flip():它将界限(limit)设置成当前的位置值(position),然后将位置值(position)回复到0,并返回重新设定limit和position后的缓冲区;

l boolean hasRemaining():判断当前位置(position)和界限(limit)之间是否还有元素可供处理;

l int limit():返回缓冲区的界限(limit);

l Buffer limit(int newLt):重新设置界限(limit)的值,并返回一个具有新的limit的缓冲区对象;

l Buffer mark():设置缓冲区的书签(mark),它只能在0和位置(position)之间做书签(mark),mark值和前面的limit、position、capacity的值的大小关系是:0 <= mark <= position <= limit <= capacity;

l int position():返回缓冲区中的当前位置(position);

l Buffer position(int newPs):设置缓冲区的新位置,并返回一个具有新的位置的缓冲区对象;

l int remaining():返回当前位置和界限(limit)之间的元素个数;

l Buffer reset():将位置回复到使用mark()方法设置的书签处;

l Buffer rewind():将位置(position)设置成0,取消设置的书签。

三.HeapByteBuffer同MappedByteBuffer 工作方式比较

MappedByteBuffer 只是一种特殊的 ByteBuffer ,即是ByteBuffer的子类。

MappedByteBuffer 将文件直接映射到内存逻辑地址空间(32位地址空间或者64位地址空间)

当用户访问buffer中一个数据时,操作系统会通过一定的地址寄存器等转换逻辑地址空间到物理内存页, 若是数据已经在内存中则直接操作,若是数据没有在内存中,则操作系统引发缺页中断,操作系统的文件管理、内存管理等程序(工作在内核态)协同下调取数据从磁盘中到内存中,然后系统返回到用户态程序继续执行,一切都是在底层进行的,用户感觉不到,另外大部分操作都是在内核态进行,并且是在真正需要数据时才发生因此效率非常高,这个工作方式是现在操作系统的实现方式,包括windows及linux系列都是这么工作的。

因此这个方式下可以非常高效方便的操作任意大小的buffer了,因为只有需要才发生,没有任何多余操作,效率是最高的。

为什么直接管理内存没有这个效率高?HeapByteBuffer什么情况下效率高?什么情况下效率低?

HeapByteBuffer是直接在系统堆内存中操作(这里是在java的堆里,但是jvm也是操作系统的一个进程,也要遵循操作系统的法则,至少你在运行时必须符合操作系统的要求)。 当HeapByteBuffer中数据较少时,效率很高。 但是当需要内存较多时,切HeapByteBuffer进程没有在工作状态(现在操作系统都是多任务操作系统,同时运行的进程很多,每个任务彼此之间通过时间片分享cpu及各种计算机资源,因此当进程不是正在运行的进程或者说没有占用cpu时,进程的一些资源是可以被操作系统调配的,这样就可以给正在有cpu处理权限的进程最多的资源让他尽快完成),并且操作系统发现有内存需要,同时内存不足,因此会将一些不用内存交换到虚拟内存文件,或者内存页放弃(若是内存页中数据从磁盘中读取并且没有在内存中修改,有可能发生放弃操作,不用交换到磁盘文件中,因此有时候操作系统运行时需要经常锁定一些文件,就是防止被在运行时修改了,比如*.exe文件)。 当HeapByteBuffer进程再次获取了cpu时间后,进入工作状态就会从磁盘中加载数据或者部分数据,这样造成不必要的时间浪费。 另外即使HeapByteBuffer进程中缓存没有交换到磁盘中,由于他使用大内存做缓存也会造成系统整体性能下降,最后可能会牵连到自己进程的。

因此HeapByteBuffer的使用是在缓存数据较少时最有效的。

MappedByteBuffer是在缓存数据很大时高效,并且是读取最高效,随机读取也非常高效,一般随机写就有些情况了,因为牵扯到数据存储,数据移动的情况,就复杂了。例如在一个2G文件中间插入一个字节,一定造成数据的移动的,因此效率未必会好到那里去的。

四.MappedByteBuffer特点及操作方法

1. MappedByteBuffer特点

a) MappedByteBuffer的三种方式:

FileChannel提供了map方法来把文件影射为内存映像文件: MappedByteBuffer map(int mode,long position,long size); 可以把文件的从position开始的size大小的区域映射为内存映像文件,mode指出了 可访问该内存映像文件的方式:READ_ONLY,READ_WRITE,PRIVATE.

a. READ_ONLY,(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)

b. READ_WRITE(读/写): 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。 (MapMode.READ_WRITE)

c. PRIVATE(专用): 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE)

b) MappedByteBuffer的三个方法:

a. fore();缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件

b. load()将缓冲区的内容载入内存,并返回该缓冲区的引用

c. isLoaded()如果缓冲区的内容在物理内存中,则返回真,否则返回假

c) MappedByteBuffer的三个特性:

调用通道的map()方法后,即可将文件的某一部分或全部映射到内存中,映射内存缓冲区是个直接缓冲区,继承自ByteBuffer,但相对于ByteBuffer,它有更多的优点:

a. 读取快

b. 写入快

c. 随时随地写入

2. MappedByteBuffer的操作方法

RandomAccessFile fis = new RandomAccessFile("g:\\test.txt", "rw"); // 创建文件对象

FileChannel fci = fis.getChannel(); // 获取文件通道

MappedByteBuffer mbbi = fci.map(FileChannel.MapMode.READ_ONLY, 0, 1024 * 14 * 1024);  // 打开一个缓冲区

mbbi.flip();

fci.close();

fis.close();

发表评论