Java Buffer编程基础

目录 [?]

  1. Buffer抽象类的成员

    1. 属性properties
    2. 操作方法
  2. Buffer的子类
    1. ByteBuffer: 最通用的子类, 处理字节数据类型。
    2. CharBuffer
    3. DoubleBuffer
    4. FloatBuffer
    5. IntBuffer
    6. LongBuffer
    7. ShortBuffer
    8. MappedByteBuffer
  3. Buffer的创建
    1. 直接缓冲区和间接缓冲区
  4. 参考

Java 1.4中在java.nio包中增加了Buffer类以及一些处理基本数据类型的子类(除了boolean型) ,用来提供为基本数据类型(primitive) 的数据提供一个容器。
何谓Buffer? Buffer 是一个线性的有限长度的特定基本数据的序列。 除了基础数据外,它还包括一些基础操作和属性, 比如capacity, limit 和 position。

实际使用中使用特定的子类来处理数据。每个子类都定义了两套get/put的操作。

  • 相对位置操作 (Relative )。 从当前位置position读写一个或者多个元素, 并position增加相应的数值。 如果一个get请求的数据超过了limit的位置,会抛出BufferUnderflowException异常。 如果一个put操作超过了limit的限制, 会抛出BufferOverflowException异常。不管上面哪种情况,没有数据被传输。
  • 绝对位置操作 (Absolute )。 显式地提供index, 不会影响position的值。 如果索引超过limit会抛出IndexOutOfBoundsException异常。

数据也可以通过Channel的I/O操作如write,read 写入或者读出。
显然, Buffer只有写入了数据才可能有意义的数据读出。

Buffer类并不是线程安全的, 使用时要特别小心, 避免多线程同时读写同一个Buffer。 万不得已, 需要为读写操作加锁。

cache和buffer的区别
从应用场景上看:Buffer 更多的(场景)是减小写操作的冲击,而 Cache 主要用于减小读 I/O 的重复开销。

Buffer抽象类的成员

Buffer提供了一系列的操作缓冲区的方法以及属性。 但是属性(property)不是以字段field的方式提供,而是以方法method的方式提供。

属性properties

  • capacity() : 上面提到, Buffer里的元素是有限的。 这个值代表Buffer的元素的最大数量。 这个值不会为负数, 也不会被改变。
  • limit() : 很多情况下, 缓冲区不是填满的。 limit是第一不应该被读/写的数据的索引位置。 显然这个值不能为负数,也不会超过capacity的值。
  • position() : 下一个要被读/写的数据的索引。 不能为负值也不会超过limit的值。
  • mark : 被标记的索引。 调用reset方法会将position的值设为mark的值。 这样可以重新读/写Buffer的数据。 当position或者limit的值小于它的值时,它的值会被丢弃。 它的值不能为负数,也不会超过position的值, 也可能没有设置, 如果没有设置的话调用reset方法会抛出InvalidMarkException异常。 没有直接读取的方法。
  • remaining() : 返回position和limit之差, 也就是未读/写的数据的数量.

操作方法

  • reset 重置。 将position的值重置为mark的值。 这个方法不会更改mark的值,也不会将mark的值丢掉。
  • clear 清空。 清空缓冲区。 position的值设为0, limit的值设为capacity,mark的值被丢弃。 在填充Buffer之前一般会调用此方法:buf.clear(); in.read(buf);。 这个方法不会擦除以前填充的数据,但是在实际使用中的情况下功能一样。
  • flip 反转。 反转缓冲区会将limit的值设为position的值, 然后position的值设为0。 如果设置了mark, 则会被丢弃。 一般在填充完缓冲区后读写数据时调用此方法:buf.put(magic);in.read(buf);buf.flip();out.write(buf);
  • rewind 回退。 position设为0, mark值被丢弃, limit的值不变。 和上面的flip类似,但是flip会改变limit的值,但是remind不会。 应用场景: out.write(buf);buf.rewind(); buf.get(array);
  • mark() 标记当前位置。 用position的值设置mark。

