NIO简介、缓冲区与Buffer

NIO简介

        NIO 提供了一个全新的底层 I/O 模型。与最初的 java.io 包中面向流的概念不同, NIO 中采用面向块的概念。这意味着在尽可能的情况下, I/O 操作以大的数据块为单位进行,而不是一次一个字节或者字符进行。采用这样的操作方式 Java 的 I/O 性能已有很大的提高。当然这样做也牺牲了 Java 操作的简单性。

        NIO 中提供了与平台无关的非阻塞 I/O 。与面向线程的、阻塞式 I/O 方式相比,多道通信、非阻塞 I/O 技术可以使应用程序更有效地处理大量连接的情况。

        IO 的阻塞操作: 在之前的 IO 操作中接收键盘数据的操作时只要执行到 readLine() 方法,程序就要停止而等待用户输入数据;在网络编程中服务器使用 ServerSocket 类的 accept() 方法时,服务器一直处于等待状态,等待客户端连接。这两个操作都是阻塞操作。

        新 IO 并没有在原来的 IO 基础上开发,而是采用了全新的类和接口,除了原有的功能之外还提供了如下新的特性:

  • 多路选择的非封锁式 I/O 设施。
  • 支持文件锁和内存映射。
  • 支持正则表达式的模式匹配设施。
  • 字符集编码器和译码器。

       在 Java 新 IO 中使用 Buffer 和 Channel 支持以上的操作。

缓冲区与 Buffer

       在基本 IO 操作中所有的操作都是直接以流的形式完成的,而在 NIO 中所有的操作都要使用到缓冲区处理,且所有的读写操作都是通过缓冲区完成的。缓冲区是一个线性的、有序的数据集,只能容纳某种特定的数据类型。

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成 NIO Buffer 对象,并且提供了一组方法,用来方便的访问这块内存。

       Buffer 的基本操作

       java.nio.Buffer 本身是一个抽象类,常用方法如下:

public final int capacity()    // 此缓冲的容量
public final int limit()       // 此缓冲的限制
public final Buffer limit(int newLimit)     // 设置此缓冲的限制
public final int position()    // 缓冲区的操作位置
public final Buffer position(int newPosition)     // 设置缓冲区的操作位置
public final Buffer clear()    // 清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记
public final Buffer flip()     // 将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。
public final Buffer reset()    // 将此缓冲区的位置重置为以前标记的位置
public final int remaining()   // 返回当前位置与限制之间的元素数

       在新 IO 中针对每一种基本数据类型都有一种对应的缓冲区操作类,如下:

       ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer

       以上的 7 种数据缓冲操作类,都提供了如下的常用方法:

public static 缓冲区类型 allocate(int capacity)    // 分配缓冲区空间
public abstract 基本数据类型 get()				   // 取得当前位置的内存
public abstract 缓冲区类型 put(基本数据类型 x)     // 写入指定数据类型的数据
public final 缓冲区类型 put(基本数据类型[] src)    // 写入一组指定的基本数据类型的数据
public 缓冲区类型 put(基本数据类型[] src, int offset, int length)
public abstract 缓冲区类型 slice()				   // 创建子缓冲区,其中一部分与原缓冲区共享数据
public abstract 缓冲区类型 asReadOnlyBuffer()      // 将缓冲区设置为只读缓冲区
       下面以 IntBuffer 类为例演示缓冲区的操作流程,同时观察 position、limit 和 capacity
public class IntBufferDemo {
	public static void main(String[] args) {
		// 开辟缓冲区
		IntBuffer buf = IntBuffer.allocate(10);
		System.out.println("写入数据之前的position、limit 和 capcity:");
		System.out.println("position : " + buf.position() + " ------ limit : "
				+ buf.limit() + " ------ capacity : " + buf.capacity());
		int[] temp = {1, 3, 5, 7};
		// 写入数据到
		buf.put(temp);
		System.out.println("写入数据之后的position、limit 和 capcity:");
		System.out.println("position : " + buf.position() + " ------ limit : "
				+ buf.limit() + " ------ capacity : " + buf.capacity());
		// 重设缓冲区
		buf.flip();
		System.out.println("准备输出数据时的position、limit 和 capcity:");
		System.out.println("position : " + buf.position() + " ------ limit : "
				+ buf.limit() + " ------ capacity : " + buf.capacity());
		// 从缓冲区中读取数据
		System.out.println("缓冲区中的内容:");
		while (buf.hasRemaining()) {
			System.out.print(buf.get() + "\t");
		}
	}
}

       运行程序观察到:


       注意:如果只开辟了3个缓冲区,但是写4个数据,则操作中就会出现以下错误:

