ByteBuffer前前后后看过好几次了,实际使用也用了一些,总觉得条理不够清晰。

《程序员的思维修炼》一本书讲过,主动学习,要比单纯看资料效果来的好,所以干脆写个详细点的文章来记录一下。

缓冲区(Buffer)

缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对输入/输出(I/O)的数据作临时存储,这部分预留的内存空间就叫做缓冲区:

使用缓冲区有这么两个好处:

1、减少实际的物理读写次数

2、缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数

举个简单的例子,比如A地有1w块砖要搬到B地

由于没有工具(缓冲区),我们一次只能搬一本,那么就要搬1w次(实际读写次数)

如果A,B两地距离很远的话(IO性能消耗),那么性能消耗将会很大

但是要是此时我们有辆大卡车(缓冲区),一次可运5000本,那么2次就够了

相比之前,性能肯定是大大提高了。

而且一般在实际过程中,我们一般是先将文件读入内存,再从内存写出到别的地方

这样在输入输出过程中我们都可以用缓存来提升IO性能。

所以,buffer在IO中很重要。在旧I/O类库中(相对java.nio包)中的BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter在其实现中都运用了缓冲区。java.nio包公开了Buffer API,使得Java程序可以直接控制和运用缓冲区。

在Java NIO中,缓冲区的作用也是用来临时存储数据,可以理解为是I/O操作中数据的中转站。缓冲区直接为通道(Channel)服务,写入数据到通道或从通道读取数据,这样的操利用缓冲区数据来传递就可以达到对数据高效处理的目的。

概述

ByteBuffer是NIO里用得最多的Buffer,它包含两个实现方式:HeapByteBuffer是基于Java堆的实现,而DirectByteBuffer则使用了unsafe的API进行了堆外的实现。这里只说HeapByteBuffer。

使用

ByteBuffer最核心的方法是put(byte)get()。分别是往ByteBuffer里写一个字节,和读一个字节。

值得注意的是,ByteBuffer的读写模式是分开的,正常的应用场景是:往ByteBuffer里写一些数据,然后flip(),然后再读出来。

这里插两个Channel方面的对象,以便更好的理解Buffer。

ReadableByteChannel是一个从Channel中读取数据,并保存到ByteBuffer的接口,它包含一个方法:

public intread(ByteBuffer dst) throwsIOException;

 

WritableByteChannel则是从ByteBuffer中读取数据,并输出到Channel的接口:

public intwrite(ByteBuffer src) throwsIOException;

 

那么,一个ByteBuffer的使用过程是这样的:

1. byteBuffer = ByteBuffer.allocate(N);    //创建

2. readableByteChannel.read(byteBuffer);   //读取数据,写入byteBuffer

3. byteBuffer.flip();              //变读为写

4. writableByteChannel.write(byteBuffer);   //读取byteBuffer,写入数据

 

看到这里,一般都不太明白flip()干了什么事,先从ByteBuffer结构说起:

ByteBuffer的创建和读写

1. ByteBuffer定义了4个static方法来做创建工作:

  ByteBuffer allocate(int capacity) //创建一个指定capacity的ByteBuffer。
  ByteBuffer allocateDirect(int capacity) //创建一个direct的ByteBuffer,这样的ByteBuffer在参与IO操作时性能会更好
  ByteBuffer wrap(byte [] array)
  ByteBuffer wrap(byte [] array, int offset, int length) //把一个byte数组或byte数组的一部分包装成ByteBuffer。

2. ByteBuffer定义了一系列get和put操作来从中读写byte数据,如下面几个:
  byte get()
  ByteBuffer get(byte [] dst)
  byte get(int index)
  ByteBuffer put(byte b)
  ByteBuffer put(byte [] src)
  ByteBuffer put(int index, byte b)
    这些操作可分为绝对定位和相对定为两种,相对定位的读写操作依靠position来定位Buffer中的位置,并在操
  作完成后会更新position的值。在其它类型的buffer中,也定义了相同的函数来读写数据,唯一不同的就是一
  些参数和返回值的类型。