clear,flip,limit(newLimit),mark,position(newPosition), reset()和rewind返回本身的Buffer, 这意味着你可以使用流式风格, 如
buffer.flip().position(23).limit(42);

请记住以下公式, 下面的不等式在任何时候都成立:
0 <= mark <= position <= limit <= capacity
一个新创建的Buffer的position总是0, mark未定义。 初始的limit可能为0,或者其它正值, 这依赖于buffer的类型以及它是如何创建的。 初始化的Buffer包含零个元素。

另外, Buffer还提供其它的一些成员:

  • array: 返回底层的数组实现。 如果底层不是使用数组实现,或者是只读的, 可能会抛出异常ReadOnlyBufferException, UnsupportedOperationException。 一般使用前会调用hasArray判断是否支持数组。 Buffer内容的修改会影响数组的值,反之亦然。
  • arrayOffset: Buffer的position的值p对应 数组的p + arrayOffset(), 也就是Buffer的第一个元素在数组中的偏移值。
  • hasArray: 判断Buffer是否有底层的数组实现。
  • hasRemaining: position和limit之前是否还有元素。
  • isReadOnly: 是否只读。
  • isDirect: buffer是否是直接缓冲区。

equals()
当满足下列条件时,表示两个Buffer相等:

  • 有相同的类型(byte、char、int等)。
  • Buffer中剩余的byte、char等的个数相等。
  • Buffer中所有剩余的byte、char等都相同。
    注意它只比较剩余的部分。

compareTo()
compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer:

  • 第一个不相等的元素小于另一个Buffer中对应的元素 。
  • 前面的元素都相等,但第一个Buffer剩余的元素比另一个少。

Buffer的子类

Buffer是一个抽象类。 实际我们使用的是它的子类, 主要是针对基本数据类型做的优化。

ByteBuffer: 最通用的子类, 处理字节数据类型。

提供了get/put 单个字节或者字节数组的方法, 还是分相对和绝对操作。
字节是其它基本数据类型的基础。 比如int类型是32位也就是4个字节。

Data Type size (byte)
byte 1
short 2
int 4
long 8
float 4
double 8
char 2

所以可以将这些基本数据类型的数据写入到字节缓冲区中或者从中读出。
ByteBuffer针对基本数据类型定义了便利的方法, 如getChar(),getInt(),putFloat,putShort ...等方法。
注意putXXX可能会抛出BufferOverflowException和ReadOnlyBufferException异常, getXXX可能会抛出BufferUnderflowException异常。

同时ByteBuffer还提供了创建视图view的方法。 可以基于ByteBuffer创建其它基本类型的buffer,它们的底层数据指向都一个对象,但是相应的position, limit, mark都是独立的。例如asIntBuffer()返回一个IntBuffer对象, 返回的IntBuffer对象的第一个元素对应于此ByteBuffer的position的位置的元素。 IntBuffer的position的值为0,capacity 和limit是此ByteBuffer的剩余的字节的数量/4 (int是四个字节), mark未定义。 当且仅当ByteBuffer是直接缓冲区时此IntBuffer才是直接缓冲区, 当且仅当ByteBuffer是只读的 IntBuffer才是只读的。

注意ByteBuffer依然是抽象类, allocate方法和allocateDirect方法创建缓冲区时实际是创建HeapByteBuffer或者DirectByteBuffer类。

仍然支持流式风格。 bb.putInt(0xCAFEBABE).putShort(3).putShort(45);

asReadOnlyBuffer()转换成只读缓冲区。新缓冲区的position, limit, 和 mark是独立的。 初始值和原缓冲区相同。
duplicate复制当前的ByteBuffer,底层的数据是公用的,但是position,capacity,limit ,mark是独立的, 方法返回的ByteBuffer初始拥有和原ByteBuffer相同的position,capacity,limit ,mark。
slice()也是一个新的byte buffer,和原bye buffer的数据共享。 但是新的byte buffer将自原byte buffer的position的位置开始。 这也就是slice的含义。

提供了allocate(int capacity)和allocateDirect(int capacity)两种方法。
wrap(byte[] array), wrap(byte[] array, int offset, int length)将字节数组包装成ByteBuffer。