Exception in thread "main" java.nio.BufferOverflowException
	...

       深入理解缓冲区操作

       在 Buffer 中存在一系列状态变量,这些变量随着写入或读取都有可能被改变,在缓冲区中可以使用 3 个值表示缓冲区的状态。

  • position : 当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1。 
    当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
  • limit : 在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position
  • capacity : 作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
       下面将逐步分析上面例子的操作步骤:


       创建子缓冲区

       可以使用各个缓冲区类的 slice() 方法从一个缓冲区中创建一个新的缓冲区,子缓冲与原缓冲区中的部分数据可以共享。下面通过例子说明:

public class IntBufferDemo2 {
	public static void main(String[] args) {
		// 开辟缓冲区
		IntBuffer buf = IntBuffer.allocate(10);
		IntBuffer sub = null;
		for (int i = 0; i < 10; i++) {
			buf.put(2 * i + 1);
		}
		buf.position(2); // 主缓冲区指针设置在地3个元素上
		buf.limit(6); // 住缓冲区 limit 为 6
		sub = buf.slice(); // 开辟自缓冲区
		for (int i = 0; i < sub.capacity(); i++) {
			int temp = sub.get(i);
			sub.put(temp - 1); // 修改自缓冲区中的东西
		}
		buf.flip(); // 重设缓冲区
		buf.limit(buf.capacity()); // 设置 limit
		System.out.println("主缓冲区中的内容:");
		while (buf.hasRemaining()) {
			System.out.print(buf.get() + " ");
		}
	}
}
       运行结果:



       创建只读缓冲区

       如果现在要使用到缓冲区中的内容,但又不希望其内容被修改,则可以通过 asReadOnlyBuffer() 方法创建一个只读缓冲区,但是创建完毕后,此缓冲区不能变为可写状态。

public class IntBufferDemo3 {
	public static void main(String[] args) {
		IntBuffer buffer = IntBuffer.allocate(10);
		IntBuffer read = null;
		for (int i = 0; i < 10; i++) {
			buffer.put(2 * i + 1);
		}
		read = buffer.asReadOnlyBuffer();
		read.flip();
		System.out.println("缓冲区中的内容:");
		while (read.hasRemaining()) {
			System.out.print(read.get() + " ");
		}
		// read.put(19);
	}
}

       创建直接缓冲区

       在缓冲区操作类中,只有 ByteBuffer 可以创建直接缓冲区,这样 Java 虚拟机将尽最大努力直接对其执行本机的 IO 操作,也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中。

       创建直接缓冲区时使用 ByteBuffer 类定义如下方法:

public static ByteBuffer allocateDirect(int capacity)
public class ByteBufferDemo {
	public static void main(String[] args) {
		ByteBuffer buffer = ByteBuffer.allocate(10); // 开辟直接缓冲区
		byte[] temp = { 1, 3, 5, 7 };
		buffer.put(temp);
		buffer.flip();
		System.out.println("缓冲区中的内容:");
		while (buffer.hasRemaining()) {
			System.out.print(buffer.get() + " ");
		}
	}
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java中,可以使用缓冲区Buffer)来接收数据。缓冲区是一个数组,用于存储数据。Java中提供了四种类型的缓冲区:ByteBuffer、CharBuffer、ShortBuffer和IntBuffer。其中,ByteBuffer是最常用的缓冲区类型。 下面是一个简单的示例,演示如何使用ByteBuffer创建缓冲区并接收数据: ``` import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class BufferExample { public static void main(String[] args) throws Exception { SocketChannel channel = SocketChannel.open(); channel.connect(new InetSocketAddress("www.example.com", 80)); ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = channel.read(buffer); while (bytesRead != -1) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); bytesRead = channel.read(buffer); } channel.close(); } } ``` 在上面的示例中,我们首先创建了一个SocketChannel,并连接到了一个远程服务器。然后,我们创建了一个ByteBuffer对象,并分配了1024个字节的空间。接着,我们使用SocketChannel的read()方法将数据读入到缓冲区中。如果读取成功,则返回读取的字节数;如果已经到达流的末尾,则返回-1。 接下来,我们使用flip()方法将缓冲区从写模式切换到读模式。然后,我们使用hasRemaining()方法检查是否还有剩余的数据可供读取。如果有,我们使用get()方法读取数据,并将其转换为字符打印出来。最后,我们使用clear()方法清空缓冲区,并再次调用read()方法读取更多的数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值