3. 除了读写byte类型数据的函数,ByteBuffer的一个特别之处是它还定义了读写其它primitive数据的方法,如:

  int getInt()             //从ByteBuffer中读出一个int值。
  ByteBuffer putInt(int value)  // 写入一个int值到ByteBuffer中。

  3.1 字节序

    读写其它类型的数据牵涉到字节序问题,ByteBuffer会按其字节序(大字节序或小字节序)写入或读出一个其它
  类型的数据(int,long…)。字节序可以用order方法来取得和设置:
  ByteOrder order() //返回ByteBuffer的字节序。
  ByteBuffer order(ByteOrder bo)   // 设置ByteBuffer的字节序。

  3.2 ByteOrder
    用来表示ByteBuffer字节序的类,可将其看成java中的enum类型。主要定义了下面几个static方法和属性:
    ByteOrder BIG_ENDIAN       代表大字节序的ByteOrder。
    ByteOrder LITTLE_ENDIAN 代表小字节序的ByteOrder。
    ByteOrder nativeOrder()       返回当前硬件平台的字节序。

4. ByteBuffer另一个特别的地方是可以在它的基础上得到其它类型的buffer。如:
  CharBuffer asCharBuffer()
    为当前的ByteBuffer创建一个CharBuffer的视图。在该视图buffer中的读写操作会按照ByteBuffer的字节
  序作用到ByteBuffer中的数据上。

    用这类方法创建出来的buffer会从ByteBuffer的position位置开始到limit位置结束,可以看作是这段数据
  的视图。视图buffer的readOnly属性和direct属性与ByteBuffer的一致,而且也只有通过这种方法,才可
  以得到其他数据类型的direct buffer。

ByteBuffer内部字段

byte[] buff

buff即内部用于缓存的数组。

position

当前读取的位置。

读/写操作的当前下标。当使用buffer的相对位置进行读/写操作时,读/写会从这个下标进行,并在操作完成后,
buffer会更新下标的值。

mark

为某一读过的位置做标记,便于某些时候回退到该位置。

一个临时存放的位置下标。调用mark()会将mark设为当前的position的值,以后调用reset()会将position属性设
置为mark的值。mark的值总是小于等于position的值,如果将position的值设的比mark小,当前的mark值会被抛弃掉。

capacity

初始化时候的容量。

这个Buffer最多能放多少数据。capacity一般在buffer被创建的时候指定。

limit

在Buffer上进行的读写操作都不能越过这个下标。当写数据到buffer中时,limit一般和capacity相等,当读数据时,
limit代表buffer中有效数据的长度。

读写的上限,limit<=capacity。

这些属性总是满足以下条件:
  0 <= mark <= position <= limit <= capacity

limit和position的值除了通过limit()和position()函数来设置,也可以通过下面这些函数来改变:

Buffer clear()
  把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。

Buffer flip()
  把limit设为当前position,把position设为0,一般在从Buffer读出数据前调用。

Buffer rewind()
  把position设为0,limit不变,一般在把数据重写入Buffer前调用。

compact()

  该方法的作用是将 position 与 limit之间的数据复制到buffer的开始位置,复制后 position  = limit -position,limit = capacity

  但如果position 与limit 之间没有数据的话发,就不会进行复制  详细参考:java nio Buffer 中 compact的作用

mark()与reset()方法

  通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:

  1.buffer.mark();

  2.//call buffer.get() a couple of times, e.g. during parsing.

  3.buffer.reset(); //set position back to mark

equals()与compareTo()方法

  可以使用equals()和compareTo()方法两个Buffer。

  equals()

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

  1. 有相同的类型(byte、char、int等)。
  2. Buffer中剩余的byte、char等的个数相等。
  3. Buffer中所有剩余的byte、char等都相同。

  如你所见,equals只是比较Buffer的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer中的剩余元素。

  compareTo()方法

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

    1. 第一个不相等的元素小于另一个Buffer中对应的元素 。
    2. 所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。

Buffer对象有可能是只读的,这时,任何对该对象的写操作都会触发一个ReadOnlyBufferException。
isReadOnly()方法可以用来判断一个Buffer是否只读。

图解

put

写模式下,往buffer里写一个字节,并把postion移动一位。写模式下,一般limit与capacity相等。

flip

写完数据,需要开始读的时候,将postion复位到0,并将limit设为当前postion。

get

从buffer里读一个字节,并把postion移动一位。上限是limit,即写入数据的最后位置。

clear

将position置为0,并不清除buffer内容。

mark相关的方法主要是mark()(标记)和reset()(回到标记).

这篇文章对buffer的讲解也很详细,可以参考 Java NIO系列教程(三) Buffer

其他具体的接口信息查阅 http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html

参考链接:

1. ByteBuffer常用方法详解

