缓冲区和通道

缓冲区

  • java.nio包提供了对缓冲区的支持,缓冲区是一种对象,表示存储在内存中的数据流。
  • 缓冲区常被用来提高那些读取输入和发送输出的程序的性能。它们让程序能够将大量的数据存储到内存中,这样使用和修改这些数据时速度将快很多。
  • 对于java的每种基本数据类型,都有相应的缓冲区:ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer。

继承关系

继承关系图
Buffer是顶层抽象类, ByteBuffer继承Buffer,也是抽象类.
byteBuffer最常见的两个具体实现类如下:DirectByteBuffer(JVM堆外部、通过unsafe.allocateMemory实现)、HeapByteBuffer(JVM堆)

常用函数

 int capacity() 
          返回此缓冲区的容量。 
Buffer flip() 
          反转此缓冲区。 首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标
          记,则丢弃该标记。 
 Buffer position(int newPosition) 
          设置此缓冲区的位置。 
 Buffer position(int newPosition) 
          设置此缓冲区的位置。 
int position() 
          返回此缓冲区的位置。 
 int limit() 
          返回此缓冲区的限制。 
 Buffer limit(int newLimit) 
          设置此缓冲区的限制。 
 boolean hasRemaining() 
          告知在当前位置和限制之间是否有元素。 
 int remaining() 
          返回当前位置与限制之间的元素数。 

缓冲区的常见操作

  • 存取:get()、put()等操作,这些都是会自动改变position的。
  • 翻转:也就是flip()操作。当要读取数据的时候,需要将position置0,并将limit指针指向内容的最后面,也就是position置0之前的位置。等价于这个操作:buffer.limit(buffer.position()).position(0);所以,这个操作执行两遍之后,它的position和limit是会变为0,所以这个是不能再进行读取或者写入操作的。
  • 压缩:可以释放一部分的缓冲区空间。例如可以将position之前的位置释放(position指针就是指向下次可以写的位置),调用compact,就会将position到limit之间的数据拷贝到缓冲区0位置开始,然后将移动元素的个数作为position的值(下次就可以从该位置接着写了)。
  • 清除:就是将position置0和limit=容量大小。

缓冲区的四个属性

  • 缓冲区的容量 :是它所包含的元素的数量。缓冲区的容量不能为负并且不能更改。
  • 缓冲区的限制:是第一个不应该读取或写入的元素的索引。缓冲区的限制不能为负,并且不能大于其容量。
  • 缓冲区的位置 是下一个要读取或写入的元素的索引。缓冲区的位置不能为负,并且不能大于其限制。
  • 标记(mark):一个备忘位置,调用mark()方法的话,mark值将存储当前position的值,等下次调用reset()方法时,会设定position的值为之前的标记值;

示例观察属性值变化
写一个输出函数

public static void disProperty(ByteBuffer byteBuffer){
System.out.println("position  = "+byteBuffer.position ()+
" limit  = "+byteBuffer.limit ()+" capacity  = "+byteBuffer.capacity ());
	}

1、创建一个容量为10的缓冲区

	ByteBuffer byteBuffer =ByteBuffer.allocate(10);

此时:mark = -1; position = 0; limit = 10; capacity = 10;
结果
2、往缓冲区输入5个元素

		byteBuffer.put((byte) 'H');
		byteBuffer.put((byte) 'e');
		byteBuffer.put((byte) 'l');
		byteBuffer.put((byte) 'l');
		byteBuffer.put((byte) 'o');
等价于
byteBuffer.put((byte) 'H').put((byte) 'e').put((byte) 'l').put((byte) 'l').put((byte) 'o');
因为这些函数的返回值也是byteBufer

此时:mark = -1; position = 5; limit = 10; capacity = 10;
结果
3、如果缓冲区存储的数据量少于分配给他的内存空间(分配了10个字节,之存储5个字节的数据),则将数据读入到数据缓冲区后,应调用缓冲区的flip()函数,将limit指针指向内容的最后面,也就是position置0之前的位置。然后将位置设置为 0。

		byteBuffer.flip();

此时:mark = -1; position = 0; limit = 5; capacity = 10;
结果
4、读取两个元素,输出此时的position位置

	System.out.println((char)byteBuffer.get()+" "+(char)byteBuffer.get());	
	System.out.println(byteBuffer.position());

此时:mark = -1; position = 2; limit = 5; capacity = 10;
结果
5、标记此时的位置

byteBuffer.mark();

此时:mark = 2; position = 2; limit = 5; capacity = 10;
在这里插入图片描述
6、读取两个元素,恢复到之前mark的位置

	System.out.println((char)byteBuffer.get()+" "+(char)byteBuffer.get());
		byteBuffer.reset();

属性变化情况
执行完第一行代码之后:
在这里插入图片描述
执行完第二行代码:mark = 2; position = 2; limit = 5; capacity = 10;
在这里插入图片描述
7、调用compact(),释放已读数据的空间,准备重新填充缓存区

byteBuffer.compact();

此时:mark = 2; position = 3; limit = 10; capacity = 10;
在这里插入图片描述
注意观察数组中元素的变化,实际上进行了数组拷贝,抛弃了已读字节元素,保留了未读字节元素;

