Java NIO学习笔记(一)

版权声明:本文为博主原创文章,转载请注明出处! https://blog.csdn.net/q1406689423/article/details/85245018

一直有听到人家说Java NIO这个词,只是一直也没有太深入理解,前段时间看到一本书上正好有这部分内容,看了讲解,做了一些demo,也算多少有点理解,在此记录一下。

Java的“NIO”这个词呢,可以分成两部分来理解,一部分就是N,这个N有些人解释为"new"也就是新,有些人解释为“no”是指”非阻塞“,所以NIO既可以叫"新IO",又可以叫"非阻塞IO"。Java中既然已经有lO这个类库了,为什么还要再整一个NIO呢?那肯定是因为NIO做了一些IO做不了的事情,我认为其中最重要的一点就是NIO实现了非阻塞,而不像传统IO中使用InputStream.read()会阻塞。使用ServerSocket.accept()也会阻塞。
NIO的原理,是有一个线程专门负责处理IO时间,负责分发,而不再是创建多个IO线程,那样要一直进行上下文切换,并不高效。另外它是根据事件触发的,而不是像传统方式一样的同步监听。
NIO中包含了几个重要概念:

  1. 缓冲区(Buffer)
  2. 通道(Channel)
  3. 选择器(Selector)
  4. 键(SelectionKey)

这篇里先讲缓冲区部分的内容

缓冲区是一个线性的、有序的数据集,只能用来容纳特定的数据类型。平常常用的一些基本数据类型,都预设了相对应的缓冲区类。比如

ByteBuffer:存储字节的Buffer
CharBuffer:存储字符的Buffer
ShortBuffer:存储短整型的Buffer
IntBuffer:存储整型的Buffer
LongBuffer:存储长整型的Buffer
FloatBuffer:存储单精度的Buffer
DoubleBuffer:存储双精度浮点数的Buffer

Buffer类是一个抽象类,但提供了有一些不支持继承的方法如下:

capicity():返回缓冲区的容量
limit():返回此缓冲区的限制
limit(int newLimit):设置缓冲区限制
position(int newPosition):设置缓冲区的操作位置
clear():清空缓冲区
flip():重设缓冲区、在写入之前调用,改变缓冲区指针
reset():恢复缓冲区的标记位置
hasRemaining():判断当前位置和限制之间是否有内容

继承了Buffer类的各个子类也有一些自己常用的方法

allocate(int capicity):分配缓冲区空间
get():取得当前位置的内容
get(int index):获得指定位置的内容
put(基本数据类型 x):写入制定基本数据类型的数据
put(数据类型[]src):写入一组指定的基本数据类型的数据
put(数据类型[]src,int offset,int length):写入一组指定的基本数据类型的数据
slice():创建子缓冲区,其中一部分与原缓冲区共享数据
asReadOnlyBuffer():将缓冲区设置为只读缓冲区

以上这些方法都是在子类中定义的,并不是实现了父类Buffer类中的抽象方法,所以这些方法只能在子类对象中使用,而不能用多态的方式调用。
下面用个例子做一下演示

public static void main(String[] args) {
		ByteBuffer buffer = ByteBuffer.allocateDirect(10);// 分配大小为10的缓冲区
		byte[] temp = { 1, 2, 5, 4, 3 };// 定义一组数据用来存储到缓冲区
		System.out
				.println("存入数据之前,capacity:" + buffer.capacity() + "、position:"
						+ buffer.position() + "、limit:" + buffer.limit());
		buffer.put(temp);// 将数据存入缓冲区
		System.out
				.println("存入数据之后,capacity:" + buffer.capacity() + "、position:"
						+ buffer.position() + "、limit:" + buffer.limit());
		buffer.flip();// 重设缓冲区
		System.out
				.println("重设缓冲区之后,capacity:" + buffer.capacity() + "、position:"
						+ buffer.position() + "、limit:" + buffer.limit());
		System.out.print("缓冲区的内容:");
		while (buffer.hasRemaining()) {// 缓冲区内仍有数据
			System.out.print(buffer.get() + "、");// 取出缓冲区内数据
		}
	}

返回的结果为

存入数据之前,capacity:10、position:0、limit:10
存入数据之后,capacity:10、position:5、limit:10
重设缓冲区之后,capacity:10、position:0、limit:5
缓冲区的内容:1、2、5、4、3、

通过以上的代码基本就可以知道Buffer中三个状态的含义了,capacity代表的就是缓冲区大小,position就是再插入数据时候的位置,limit代表的是还有多少的空间可以用来存储数据。
还可以测试一下创建一个子Buffer

	public static void main(String[] args) {
		IntBuffer buff = IntBuffer.allocate(10);
		IntBuffer sub = null;
		for (int i = 0; i < 10; i++) {
			buff.put(2 * i + 1);
		}
		buff.position(2);// 主缓冲区指针设置到第3个上
		buff.limit(6);// 主缓冲区limit为6

		sub = buff.slice();// 开辟子缓冲区
		for (int i = 0; i < sub.capacity(); i++) {
			int temp = sub.get(i);// 根据下标获得数据
			sub.put(temp - 1);// 修改子缓存区的数据
		}
		buff.flip();// 重设缓冲区
		buff.limit(buff.capacity());
		System.out.print("主缓冲区中的内容:");
		while (buff.hasRemaining()) {
			System.out.print(buff.get() + "、");
		}
	}

结果是

主缓冲区中的内容:1、3、4、6、8、10、13、15、17、19、

我们看代码就可以知道,在第一个for之后,Buffer中存放的数据应该是“1、3、5、7、…”这样的奇数数列,在之后对position和limit进行了重新设定,在开辟子Buffer之后,通过for循环进行修改时,就只能修改从第3到第6个的范围,这也就是最后输出结果从第3到第6为偶数的原因。
我们再来看asReadOnlyBuffer()这个方法,还是先看看测试的代码和效果

public static void main(String[] args) {
		IntBuffer buff = IntBuffer.allocate(10);
		IntBuffer read = null;
		for (int i = 0; i < 10; i++) {
			buff.put(2 * i + 1);
		}
		read = buff.asReadOnlyBuffer();// 设置缓冲区为只读模式
		read.flip();// 重置缓冲区
		read.put(30);
		System.out.print("缓冲区中的内容:");
		while (read.hasRemaining()) {
			System.out.print(read.get() + "、");
		}
	}

结果是什么呢?

Exception in thread "main" java.nio.ReadOnlyBufferException
	at java.nio.HeapIntBufferR.put(Unknown Source)
	at com.nio.IntBufferDemo3.main(IntBufferDemo3.java:14)

是报错了,为什么呢?就是因为将Buffer设置为了只读,再这种情况下在使用put向缓冲区内放入东西就会抛出这样的异常。

需要注意的是,在所有这些缓冲区类之中,只有ByteBuffer这个类是可以创建直接缓冲区的,所谓直接缓冲区,就是不需要创建其他缓冲区。也就是说其他一些Buffer类都是要通过ByteBuffer这个缓冲区作为中间缓冲区来实现的,这和字符流最终也是要靠字节流形式来传输数据是一样的道理。

Buffer部分就写到这里。

猜你喜欢

转载自blog.csdn.net/q1406689423/article/details/85245018