java中的输入输出都是以流的形式,包括文件读写和网络传输。
传统的IO是面向流的,每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。而且需要为每个连接创建一个线程,当并发的连接数量非常巨大时,线程所占用的栈内存和CPU线程切换的开销将非常巨大。使用NIO,不再需要为每个线程创建单独的线程,可以用一个含有限数量线程的线程池,甚至一个线程来为任意数量的连接服务。由于线程数量小于连接数量,所以每个线程进行IO操作时就不能阻塞,如果阻塞的话,有些连接就得不到处理,NIO提供了这种非阻塞的能力。
NIO是面向缓冲区的,而且不会每个连接都占用一个线程,而且NIO是双向的。NIO主要由缓存、通道、选择器组成。
1、缓存(Buffer)
缓冲区在NIO中主要负责数据的存取,可以把它理解为需要被传输的数据的容器。缓冲区的底层是数组,用于存储不同类型的数据,根据数据类型的不同,分为七种相应类型的缓冲区。ByteBuffer、IntBuffer、LongBuffer、shortBuffer、CharBuffer、FloatBuffer、DoubleBuffer。
(1)缓冲区分别可以在直接内存和物理内存中通过相应方法获取:
- 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中;
- 直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提供效率,缺点是内存占用比较大。
直接缓冲区和非直接缓冲区可以通过方法 buffer.isDirect()判断。
(2)缓冲区存取数据的两个核心方法分别是:
put():把数据存到缓冲区中
write():把缓冲区中的数据写出来
(3)缓冲区的四个核心属性:
private int mark = -1; 标记:表示记录当前position的位置,可以通过reset()恢复到mark标记的位置,如果reset是倒退,那么中间的数据将会被遗忘。
private int position = 0; 位置:表示缓冲区中正在操作的数据的位置。必须满足:position<=limit<=capacity
private int limit; 限制:表示缓冲区中的可以操作数据的大小,(limit后面的数据不能进行读写)
private int capacity; 容量:表示缓冲区中最大存储数据的容量,一旦声明,不能改变
缓冲区有读写两种模式,缓冲区被创建出来后默认是写模式,即只能往缓冲区里写入,如果需要读的话必须使用buffer.fli()方法切换,如果没有切换就去读,则会报错。
来创建一个简单的缓冲区,并通过方法获取它的属性的值:
ByteBuffer buffer=ByteBuffer.allocate(1024);
System.out.println(" 当前指针位置"+ buffer.position());
System.out.println(" 可操作数据长度"+ buffer.limit());
System.out.println(" 总共数据长度"+ buffer.capacity());
结果如下所示:
那么此时缓冲区的结构就可以用下图表示:
然后我们再创建一个字符串,并将字符串写到缓冲区当中,然后再查看缓冲区的属性值。
String str="abcde" ;
buffer.put(str.getBytes()); //把字符串转换成byte数组形式存入缓存区
System.out.println(" 当前指针位置"+ buffer.position());
System.out.println(" 可操作数据长度"+ buffer.limit());
System.out.println(" 总共数据长度"+ buffer.capacity());
结果如下图所示:
可以看出指针位置发生了变化,而可操作数据长度和总数据长度没有变化,此时结构如下:
然后将缓冲区切换到写模式:
buffer.flip(); //切换到读取数据模式
System.out.println(" 当前指针位置"+ buffer.position());
System.out.println(" 可操作数据长度"+ buffer.limit());
System.out.println(" 总共数据长度"+ buffer.capacity());
结果:
可以看出来,因为空间中只有5个单位有数据,所以limit变为5,且指针位置为0,代表着只能对这5个数进行操作。
然后用get方法进行读取:
byte[] dst=new byte[buffer.limit()]; //存有效数据长度即可
buffer.get(dst); //对buffer进行读取,并存储到bst数组中
System.out.println(new String(dst));
打印出了最开始时候创建的字符串。