这里,不先去解释啥叫NIO,不先去说那些理论上的东西,直接先实战Buffer,后面几篇文章再详细说明一下啥是NIO。
一、缓冲区(Buffer)
在Java NIO中负责数据的存取。缓冲区就是数组,用于存储不同类型的数据
根据数据类型不同(boolean除外)了,提供了相应的缓冲区:
BytrBufferCharBufferShortBufferIntBufferLongBufferFloatBufferDoubleBuffer
上述缓缓冲区的管理方式几乎一致,通过allocate()获取缓冲区。
二、缓冲区存取数据的两个核心方法
put():存入数据到缓冲区中get():获取缓冲区中的数据
因为很多人都会混淆流,通道,缓冲区的读写方法,这里按我理解(不一定准确的解释,但是方便记),不管是通道还是流还是缓冲区,只要是从外面到里面,都是read,get,从里面到外面,write,put,比如从硬盘到内存。从缓冲区到内存(其实都是内存,但是我觉得缓冲区在外面一层),都是read,get,而反过来就都是write,put。
三、缓冲区中的四个核心属性
| 属性 | 说明 |
|---|---|
| capacity | 容量,表示缓冲区中最大存储数据 的容量,一旦声明不可改变 |
| limit | 界限,表示缓冲区中可以操作数据的大小 |
| position | 位置,表示缓冲区中正在操作数据的位置 |
| mark | 标记,标记当前position的位置,可以通过reset()恢复德奥markd的位置 |
其中
0 <= mark <= position <=limit <=capacity
四、直接缓冲区与非直接缓冲区
| 名称 | 意义 |
|---|---|
| 非直接缓冲区 | 通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中。 |
| 直接缓冲区 | 通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。 |
因为不需要再拷贝一份到JVM内存中,所以可以提高效率,但是也有确缺点,因为这部分内存JVM是不能操作的,驻留在常规的垃圾回收堆之外。
五、Buffer的几个常用方法及说明
| 方法 | 作用 |
|---|---|
| allocate() | 调用方式:ByteBuffer buf= ByteBuffer.allocate(1024);分配一个指定大小的缓冲区,这里是1024字节,我们用的最多的就是ByteBuffer |
| allocateDirect() | 创建一个直接缓冲区 |
| put() | 缓冲区初分配完大小后,开始是处于写模式,利用put存入数据到缓冲区中(相当于从内存到缓冲区,从里面到外面用put) |
| flip() | 假如我们要读取缓冲区里面的数据,那么我们需要用flip切换为读取数据的模式 |
| get() | 利用get方法读取缓冲区中的数据(这里是从缓冲区到内存,所以相当于从外到里就用get) |
| rewind() | 调用这个方法可以让position重新变为0,相当于可以重新读取数据 |
| clear() | 清空缓冲区,继续下一次读,其实这里只是把position的位置变为0,并没有真正清空缓冲区,数据是处于被遗忘的状态,此时limit也变成1024 |
| mark() | 标记当前position的位置,然后这里用reset()来恢复到mark的位置 |
| reset() | 恢复到mark标记的位置 |
| hasRemaining() | 判断缓冲区中是否还有剩余的数据 |
| remaining() | 获取缓冲区还有多少个可操作的数据(字节),从position算起 |
| isDirect() | 判断缓冲区是否是直接缓冲区,true为是,false为否 |
六、代码实例
以下代码直接拷贝到eclipse即可运行
package cn.myforever.nio;import java.nio.ByteBuffer;import org.junit.Test;/*** suibibk.com*/public class TestBuffer {//缓冲区的基本操作@Testpublic void test1() {String str = "abcde";//1、分配一个指定大小的缓冲区,这里是1024字节,,我们用的最多的就是ByteBuffer//开始的时候是写数据模式ByteBuffer buf = ByteBuffer.allocate(1024);System.out.println("---------allocate()----------");//看一下各个属性的大小,理论上来说此事capacity因该为1024字节,看ByteBuffer的构造函数可以知道,limit也为1024.因为还没有数据,所以此事和position因该是0System.out.println("capacity:"+buf.capacity());System.out.println("limit:"+buf.limit());System.out.println("position:"+buf.position());//2、利用put存入数据到缓冲区中(相当于从内存到缓冲区,从里面到外面用put)buf.put(str.getBytes());System.out.println("---------put()----------");//因为str是五个字节,说以存入后capacity应该还是1024,因为是写数据的模式,所以,limit还是1024,,position因为写了数据变成了5System.out.println("capacity:"+buf.capacity());System.out.println("limit:"+buf.limit());System.out.println("position:"+buf.position());//3、上面都是写数据,接下来读数据的话需要先切换为读取数据的模式buf.flip();System.out.println("---------flip()----------");//flip(),将缓冲区改为了读模式,position变为了0,limit还是变为了5,capacity还是1024,因为改成了读取模式System.out.println("capacity:"+buf.capacity());System.out.println("limit:"+buf.limit());System.out.println("position:"+buf.position());//4、利用get方法读取缓冲区中的数据(这里是从缓冲区到内存,所以相当于从外到里就用get)byte[] dst = new byte[buf.limit()];buf.get(dst);System.out.println("---------get()----------");//get()相当于读取数据,读取了5个,所以所以position变成了5,capacity还是1024,limit还是5System.out.println("capacity:"+buf.capacity());System.out.println("limit:"+buf.limit());System.out.println("position:"+buf.position());//5、rewind()buf.rewind();System.out.println("---------rewind()----------");//rewind()相当于让用户可以重复读数据,所以。position变回了0,capacity还是1024,limit还是5System.out.println("capacity:"+buf.capacity());System.out.println("limit:"+buf.limit());System.out.println("position:"+buf.position());//6、clear()buf.clear();System.out.println("---------clear()----------");//clear()相当于清空缓冲区,所以。position变回了0,capacity还是1024,limit变为1024System.out.println("capacity:"+buf.capacity());System.out.println("limit:"+buf.limit());System.out.println("position:"+buf.position());//6、这里就是相当于可以重新读了,用mark()来标记一下System.out.println("---------mark()----------");byte[] dst2 = new byte[buf.limit()];//先读取两个字节buf.get(dst2,0,2);System.out.println("ddst2:"+new String(dst,0,2));//此事position的位置应该是2System.out.println("postion:"+buf.position());//用mark()标记一下位置,position位置为2的位置buf.mark();//再去读俩个字节buf.get(dst2,2,2);//此时位置应该是4System.out.println("position:"+buf.position());//然后这里用reset()来恢复到mark的位置buf.reset();//此时位置应该变为2System.out.println("position:"+buf.position());//判断缓冲区中是否还有剩余的数据if(buf.hasRemaining()) {//查看还有多少个可操作的数据,因为位置变为了2,所以这里应该还有三个System.out.println(buf.remaining());}//创建一个直接缓冲区ByteBuffer buf2 = ByteBuffer.allocateDirect(1024);//用isDirect()方法来判断是直接缓冲区还是非直接缓冲区System.out.println(buf.isDirect());System.out.println(buf2.isDirect());}}
结语
上面介绍了缓冲区的一些基本规则和用法,其实可以提前说一句,在NIO中,数据的传输就是用缓冲区来,通道只是为了建立连接,形象来说,缓冲区是在通道上面跑,通道仿佛是铁路,缓冲区是火车,但是以前的流就直接是水管,直接传输信息。这里我还对流的read、write,通道的read、write,缓冲区的get、put做一个说明,很多人学到这些地方都会有点懵,到底是用read还是write。按我的记忆方法来说:只要是从外面到里面就用read,get;反之,只要是从里面到外面,就用write,put。比如
| 例子 | 说明 |
|---|---|
| 流(Stream)和程序 | 硬盘到程序(内存),也就是从外面到里面用read.从内存写到硬盘,也就是从里面到外面就用write |
| 缓冲区和程序 | 从缓冲区到程序,表示从外面到里面,用get,程序写东西到缓冲区,表示从里面到外面就用put |
| 通道和缓冲区 | 很明显,从通道到缓冲区,就是从外面到里面,用read,从缓冲区到通道,就是从里面到外面用write |
也许我的理解方式不适合大家,但是我只是给个参考而已。