缓存之间的比较

这是equal的源码:

扫描二维码关注公众号,回复: 5019317 查看本文章
public boolean equals(Object ob) {
        if (this == ob)
            return true;
        if (!(ob instanceof ByteBuffer))
            return false;
        ByteBuffer that = (ByteBuffer)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;
    }

总的来说,两个缓冲区被认为相等的条件如下=
(1)两个对象类型相同。包含不同数据类型的 buffer 永远不会相等,而且 buffer绝不会等于非 buffer 对象。
(2)两个对象在当前位置和上届之间的元素相同认为是相同。
例如:

   	     int arr1[] = new int[]{1,2,3,4,5};
	     IntBuffer intBuffer1 = IntBuffer.wrap(arr1);
	     intBuffer1.position(1);
	     int arr2[] = new int[]{6,2,3,4,5};
	     IntBuffer intBuffer2 = IntBuffer.wrap(arr2);
	     intBuffer2.position(1);
	     System.out.println(intBuffer1.equals(intBuffer2));

输出值为true;

字符集

  • 字符集位于java.nio.charset包中,这些类用于在byte缓冲区和字符缓冲区之间进行数据转换。主要的类有三个,分别是:
    (1)Charset:一个Unicode字符集,其中每个字符都有不同的byte值。
    (2)Decoder:将一系列的byte值转换成一系列的字符。
    (3)Encoder:将一系列字符转换成一系列字节值。
  • 在byte缓冲区和字符缓冲区之间进行转换之前,必须创建一个Charset对象,它将字符映射到相应的byte值。
  • 要创建字符集,可调用Charset类的静态方法forName(String),并将字符编码技术的名称作为参数传递给它。常用的字符编码方式有US-ASCII,ISO-8859-1,UTF-8,UTF-16BE,UTF-16LE,UTF-16。例如Charset charset= Charset.forName("ISO-8859-1");
  • 有了字符集对象之后,便可以使用它来创建编码器和解码器,要创建编码器(CharsetDecoder)和解码器(CharsetEncode),可调用字符集对象的方法newDecoder(),newEncoder()。例如CharsetDecoder charsetDecoder =charset.newDecoder(); CharsetEncoder charsetEncoder =charset.newEncoder();
  • 要将byte缓冲区转换为字符缓冲区,可调用解码器的decode(ByteBuffer)方法,该方法返回一个CharBuffer,其中包含转换得到的字符。
  • 要将字符缓冲区转换成byte缓冲区,可调用编码器的encode(CharBuffer)方法。该方法返回一个ByteBuffer,其中包含转换得到的byte值。
    下面的语句使用ISO-8859-1字符集将一个名为byteBuffer的byte缓冲区转换成一个字符集。
	Charset charset =Charset.forName("ISO-8859-1");
	CharsetDecoder decoder =charset.newDecoder();
	byteBuffer.position(0);
	CharBuffer chBuffer =decoder.decode(byteBuffer);

warning: 使用解码器来创建字符缓冲区之前,调用position()将byteBuffer的当前位置重置为缓冲区的开头,否则得到的缓冲区的内容可能比期望的少。

通道

  • 缓冲区常与输入输出流关联起来,我们可以使用输入流中的数据来填充缓冲区,或将缓冲区的数据写入到输出流。要实现这种操作,必须使用通道:一种将缓冲区和流连接起来的对象,通道类位于java.nio.channels包中。
  • 通道主要分为两大类,文件(File)通道和套接字(socket)通道,涉及的类有FileChannel类和三个socket通道类:SocketChannel、ServerSocketChannel和DatagramChannel;

FileChannel

  • FileChannel通道只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel( )方法来获取。
    例子为从(UTF-8编码的)文件读取到字节流,并转换成字符的程序
package ChannelDemo;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

public class ChannelDemo {
	private static String FilePath ="C:/Users/asus-pc/Desktop/javaProject/Demo/src/ChannelDemo/test.txt";
	private static FileInputStream fileInputStream =null;
	private static FileChannel  fileChannel =null;
	public static void main(String[] args) {
	try {
		fileInputStream =new FileInputStream(FilePath);
		fileChannel = fileInputStream.getChannel();
		long size =fileChannel.size();
		ByteBuffer byteBuffer =ByteBuffer.allocate((int)size);
		fileChannel.read(byteBuffer);
		byteBuffer.position(0);
		System.out.println("未编码前");
		while(byteBuffer.hasRemaining())
			System.out.print((char)byteBuffer.get());
		byteBuffer.position(0);
		Charset charset =Charset.forName("UTF-8");
		CharsetDecoder charsetDecoder =charset.newDecoder();
		CharBuffer charBuffer =charsetDecoder.decode(byteBuffer);
		System.out.println();
		charBuffer.position(1);
		System.out.println("UTF-8编码后");
		while(charBuffer.hasRemaining())
			System.out.print(charBuffer.get());
	} catch (FileNotFoundException e) {
		
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}
	}
}

猜你喜欢

转载自blog.csdn.net/kuangpeng1956/article/details/84497411
今日推荐