缓冲区

1相关概念

1.1 引入

在jdk1.4提供了新的io包,引入了Buffer类(缓冲区)。从概念上来讲,缓冲区是包在一个对象内的基本数据元素数组。Buffer类相比一个简单的数组的优点是它将关于数据的内容和信息包含在一个单一的对象中。Buffer类以及它专有的子类定义了一个用于处理数据缓冲区的API。

1.2 Buffer

一个Buffer是固定数量的数据容器。对于每个非布尔类型的原始数据都有一个缓冲区类。尽管如此,缓冲区是十分倾向于处理字节。当然非字节缓冲区可以在后台从字节或者到字节的转换,这取决于缓冲区是如何建立的(大端小端问题)。
缓冲区的工作与通道紧密联系。通道是I/O传输发生的入口,而缓冲区是这些数据传输的来源或者目标

2Buffer 类的家谱

这里写图片描述
值得说明的:Buffer类的家谱中所有类都是抽象类,也是我们常用到的

2.1Buffer基类
package java.nio;

public abstract class Buffer {

    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;


    public final int capacity();
    public final Buffer position(int newPosition);
    public final int limit();
    public final Buffer limit(int newLimit);
    public final Buffer mark();
    public final Buffer reset();
    public final Buffer clear();
    public final Buffer flip();
    public final Buffer rewind();
    public final int remaining();
    public final boolean hasRemaining();
    public abstract boolean isReadOnly();
    ...
}
2.2 属性

所有的缓冲区都具有四个属性来提供其所包含的数据元素信息。具体如下:
容量(capacity):缓冲区能够容纳的数据元素的最大的数量、创建时候设定且永远不能改变。
上界(limit):缓冲区的第一个不能被读或者写的元素。
位置(position):下一个要被读或者写的元素的索引。
标记(mark):一个备忘位置,可以调用mark()来设定mark=position;调用reset()设定position=mark,调用reset之前必须先调用mark,否则会抛InvalidMarkException;缓冲区创建时候mark为-1,limit的大小和capacity一样。

public class CharBufferDemo {
    public static void main(String[] args) {
        /**
         * 创建容量为10类型为CharBuffer,由于其为抽象类,不能直接实例化.
         *
         */
        CharBuffer charBuffer = CharBuffer.allocate(10);

        System.out.println("position:"+charBuffer.position());
        System.out.println("limit:"+charBuffer.limit());
        System.out.println("capacity:"+charBuffer.capacity());

    }
}
//输出
position:0
limit:10
capacity:10

allocate 实现,可以看出实际返回HeapCharBuffer(CharBuffer子类)

    public static CharBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapCharBuffer(capacity, capacity);
    }
2.3常用的API

flip()方法将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的状态(其实实现很简单);rewind()方法是绕回也就是从头开始读;hasRemaining() 判断是否到上界了。

    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
  public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }
    public final boolean hasRemaining() {
        return position < limit;
    }
public class CharBufferDemo {
    public static void main(String[] args) {

        CharBuffer charBuffer = CharBuffer.allocate(10);
        /** 往缓冲区中写数据,可以写单个字符或者数组
         *  charBuffer.put('h')
         *  charBuffer.put('e')
         *  charBuffer.put('l')
         *  charBuffer.put('l')
         *  charBuffer.put('o')
         */
        charBuffer.put("hello".toCharArray());
        /**
         * 缓冲区翻转,如果翻转的话,position为下一个要写的位置
         */
        charBuffer.flip();
        /**
        *hasRemaining() 判断是否已经读到了上界
        */
        while (charBuffer.hasRemaining()) {
            //读取数据
            System.out.println(charBuffer.get());
        }
        /**
         * 缓冲区绕回,可以重读
         */
        charBuffer.rewind();
        System.out.println("重读");
        while (charBuffer.hasRemaining()) {
            System.out.println(charBuffer.get());
        }

    }
}

输出

值得强调的是缓冲区不是线程安全的,如果你想多线程同时存取特定的缓冲区,需要在存取缓冲区之前进行同步。

2.4 缓冲区的比较

两个缓冲区被认为是相等的充要条件:
a.两个缓冲区类型相同
b.两个对象剩余相同元素个数
c.在每一个缓冲区中应被get()方法返回的剩余数据元素序列必须一致
所有的缓冲区都提供了一个常规的equals()方法用于判断两个缓冲区是否相等,以及一个compareTo()方法,下面以Charbuffer为例,可以看出两个方法参数不一:

public abstract class CharBuffer
{
    public int compareTo(CharBuffer that)
    {}
    public boolean equals(Object ob)
    {}
}

3.ByteBuffer

3.1字节顺序

字节缓冲区有自己独特之处和其它缓冲区的明显不同在于,它们可以成为通道所执行的I/O的源头或目标。字节是操作系统及其I/O设备类型基本数据类型。当在JVM和操作系统间传递数据时候,将其他的数据类型拆分成构成他的字节是十分必要的。每一个基本数据类型都是以连续字节序列形式存放在内存中,这个出现一个存放顺序问题,也就是我们常说的大端字节顺序和小端字节顺序

3.2直接缓冲区

如果你向一个通道重传递一个非直接ByteBuffer对象用于写入,通道可能会在每次调用中隐含地进行下面的操作:
创建一个临时的直接ByteBuffer对象
将非直接缓冲区的内容复制到临时缓冲区
使用临时直接缓冲区执行低层次I/O操作
临时缓冲区对象离开作用域,最终成为被回收的无用数据
从上面可以看出这可能会导致缓冲区在每个I/O上复制并产生大量对象,这时候可以考虑使用直接缓冲区。直接缓冲区使用的内存是通过调用本地操作系统方面的代码分配的,它不被GC直接管理(但Direct Buffer的JAVA对象是归GC管理的,只要GC回收了它的JAVA对象,操作系统才会释放DirectBuffer所申请的空间)。

如果我们构造一个ByteBuffer仅仅使用一次,不复用它,那么Direct Buffer和Heap Buffer没有明显的区别。两个地方我们可能通过Direct Buffer来提高性能:
a.大文件,尽管我们Direct Buffer只用一次,但是如果内容很大,Heap Buffer的复制代价会很高,此时用Direct Buffer能提高性能。这就是为什么,当我们下载一个大文件时,服务端除了用SendFile机制,也可以用“内存映射”,把大文件映射到内存,也就是MappedByteBuffer,是一种Direct Buffer,然后把这个MappedByteBuffer直接写入SocketChannel,这样减少了数据复制,从而提高了性能。

b.重复使用的数据,比如HTTP的错误信息,例如404呀,这些信息是每次请求,响应数据都一样的,那么我们可以把这些固定的信息预先存放在Direct Buffer中(当然部分修改Direct Buffer中的信息也可以,重要的是Direct Buffer要能被重复使用),这样把Direct Buffer直接写入SocketChannel就比写入Heap Buffer要快了。
值得强调是:使用直接缓冲区或者非直接缓冲区的性能会因为JVM和OS以及代码设计而产生具大的差异。一定要保证程序的正确性,然后再想办法优化。

参考文献[1]http://eyesmore.iteye.com/blog/1133335

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值