`
QING____
  • 浏览: 2233029 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

NIO-BUFFER详解

 
阅读更多

一.Buffer类(抽象):

    一个用于特定基本数据的容器.buffer即为特定基本类型元素的线性存储容器,底层通过数组结构支撑.buffer具有数据的很多特征,如容量,限制(limit),位置(postion),以及方便操作的"标记"(mark).

 

    容量:capacity,即buffer所能承载的最大数据元素个数.一个buffer一旦创建,capacity将无法被修改.

    限制:limit,即buffer中可供read操作的边界值(offset),limit值需要[0,capacity]

    位置:position,即当前操作所处于的索引位置,对于read操作值为[0,limit],对于write操作[0,capacity]

 

    除boolean之外,其他的基本数据类型都有相应的buffer类.

    buffer类都具有3个基本操作:分配空间(allocate)/put/get.

    buffer类提供了"只读模式"的视图buffer,任何put操作都将不被支持,但是其position/limit/mark等是可以被修改.可以通过isReadOnly来判断buffer的读写特性.buffer类(包括其子类)非线程安全,如果一个buffer需要在多线程之间修改,那么需要必要的同步操作.

    对于buffer的操作API,有很多"约定"的调用顺序,只有如此调用才能得到预期的结果,例如:

ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(...);
buffer.flip();
buffer.get();
 
  • public final int capacity()
  • public final int limit()
  • public final int position():获取buffer的各项属性.这些属性对于IO操作来说非常重要.
  • public final Buffer limit(int limit)
  • publicfinal Buffer position(int position):重置参数.参数值需要符合边界限制.
  • public final Buffer reset():在对buffer多次get操作后,如果想重复操作,可以执行reset.reset是将position重新复位到mark位置.如果mark没有设定,将抛出异常.
    buffer.mark();
    buffer.get(..);//或put()...
    buffer.get(...);//或put
    buffer.reset();//
     
  • public final Buffer clear():清空缓冲区,逻辑清空,将position只为0,limit设置为capacity.mark被丢失(设置为-1),执行clear之后,buffer犹如"空"容器一样可以被write操作.
  • public final Buffer flip():反转buffer."limit = position;position = 0";flip之后,此buffer即准备待续,可以被IO操作write,即可以对buffer进行get调用.
    ////
    buffer.put(...);
    buffer.flip;
    channel.write(buffer);
  • public final Buffer rewind():重绕buffer,"position = 0";可以对一个buffer进行"重新"的get或者put.注意rewind方法,不会改变limit值.
buffer.put(...);
buffer.flip();
channel.write(buffer);
buffer.rewind();
channel2.write(buffer);
 
  •  public final int remaining():检测buffer中可供get的剩余元素的个数.即limit - position;可以配合hasRemaining方法使用.
  • public abstract boolean isReadOnly():当前buffer是否为只读buffer.对于只读buffer将会具有一个属性为readOnly = true;只读的buffer为视图buffer.它将和实体buffer共享底层数据,但是各自具有不同的postion和limit.
  • public abstract boolean hasArray():检测当前buffer是否允许直接获取底层的数组结构.return (hb != null && !isReadOnly());即如果buffer已经分配空间(hb被创建)同同时也不是"只读".如果此方法返回true,将可以使用array()方法获取底层的hb数组结构.直接返回数组.
  • public abstract int arrayOffset():需要配合boolean hasArray()检测.在true的情况下才可以访问此方法,如果为false,此方法将会抛出UnsupportedOperationException.如果buffer为Direct分配,此时hasArray将返回false,因为实际的数据不再JVM中,底层的数据结构hb为null.
  • public abstract boolean isDirect():告知缓冲区是否为直接缓冲区,通过allocateDirect()方法创建的buffer.(来自ByteBuffer)此属性在buffer创建时,就已经注定.将无法被修改.

二.ByteBuffer类(字节缓冲区,抽象类,继承自Buffer):

    ByteBuffer为抽象类,之所以抽象是因为其具体实现和buffer的存储特性有直接关系.

    ByteBuffer提供了转换成其他视图buffer的便捷方法.(asCharBuffer,asIntBuffer()....)

    ByteBuffer也是基于IO/NIO操作的支持的buffer类型..

    ByteBuffer提供了compact(),duplicate(),slice()等特有操作.

    此buffer可以通过allocate方式(静态分配法)和wrap方式.根据缓冲区的分配类型,分为"直接缓冲区""Heap缓冲区".从数据的流向来说:Netinterface buffer(网络接口缓冲区) -->kernal buffer(系统内存) -- > process buffer(JVM内存中).

 

    直接缓冲区,简单来说,就是有OS直接分配缓冲区,对缓冲区的操作也将通过本地方法的方式直接操作,而不再有buffer copy的情况.直接缓冲区可以通过ByteBuffer.allocateDirect()来创建.此方法返回的缓冲区进行分配和取消分配所需要的成本通常高于非直接缓冲区.直接缓冲区的内容长期驻留在常规的垃圾回收堆中,因此他们对应用程序内存需求量造成的影响并非明显.所以,建议将直接缓冲区主要分配给那些易受基础系统的本机I/O操作影响的大型的/长期的缓冲区.一般情况下,最好仅仅在直接缓冲区在程序性能方面所得到的收益更加明显(更少buffer拷贝次数).

    直接ByteBuffer还可以通过mapping将文件区域直接映射到内存中创建.java平台的实现有助于通过JNI(native方法)从本机代码创建直接ByteBuffer.如果OS没有足够的内存(物理内存不足,或者swap空间不足),仍然不能成功获取直接缓冲区,以OOM的方式结束.

 

    如果直接缓冲区分配成功,那么DirectByteBufer对象将会持有内存地址(address,long)以及容量,此后使用它进行get/put操作都会直接操作内存(unsafe.getByte(address + offset));即使使用了直接缓冲区,并不意味着不消耗JVM堆空间,比如使用:DirectByteBuffer.get(dst,offset,length)方法仍然会导致字节数组的copy,意味着将额外的消耗JVM空间.

 

    当DirectByteBuffer实例销毁时,也将触发直接内存的回收:unsafe.freeMemory(address...).通常我们需要重用DirectByteBuffer,而不是重复的创建/销毁,这一点非常的重要.这就要求开发者需要让DirectByteBuffer分配合适的内存,并多次重用它,但还要避免线程的并发对数据安全性的影响.(参见源码,每个DirectByteBuffer都有一个Cleaner,它是一个虚引用对象,当DirectByteBufferGC时将会有单独的线程去freeMemory。)

 

 

 

非直接缓冲区,即为有VM创建的进程内存缓冲区,和[普通的创建对象一样.在进行IO操作的时候,会将buffer copy到系统内存中,然后系统将指示网络驱动器(文件驱动器)对数据进行操作.

 

ByteBuffer还提供了getInt/putInt/getChar/putChar等直接操作基本数据类型的便捷方法.对于ByteBuffer是以2个字节存储字符串,所有基本类型都以BIG_ENDIAN字节排序存储.

字节排序请参看ByteOrder类.

视图缓冲区(view buffer)的put/get方法,将会根据相应类型的字节存储规则(个数和顺序)进行操作.

  1. 视图缓冲区不是根据字节进行索引,而是根据其特定类型的值的大小进行索引(position + byte_size).
  2. 视图缓冲区提供了相对批量get和put的方法.例如:IntBuffer.put(int[]).
  3. 视图缓冲区的内存分配类型,有其byteBuffer决定.

因为ByteBuffer支持各种基本数据类型的操作,所以put和get的顺序需要匹配,才可以确保操作正常.

//
byteBuffer.putInt(100);
byteBuffer.putShort(2);
byteBuffer.flip();
byteBuffer.getInt();
byteBuffer.getShort()
 

ByteBuffer根据其分配方式,其子类结构如下:

ByteBuffer

|                            |

|                            |

HeapByteBuffer        MappedByteBuffer               

|                                    |

|                                    |

HeapByteBufferR(只读)         DirectByteBuffer

|

DirectByteBufferR(只读)

 

 

  •  public static ByteBuffer allocateDirect(int cap): (创建buffer)创建一个直接缓冲区,容量为cap.底层将创建一个DirectByteBuffer实例.此类型的Buffer底层的数组结构hb,将为null,任何对数据的操作都将通过JNI的方式操作.实现为:unsafe.allocateMemory(cap + Bits.pageSize()).
  • public static ByteBuffer allocate(int cap):创建一个非直接缓冲区,即HeapByteBuffer,数组结构有JVM维护,其为普通的java数组的封装.此方法将导致一个cap大小的数组被创建.
  • public ByteBuffer wrap(byte[] bytes): 包装一个bytes通过wrap一个字节数组的方式,生成一个byteBuffer,为HeapByteBuffer实例.它将和被包装数组共享数据.(即并没有对bytes进行copy操作)通过wrap方式创建的ByteBuffer,其capacity为原始内容的字节长度,limit == capacity,position = 0;使用wrap方式创建byteBuffer,绝大部分场景是我们需要从外部获得数据且直接在IO中发送.注意,wrap之后,如果想对数据进行get,无需执行flip.

 

///
byte[] test = {1,2,3};
ByteBuffer bb = ByteBuffer.wrap(test);
System.out.println(bb.capacity() + "//" + bb.position() + "//" + bb.limit());
// bb.flip();//如果接下来执行get,无需flip,否则将会导致limit参数被修改.
System.out.println(bb.get(0));//1
test[0] = 2;
bb.rewind();
System.out.println(bb.get(0));//2
 

 

  • public abstract ByteBuffer slice():对原缓冲区的remaining创建新的缓冲区,其内容为原缓冲区的共享子序列.但和原缓冲区具有独立的position/limit/mark.新缓冲区的内容范围为原缓冲区的position ~ limit.适用于当byteBuffer在不同的调用过程中使用.
  • public abstract ByteBuffer duplicate():对原缓冲区创建"复制",底层共享数组结构.和slice不同的时,duplicate方法将创建原缓冲区的全部"复制",并且其初始的position/limit都和原缓冲区一样.但是新缓冲区和原缓冲区,具有各自独立的position/limit/mark.并且底层数据改动将互相可见.
  • public abstract ByteBuffer asReadOnlyBuffer():创建一个新的"只读"缓冲区,新缓冲区的初始position/limit/capacity和原缓冲区一样,但它们和原缓冲区独立.如果原缓冲区为direct,那么此只读缓冲区也为direct.
  • public abstract byte get():获取当前位置的字节,然后position递增.
  • public abstract byte get(int index):获取指定索引的字节值,注意index必须小于limit.
  • public abstract ByteBuffer put(byte):下一个位置设置新值.
  • public abstract ByteBuffer get(byte[] dst):批量获取字节,注意dst数组的长度不得大于缓冲区的remaining.即要确保dst能够被完全填充.
  • public abstract ByteBuffer put(byte[] dst):批量填充缓冲区,dst长度不得大于缓冲区的remaining,即(limit - position).确保dst能够被完全写入.
  • public abstract ByteBuffer compact():压缩此缓冲区.去除此缓冲区中已经被"消费"的数据.把position~limit之间的数据copy到缓冲区的开始处.此后position=remaining();
limit = capacity();
//即compact方法之后,即可以继续put操作.
////
while(channel.read(buffer) >0
buffer.flip();
channelX.write(buffer);
buffer.compact();
}
 
  • public final ByteOrder order():获取此缓冲区的字节顺序.,新自己缓冲区始终为BIG_ENDIAN.
  • public final boolean hasArray():同Buffer.
  • public abstract CharBuffer asCharBuffer():创建一下CharBuffer类型的视图buffer.和其他视图类型的buffer一样,CharBuffer将和原buffer具有各自独立的position/limit和mark.新缓冲区的position为0,其容量和limit都将为原buffer的一半(默认char为2个字节存储)
  • public abstract IntBuffer asIntBuffer():同上,不过int将用4个自己存储,即其容量和limit将会为原buffer的四分之一.

 三.其他

视图缓冲区:CharBuffer,DoubleBuff,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer.

MappedByteBuffer,是一种特殊的buffer,即直接缓冲区,其内容是文件的内存映射区域.ByteBuffer所使用的allocateDirect方式创建的buffer也是MappedByteBuffer类型.MappedByteBuffer类为抽象类,其子类为DirectByteBuffer.MappedByteBuffer也可以通过FileChannel.map方式获取.MappedByteBuffer和它所表示的文件映射关系在该缓冲区本身成为垃圾回收缓冲区之前一直保持有效.此类无法被直接构造,MappedByteBuffer继承自ByteBuffer.

 

  • MappedByteBuffer force():将此缓冲区所做的内容更改强制写入包含映射文件的存储设备中。如果映射到此缓冲区中的文件位于本地存储设备上,那么当此方法返回时,可以保证自此缓冲区创建以来,或自最后一次调用此方法以来,已经将对缓冲区所做的所有更改写入到该设备。如果文件不在本地设备上,则无法作出这样的保证。如果此缓冲区不是以读/写模式 (FileChannel.MapMode.READ_WRITE) 映射的,则调用此方法无效。
  • public final MappedByteBuffer load():将此缓冲区内容加载到物理内存中。此方法最大限度地确保在它返回时此缓冲区内容位于物理内存中。调用此方法可能导致一些页面错误,并导致发生 I/O 操作。
  • public final boolean isLoaded():判断此缓冲区的内容是否位于物理内存中。返回值为 true 意味着此缓冲区中所有数据极有可能都位于物理内存中,因此是可访问的,不会导致任何虚拟内存页错误,也无需任何 I/O 操作。返回值为 false 不一定意味着缓冲区的内容不位于物理内存中。返回值是一个提示,而不是保证,因为在此方法的调用返回之前,底层操作系统可能已经移出某些缓冲区数据。
分享到:
评论

相关推荐

    java网络编程NIO视频教程

    05-Java NIO-Channel-FileChannel详解(一).mp4 06-Java NIO-Channel-FileChannel详解(二).mp4 07-Java NIO-Channel-Socket通道-概述.mp4 08-Java NIO-Channel-ServerSocketChannel.mp4 09-Java NIO-Channel-...

    Java NIO实战开发多人聊天室

    05-Java NIO-Channel-FileChannel详解(一).mp4 06-Java NIO-Channel-FileChannel详解(二).mp4 08-Java NIO-Channel-ServerSocketChannel.mp4 09-Java NIO-Channel-SocketChannel.mp4 10-Java NIO-Channel-...

    JAVA IO-NIO 详解

    NIO的核心组件包括Channel(通道)、Buffer(缓冲区)和Selector(选择器)。Channel是数据传输的通道,它替代了传统IO中的流;Buffer是数据的容器,它可以在Channel和程序之间进行数据的读写操作;Selector则用于...

    Java NIO Buffer过程详解

    主要介绍了Java NIO Buffer过程详解,缓冲区在java nio中负责数据的存储。缓冲区就是数组。用于存储不同数据类型的数据。,需要的朋友可以参考下

    Java 高并发八:NIO和AIO详解

    本文主要介绍Java 高并发NIO和AIO 的知识,这里整理了详细的资料,并详细介绍了 1. 什么是NIO 2. Buffer 3. Channel 4. 网络编程 5. AIO的知识,有需要的小伙伴可以参考下

    java8中NIO缓冲区(Buffer)的数据存储详解

    在本篇文章中小编给大家分享了关于java8中NIO缓冲区(Buffer)的数据存储的相关知识点,需要的朋友们参考下。

    精通并发与netty视频教程(2018)视频教程

    51_NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 52_NioEventLoopGroup源码分析与线程数设定 53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与...

    精通并发与 netty 视频教程(2018)视频教程

    32_IO体系架构系统回顾与装饰模式的具体应用 33_Java NIO深入详解与体系分析 34_Buffer中各重要状态属性的含义与关系图解 35_Java NIO核心类源码解读与分析 36_文件通道用法详解 37_Buffer深入详解 38_NIO堆外内存与...

    精通并发与netty 无加密视频

    第51讲:NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 第52讲:NioEventLoopGroup源码分析与线程数设定 第53讲:Netty对Executor的实现机制源码分析 第54讲:Netty服务端初始化过程与反射在其中的应用分析...

    Java 基础核心总结 +经典算法大全.rar

    缓冲区(Buffer)通道(Channel) 示例:文件拷贝案例 BIO 和 NIO 拷贝文件的区别操作系统的零拷贝 选择器(Selectors) 选择键(SelectionKey) 示例:简易的客户端服务器通信 集合 集合框架总览 -、Iterator Iterable ...

    Java CP/IP Socket编程

    5.4 Buffer详解..........125 5.4.1 Buffer索引..........125 5.4.2 创建Buffer..........126 5.4.3 存储和接收数据..........128 5.4.4 准备Buffer:clear(),flip(),和rewind()..........130 5.4.5 压缩...

    JAVA核心知识点整理(有效)

    1. 目录 1. 2. 目录 .........................................................................................................................................................1 JVM ........................

    疯狂JAVA讲义

    6.11.1 jar命令详解 235 6.11.2 创建可执行的JAR包 237 6.11.3 关于JAR包的技巧 238 6.12 本章小结 239 本章练习 239 第7章 Java集合 240 7.1 Java集合概述 241 7.2 Collection和Iterator接口 243 7.2.1 ...

Global site tag (gtag.js) - Google Analytics