order()和order(ByteOrder bo)用来返回和设置字节序: 大端模式(BIG_ENDIAN)和小端模式(LITTLE_ENDIAN)。 默认总是大端模式(BIG_ENDIAN)。

compact: 压缩ByteBuffer。 将position和limit的之间的数据复制到缓冲区的开始部分。 比如另p = position,则将 p + 1处的数据复制到index 1, ...... limit -1处的数据复制到n = limit -1 -p。 缓冲区的position设置为n+1, limit设置为capacity, mark丢弃.

以下的子类类似ByteBuffer,但是没有转换成其它Buffer的方法和视图。这是容易理解的,因为Byte才是其它基本数据类型的基础单位。
wrap包装相应基本数据类型的数组。依然有compact, duplicate, slice方法. 流式风格, get/put 单数据操作和批操作, 相对位置操作和绝对位置操作。

CharBuffer

提供了append方法,等同于put方法。
charAt(int index)返回指定位置的字符。
subSequence(int start, int end)返回指定位置的缓冲区。 与原缓冲区共享数据。 capacity相同。 新缓冲区的position为原缓冲区position + start, limit为原缓冲区的position + end。 direct, readonly和原缓冲区相同。

DoubleBuffer

处理double类型数据。

FloatBuffer

处理float类型数据。

IntBuffer

处理int类型数据。

LongBuffer

处理long类型数据。

ShortBuffer

处理short类型数据。

MappedByteBuffer

继承于ByteBuffer。 它是以内存镜像文件为基础的直接字节缓冲区。 可以通过FileChannel.map创建。
force()强制对数据的改变写入到存储设备。
isLoaded() : 缓冲区的数据是否都全部加载到物理内存中。
load() : 加载缓冲区的数据到物理内存中。

内存映射文件是一种允许Java程序直接从内存访问的特殊文件。通过将整个文件或者文件的一部分映射到内存中、操作系统负责获取页面请求和写入文件,应用程序就只需要处理内存数据,这样可以实现非常快速的IO操作。用于内存映射文件的内存在Java的堆空间以外。

Buffer的创建

  • allocate()或者allocateDirect()
  • CharBuffer cb = CharBuffer.allocate(1024);
    ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024);
  • 包装一个数组
  • CharBuffer cb = CharBuffer.allocate(1024);
    ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024);
  • 内存映射,即调用FileChannel的map()方法 
    FileChannel fc = new RandomAccessFile("test.data", "rw").getChannel();
    MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);
    

直接缓冲区和间接缓冲区

byte buffer既可以是直接缓冲区可以是非直接缓冲区。 对于直接缓冲区, Java虚拟机极可能的直接执行native I/O操作,避免在操作系统的native I/O操作时还要复制内容到一个中间缓冲区。
它使用Native函数库直接分配堆外内存,然后通过一个存储在JAVA堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。

可以通过allocateDirect工厂方法直接创建直接缓冲区, 内部会创建DirectByteBuffer对象, 通过unsafe.allocateMemory分配内存。 相对而言, 这个方法返回的缓冲区要比非直接缓冲区多少有点更高的分配/销毁的花费 (时间和空间)。 直接缓冲区在垃圾回收堆的外部, 所以建议主要用于大的长时间活动的缓冲区,确实能提高性能的环境中。

也可以通过内存镜像文件的方式使用直接缓冲区 FileChannel.map。 Java平台可选择使用JNI来创建直接缓冲区。 如果Buffer指向一个不能访问的内存区域时, 缓冲区的内容不会被更改, 访问操作可能会导致一个不确定的异常。

可以通过isDirect方法判断一个缓冲区是否是直接缓冲区。

虽然直接缓冲区是堆外内存,但是由于DirectByteBuffer引用了它,当DirectByteBuffer被垃圾回收时,此堆外内存会被释放掉,不会出现内存泄漏的问题。

时间: 2024-10-06 08:05:42

Java Buffer编程基础的相关文章

Java网络编程基础(六)— 基于TCP的NIO简单聊天系统

