在细讲Buffer和Channel前,我们先对几个概念梳理一下。我们在做网络编程的时候时常听到三种东西:BIO,NIO,AIO。 BIO是java中普通的IO包,jdk1.4时出现了NIO,N表时New,所以其实就是新版的IO,jdk1.7时出现了AIO,也叫NIO2.0。那这三种IO有什么区别呢?IO是同步阻塞IO,NIO是同步非阻塞IO,AIO是异步非阻塞IO。这里有几个名词,同步和异步,阻塞和非阻塞,我们举个例子:
我们在烧水,当水还没开这一段时间里,我们有两种选择,站在旁边等水烧开或者先去做别的事(比如刷个剧),如果我们选择等水烧开,就是阻塞,如果我们先去刷个剧,等会儿再来看一眼水有没有烧开,就是非阻塞。但是你会觉得,看会儿剧又要回来瞅一眼水开没开是一件很烦的事情,要是水开了能够告诉我水开了就好了,比如现在的智能家居,你尽管去刷剧,等水开了通过app给弹个消息告诉你水开了,这就是异步非阻塞。
放到我们网络编程的场景下,现在有AB两个系统,B调A系统一个接口,如果该系统是一个耗时操作(像烧水那样), BIO 模式下,B系统的当前线程就会等在那里(等水烧开),而NIO模式下,B系统的这个线程就会先去做别的事情,等会儿回来看接口有没有返回,如果返回了继续处理,没返回再去做别的事情(就跟你看会儿剧去看一眼水开没有一样)。而在AIO模式下,B系统就直接去做别的事情了(放宽心的看剧),等接口完成后,会通过一个回调接口通知B系统完成了(水烧开后通过APP给你弹一个水烧开的消息)。
这就是IO,NIO,AIO的表象区别,并且他们向前兼容,也就是NIO是有BIO的功能的,AIO也有IO,NIO的功能。
下面我们来讲Buffer和Channel,这两个东西是什么呢?为什么会有呢?我们知道BIO是基于流的,流相当于程序和文件的一个通道,如果是网络传输就是网络间的一个通道,而NIO则是基于通道和缓冲区的。BIO中通过流,将文件中的内容源源不断的流入程序,直到全部传输结束,关闭流,当程序还没有等到数据时,就停在那里等,由于从一边流入到另一边,所以流也是单向的,我们熟知的InputStream和OutputStream。而BIO中则是通过Channel和Buffer,Channel相当于一个通道,buffer在其间传输数据(所以你可以开辟很多个buffer一起运数据,也可以让一个buffer来回跑重复运数据)。
Buffer是一个抽象类,有许多子类比如ByteBuffer,IntBuffer,LongBuffer等等,我们常用的一般是ByteBuffer(毕竟数据传输的时候,我们不用去管我们运的是什么,反正都是二进制),所以这里以ByteBuffer为例:
/**
* position:缓冲区正在操作的位置,默认为0
* limit:缓冲区可用大小,写模式时,limit等于capacity,写模式切换成读模式时,limit的值会变成position的值,然后position变成0
* capacity:缓冲区的容量,最大能存放的单位
*
* flip():写模式切换到读模式limit=position,position=0。
* rewind();重新读取,与flip的区别是,limit不变,而是把position改回成0。
* clear();清空缓存,实际上并没有清楚数据,仅仅是把limit变成capacity,position变为0。
*/
@Test
public void test(){
ByteBuffer byteBuffer = ByteBuffer.allocate(300);
System.out.println("position="+byteBuffer.position());
System.out.println("limit="+byteBuffer.limit());
System.out.println("capacity="+byteBuffer.capacity());
byteBuffer.put("哈哈".getBytes());
System.out.println("position="+byteBuffer.position());
System.out.println("limit="+byteBuffer.limit());
System.out.println("capacity="+byteBuffer.capacity());
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.rewind();
byteBuffer.get(bytes);
System.out.println(new String(bytes));
System.out.println(byteBuffer.limit());
byteBuffer.limit(200);
System.out.println(byteBuffer.position());
byteBuffer.put("吸".getBytes());
System.out.println(byteBuffer.position());
byteBuffer.flip();
bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println(new String(bytes));
}
从上面的案例中我们可以看到buffer通过put和get方法进行写入和读取数据。另外还有三个比较重要的属性:position,limit,capacity。代码中的具体不再赘述。
缓冲区一般分为直接缓冲区和非直接缓冲区,我们代码中通过ByteBuffer.allocate(300)创建的缓冲区是非直接缓冲区,如果要创建直接缓冲区可以通过ByteBuffer.allocateDirect(300)来创建。
那直接缓冲区和非直接缓冲区有什么区别呢,直接缓冲区是直接将缓冲区建立在物理内存上的,而非直接缓冲区则是建立在jvm内存中然后再copy到物理内存中的。我们可以看到非直接缓冲区这样以来多了一步copy的动作,也就意味着其性能会下降,那直接缓冲区是不是就完美了呢?也不是,由于将缓存区直接放到物理内存中,当我们需要传输大文件时可能直接就内存溢出了,另一方面由于不是在我们的jvm中,当我们清理内存的时候,很可能就直接把缓存区给清掉了。下面是一组直接缓冲区,非直接缓冲区,以及BIO模式下文件读取写入的案例,以及时间分析:
/**
* 非直接缓冲区 20 21 33 27 31
* @throws IOException
*/
@Test
public void test2() throws IOException {
long startTime = System.currentTimeMillis();
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\xiaosong.yang\\Desktop\\测试图片.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\xiaosong.yang\\Desktop\\测试图片1.jpg");
FileChannel inChannel = fileInputStream.getChannel();
FileChannel outChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while((inChannel.read(byteBuffer))!=-1){
byteBuffer.flip();
outChannel.write(byteBuffer);
byteBuffer.clear();
}
inChannel.close();
outChannel.close();
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime));
}
/**
* 普通IO 22 35 36 29 33
* @throws IOException
*/
@Test
public void test3() throws IOException {
long startTime = System.currentTimeMillis();
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\xiaosong.yang\\Desktop\\测试图片.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\xiaosong.yang\\Desktop\\测试图片2.jpg");
byte[] bytes = new byte[1024];
int length = 0;
while ((length=fileInputStream.read(bytes))!=-1){
fileOutputStream.write(bytes,0,length);
fileOutputStream.flush();
}
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime));
}
/**
* 直接缓冲区 13 14 18 15 17
* @throws IOException
*/
@Test
public void test4() throws IOException {
long startTime = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("C:\\Users\\xiaosong.yang\\Desktop\\测试图片.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("C:\\Users\\xiaosong.yang\\Desktop\\测试图片 3.jpg"),StandardOpenOption.CREATE,StandardOpenOption.WRITE,StandardOpenOption.READ);
MappedByteBuffer inMappedByte = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
MappedByteBuffer outMappedByte = outChannel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
byte[] dsf = new byte[inMappedByte.limit()];
inMappedByte.get(dsf);
outMappedByte.put(dsf);
inChannel.close();
outChannel.close();
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime));
}
经过测试下来,直接缓冲区的效率最高的,而非直接缓冲区和BIO模式的速度相近,直接缓冲区的速度相当于比非直接缓冲区快了一倍。
来源:CSDN
作者:程序员老杨
链接:https://blog.csdn.net/qq_30095631/article/details/103457524