ByteBuffer: 图解ByteBuffer(转)的更多相关文章

  1. AIO系列文档(1)----图解ByteBuffer

    因何而写 网上关于bytebuffer的文章真的很多,为何在此还要写一篇呢?主要是基于以下几点考虑 很多人在使用t-io时,还不会bytebuffer,只会照着t-io提供的例子照猫画虎,不利于灵活运 ...

  2. 图解ByteBuffer

    https://www.cnblogs.com/ruber/p/6857159.html https://www.e-learn.cn/content/qita/750752 https://blog ...

  3. 图解java中的bytebuffer

    因何而写 网上关于bytebuffer的文章真的很多,为何在此还要写一篇呢?主要是基于以下几点考虑 很多人在使用t-io时,还不会bytebuffer,只会照着t-io提供的例子照猫画虎,不利于灵活运 ...

  4. 搞懂iobuffer就得先学习bytebuffer

    ByteBuffer前前后后看过好几次了,实际使用也用了一些,总觉得条理不够清晰. <程序员的思维修炼>一本书讲过,主动学习,要比单纯看资料效果来的好,所以干脆写个详细点的文章来记录一下. ...

  5. Java--Stream,NIO ByteBuffer,NIO MappedByteBuffer性能对比

    目前Java中最IO有多种文件读取的方法,本文章对比Stream,NIO ByteBuffer,NIO MappedByteBuffer的性能,让我们知道到底怎么能写出性能高的文件读取代码. pack ...

  6. 堆外内存操作类ByteBuffer

    本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明: 1 相关背景-->读写操作-->关键属性-->读写实践-->扩展-->参考说明 希望对想使用直接内存 ...

  7. ByteBuffer用法小结

    在NIO中,数据的读写操作始终是与缓冲区相关联的.读取时信道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区.缓冲区是定长的,基本上它只是一个列表,它的所有元素 ...

  8. 【MINA】缓存区ByteBuffer和IOBuffer你要了解的常用知识

    mina中IOBuffer是Nio中ByteBuffer的衍生类,主要是解决Bytebuffer的两个不足 1.没有提供足够灵活的get/putXXX方法 2.它容量固定,难以写入可变长度的数据 特点 ...

  9. ByteBuffer使用之道

    缓冲区分配和包装  在能够读和写之前,必须有一个缓冲区,用静态方法 allocate() 来分配缓冲区:  ByteBuffer buffer = ByteBuffer.allocate(1024); ...

随机推荐

  1. Linux路由:CentOS6的多种玩法

    将一台Linux主机作路由器使用,这本是件很容易的事情,利用Linux主机强大的网络功能,很轻松就实现了.这里在虚拟机环境下设定一台CentOS主机通过另一台CentOS主机路由接入Internet网 ...

  2. 解决Centos7安装python3后pip工具无法使用

    问题描述: Centos7安装python3,正常流程全部配置完成,python3,pip3的软链接也建立了 但是python3可以正常使用,而pip3报错,无法找到文件或目录 解决方法: which ...

  3. GCC使用总结

    概念 GCC一开始是linux系统集成的用来编译C程序的编译器(GNU C Compiler),目前GCC已经不仅仅支持C语言了,因而其缩写名单意义也变成(GNU Compiler Collectio ...

  4. P1772 [ZJOI2006]物流运输[DP+最短路]

    题目描述 物流公司要把一批货物从码头A运到码头B.由于货物量比较大,需要n天才能运完.货物运输过程中一般要转停好几个码头.物流公司通常会设计一条固定的运输路线,以便对整个运输过程实施严格的管理和跟踪. ...

  5. JDK源码那些事儿之ConcurrentLinkedQueue

    阻塞队列的实现前面已经讲解完毕,今天我们继续了解源码中非阻塞队列的实现,接下来就看一看ConcurrentLinkedQueue非阻塞队列是怎么完成操作的 前言 JDK版本号:1.8.0_171 Co ...

  6. 【Python学习】Python3 基础语法

    ==================================================================================================== ...

  7. eclipse 安装反编译工具

    jd-gui是我最喜欢使用的java反编译工具.它是一款用c++开发的轻量级的java反编译工具,无须安装即可以使用,你甚至都不需要安装jre环境就可以实现反编译:支持最新的jdk,目前是jdk 1. ...

  8. 配置Sublime,为了Python

    E:\Sublime Text 3\Data\Packages\User\untitled.sublime-build { "cmd": ["C:\Program Fil ...

  9. C# 调用 C++ Dll 类型转换的方式 全

    摘要:C#引用C++ Dll 所有类型转换的方式         //C++中的DLL函数原型为         //extern "C" __declspec(dllexport ...

  10. Linux LVM--三种Logic Volume

    本文链接:https://blog.csdn.net/u012299594/article/details/84551722 概述 为了满足在性能和冗余等方面的需求,LVM支持了下面三种Logic V ...