在Java网络编程基础(四)中提到了基于Socket的TCP/IP简单聊天系统实现了一个多客户端之间护法消息的简单聊天系统.其服务端采用了多线程来处理多个客户端的消息发送,并转发给目的用户.但是由于它是基于Socket的,因此是阻塞的. 本节我们将通过SocketChannel和ServerSocketChannel来实现同样的功能. 1.客户端输入消息的格式 username:msg    username表示要发送的的用户名,msg为发送内容,以冒号分割 2.实现思路 实现思路与Java网络

Java网络编程和NIO详解开篇:Java网络编程基础

Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为我们拥有网络.网络是一个神奇的东西,它改变了你和我的生活方式,改变了整个世界. 然而,网络的无标度和小世界特性使得它又是复杂的,无所不在,无所不能,以致于我们无法区分甚至无法描述. 对于一个码农而言,了解网络的基础知识可能还是从了解定义开始,认识OSI的七层协议模型,深入Socket内部,进而熟练地

JAVA学习(五):Java面向对象编程基础

Java面向对象编程基础 面向对象(Object oriented programming,OOP)技术是一种强有力的软件开发方法,它採用数据抽象与信息隐藏技术,来使软件开发简单化,以达到代码重用的目的. 1.OOP的3个特性(封装.继承和多态性) 封装是类的基础.指把类的相关实现细节隐藏起来,在类中将数据和实现操作的代码集中起来放在对象的内部.调用这些类时仅仅需直接使用类预留的接口就能够了. 继承提供了子类自己主动拥有父类数据结构和方法的机制.它表示类之间的一种关系. 多态指使一个对象被看成还

Java多线程编程基础之线程对象

在进入java平台的线程对象之前,基于基础篇(一)的一些问题,我先插入两个基本概念. [线程的并发与并行] 在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent).而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel). 在上面包括以后的所有论述中,请各位朋友谅解,我无法用最准确的词语来定义储

如何夯实(Java)编程基础,并深入学习和提高

如何夯实(Java)编程基础,并深入学习和提高? 240赞同反对,不会显示你的姓名 匿名用户 240 人赞同 多学习...网上自学的学习网站很多,见以下榜单~一.汇总榜单: 公开课_学习网站导航 收录了网易.多贝.传课等众多公开课学习网站 大学生常用_学习网站导航收录了外语学习.就业.实习.考研等众多大学生相关的学习网站 IT互联网_学习网站导航收录了IT.程序员.web开发.移动开发等众多互联网相关的学习网站 IT职业技能_学习网站导航收录了产品经理.UI设计师.前端.网络安全等互联网职业技能

java socket编程基础(转)

一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机. 而TCP层则提供面向应用的可靠(tcp)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的. 目前较为流行的网络编程模型是客户机/服务器(C/S)结构.即通信双方一方作为服务器等待客户提出请求并予以响应.客户则

Java网络编程基础【转】

网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编程是比较复杂的系统工程,需要了解很多和网络相关的基础知识,其实这些都不是很必需的.首先来问一个问题:你 会打手机吗?很多人可能说肯定会啊,不就是按按电话号码,拨打电话嘛,很简单的事情啊!其实初学者如果入门网络编程的话也可以做到这么简单! 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据.

Java网络编程基础

一.Java网络编程 网络编程在如今这样的网络时代是十分重要的,Java语言提供了丰富的类库来支持网络编程.这里将重点介绍Java.net中的类,充分了解认识Java网络编程的原理并深入学习各模块.在学习Java网络编程之前首先需要具备一定的网络知识:网络的层次结构,常见的网络协议(TCP/IP),IP地址端口号等等.需要学习了解这些内容,可以参考前面的文章. 二.主机地址和IP地址 在进行网络访问时,每个主机都有IP地址,也有主机名(域名),为了便于处理与之相关的问题,Java.net包中提供

【转】JAVA网络编程基础

转来自己学习用 转自http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编程是比较复杂的系统工程,需要了解很多和网络相关的基础知识,其实这些都不是很必需的.首先来问一个问题:你 会打手机吗?很多人可能说肯定会啊,不就是按按电