Java IO--Buffer

一、概述

我们在之前的文章中介绍输入输出流的时候提到过,输入流InputStream的read方法从输入流中读取数据的时候,如果数据源中没有数据,那么这个方法会阻塞。输出流OutputStream的write方法在写入数据时同样也会阻塞,也就是之前介绍的输入、输出流都是阻塞式的。不仅如此,传统的输入、输出流都是通过字节的移动来处理的,也就是说面向流的输入输出每次只能处理一个字节,因此面向流的输入输出体系通常效率不高。

Java从1.4版本开始,提供了一系列改进输入输出流的类,这些类都存放在Java.nio包下,新IO和传统IO有相同的目的,都是用于处理输入输出,但是新的IO使用了不同的方式来处理输入输出,通过使用内存的方式来加快数据处理速度,并且提供了更加丰富的API以供使用者操作。

二、概念模型

Buffer使用内存映射的方式来处理输入输出,Buffer将文件或者文件的一部分映射到内存中,这样就可以像访问内存一样访问文件了,通过这种方式访问文件要快很多,所以传统的输入输出是面向流的处理,那么新的输入输出则是面向"块"的处理。

Buffer可以理解成一个容器,发送到channel或者从channel中读取数据都需要先经过buffer进行处理,此处的buffer类似于一个缓冲器,既可以多次访问,每次访问获取一点数据,也可以一次映射某"块"数据加以处理。

从内部结构上来看,buffer就是一个数组,可以保存相同类型的一组数据,它有三个比较重要的概念:容量(capacity)、界限(limit)、位置(position)。
    容量:缓冲区的容量表示buffer可以存储的最多数据量,缓冲区的容量不可能为负值,并且创建后不可修改;
    界限:可以被读取或者可被写入的最大位置;
    位置:用于标志下一个可以被读取或者写入的位置索引;

当初始化一个buffer时,capacity为buffer边界最大值,limit为capacity,position为0,当写入一段数据之后,capacity不变,limit不变,position为写入数据最大值;再次写入数据,capacity不变,limit不变,position为两次数据和的最大值。当要读取数据时,设置buffer状态为读之后,capacity不变,limit为两次写入数据最大位置,position为0;读取一部分数据之后,capacity不变,limit不变,position为读取数据最大值位置。

buffer读写数据模型如下图所示:

 

Java IO--Buffer_第1张图片

三、buffer提供的API

1、java.nio.Buffer  API

名称

返回值

功能

Buffer(int mark, int pos, int lim, int cap)

构造方法,包私有,用户不能使用,一般由子类调用,并设置相关参数

position(int newPosition)

Buffer

重新设置position位置,如果Mark大于原position位置,则Mark置-1

limit(int newLimit)

Buffer

重新设置limit位置,如果position大于limit位置,则设置position为limit,如果Mark大于limit,则设置Mark为-1

mark()

Buffer

设置Mark的位置为position

reset()

Buffer

重置position位置为Mark位置

clear()

Buffer

重置buffer,position置0,limit置为capacity,Mark置-1

flip()

Buffer

切换buffer为读模式,limit置为position位置,position置0,Mark置为-1

rewind()

Buffer

重新读取buffer中的数据,position置0,limit不变,Mark置-1

remaining()

int

buffer可读取或者可写入的元素个数,大小为limit - position

isDirect()

boolean

判断该buffer是否为直接内存

 

2、java.nio.CharBuffer  API

名称

返回值

功能

CharBuffer(int mark, int pos, int lim, int cap, char[] hb, int offset)

构造方法,包私有方法,不能由用户调用,只能由子类构造

allocate(int capacity)

CharBuffer

创建固定大小的buffer

wrap(char[] array,  int offset, int length)

CharBuffer

创建buffer

read(CharBuffer target)

int

读取buffer内容到新buffer中

slice()

CharBuffer

用于创建一个共享了原始缓冲区子序列的新缓冲区。新缓冲区的position值是0,而其limit和capacity的值都等于原始缓冲区的limit和position的差值。slice()方法将新缓冲区数组的offset值设置为原始缓冲区的position值,然而,在新缓冲区上调用array()方法还是会返回整个数组。

duplicate()

CharBuffer

用于创建一个与原始缓冲区共享内容的新缓冲区。新缓冲区的position,limit,mark和capacity都初始化为原始缓冲区的索引值,然而,它们的这些值是相互独立的。

asReadOnlyBuffer()

CharBuffer

创建只读缓冲区

get(char[] dst, int offset, int length)

CharBuffer

从offset开始,获取length长度的数据到char数组中

put(char[] src, int offset, int length)

CharBuffer

从offset开始,在char数组中获取length长度数据到buffer中

compact()

CharBuffer

丢弃已经释放的数据,保留未释放数据,使缓冲区为重新填充内容做准备

四、buffer继承体系
    Buffer类的继承体系如下图所示:
    Java IO--Buffer_第2张图片

Buffer是最底层抽象类,定义了buffer的基本功能,capacity、position、limit及Mark的定义和操作,它的直接子类定义了buffer的存储类型及实际存储地址,每个基本数据类型对应一个buffer子类,例如CharBuffer定义了存储地址为char[],存储类型为char,还定义了char数组的创建、获取、读取等操作。

buffer的应用类按照内存划分可以分为两类,堆内存类和非堆内存类,以Heap开始的类为堆内存类,例如HeapCharBuffer,非Heap开头的类称为非堆内存类也叫作直接内存存储类,例如DirecByteBuffer,如名称一致,它的数据存储在堆外。

五、应用
    1、缓冲区使用步骤
    使用缓冲区一般需要遵循以下几个步骤
    1)创建缓冲区
    2)写入数据到缓冲区
    3)调用flip方法将缓冲区转换为读状态
    4)从缓冲区读取数据
    下面是一个使用Buffer的例子;

public class BufferTest {

	public static void main(String[] args) {
		//init
		CharBuffer buffer = CharBuffer.allocate(6);
		//write
		buffer.put('a');
		buffer.put('b');
		buffer.put('c');
		//transfer status prepare to read
		buffer.flip();
		System.out.println(buffer);
		//read
		char firstChar = buffer.get();
		System.out.println(firstChar);
		//clear
		buffer.clear();
	}
}


    5)释放或重置缓冲区
    2、创建缓冲区
    创建缓冲区主要有两种方式,调用allocate方法或者调用wrap方法,我们一CharBuffer为例说明:
    1)调用allocate方法实际上会返回一个HeapCharBuffer类,这个方法会把数据存储在堆中,源码如下:

    public static CharBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapCharBuffer(capacity, capacity);
    }
    HeapCharBuffer(int cap, int lim) {            // package-private

        super(-1, 0, lim, cap, new char[cap], 0);

    }
    CharBuffer(int mark, int pos, int lim, int cap,   // package-private
                 char[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }
    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }


    CharBuffer不仅可以分配堆空间,也可以分配直接内存,例如调用allocateDirect方法可以分配直接内存,其内部是通过 unsafe.allocateMemoryfangfa实现直接内存分配的,如果使用此方法,则hasArray方法会返回false.
    2)调用wrap方法实际上也会返回HeapCharBuffer类,这里的内存空间大小是以char数组定义的,它的重载方法可以缓冲区初始化的时候设置position和limit位置。如下代码会创建一个capacity=10、limit=8,position=3的Buffer。

        char[] myArray = new char[10];
        CharBuffer charbuffer = CharBuffer.wrap (myArray, 3, 5);


    3、复制缓冲区
    缓存区复制有三种方式,调用duplicate方法、调用asReadOnlyBuffer方法、调用slice方法。
    调用duplicate方法会创建缓冲区的一个拷贝,不是深拷贝,是浅拷贝,也就是会创建原缓冲区的一个引用,缓冲区的capacity、limit、position各自独立,但是数据共享,修改一个缓冲区的元素会影响另一个缓冲区,但是读取位置不会影响,如下代码所示:

public class BufferTest {

	public static void main(String[] args) {
		//init
		CharBuffer buffer1 = CharBuffer.allocate(6);
		CharBuffer buffer2 = buffer1.duplicate();
		//write
		buffer1.put('a');
		buffer1.put('b');
		buffer1.put('c');
		//transfer status prepare to read
		buffer1.flip();
		System.out.println("buffer1.out:" + buffer1);
		System.out.println("buffer2.out:" + buffer2);
		//read
		char firstChar = buffer1.get();
		System.out.println("buffer1.get.data:" + firstChar);
		System.out.println("buffer1.out:" + buffer1);
		System.out.println("buffer2.out:" + buffer2);
		buffer1.clear();
	}
}

执行结果如下图所示:

Java IO--Buffer_第3张图片
    调用asReadOnlyBuffer方法会产生一个只读缓冲区,与duplicate方法一致,唯一的区别是这个缓冲区是只读的,如果进行写入操作,会抛出异常。代码如下图所示:

public class BufferTest {

	public static void main(String[] args) {
		// init
		CharBuffer buffer1 = CharBuffer.allocate(6);
		CharBuffer buffer2 = buffer1.asReadOnlyBuffer();
		// write
		buffer1.put('a');
		buffer1.put('b');
		buffer1.put('c');
		// transfer status prepare to read
		buffer1.flip();
		System.out.println("buffer1.out:" + buffer1);
		System.out.println("buffer2.out:" + buffer2);
		// read
		buffer1.put('d');
		buffer2.put('e');
		System.out.println("buffer1.out:" + buffer1);
		System.out.println("buffer2.out:" + buffer2);
		buffer1.clear();
		buffer2.clear();
	}
}

执行结果如下图所示:

Java IO--Buffer_第4张图片
    调用slice方法相当于对原缓冲区进行了分割,该方法创建一个新的缓冲区,新建缓冲区会议原缓冲区的position为起始点,以limit为结束,使用剩余元素数量作为新缓冲区的容量,该缓冲区与原始缓冲区共享数据。
    4、读写缓冲区
    写入数据到缓冲区有两种方式,从Channel写数据到buffer中,或者调用buffer的put方法写入数据。
    读取缓冲区的数据也有两种方式,使用channel直接读取buffer中的数据,或者调用buffer的个头方法。
    5、缓冲区比较
    当满足下列条件时,两个buffer相等:
    1)有相同的类型;
    2)buffer中剩余元素个数相同;
    3)所有数据从开始到结束依次相同;
    equals源码如下:

    public boolean equals(Object ob) {
        if (this == ob)
            return true;
        if (!(ob instanceof CharBuffer))
            return false;
        CharBuffer that = (CharBuffer)ob;
        if (this.remaining() != that.remaining())
            return false;
        int p = this.position();
        for (int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j--)
            if (!equals(this.get(i), that.get(j)))
                return false;
        return true;
    }

 

你可能感兴趣的:(Java,网络知识,Java--IO)