IO 模型
传统 IO读写
磁盘IO主要的延时是由(以15000rpm硬盘为例): 机械转动延时(机械磁盘的主要性能瓶颈,平均为2ms) + 寻址延时(2~3ms) + 块传输延时(一般4k每块,40m/s的传输速度,延时一般为0.1ms) 决定。(平均为5ms)
/** * 传统的IO读取 * @param oriStr * @param destStr */ public void customRead(String oriStr,String destStr){ FileInputStream in = null; FileOutputStream os = null; try { in = new FileInputStream(oriStr); os = new FileOutputStream(destStr); //一次读取1K 数据 byte[] bytes = new byte[1024]; while(in.read(bytes)!=-1){ os.write(bytes); } } catch (Exception e) { e.printStackTrace(); }finally { try { in.close(); os.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 带缓冲区的IO读取 * @param oriStr * @param destStr */ public void bufferRead(String oriStr,String destStr){ BufferedInputStream in = null; BufferedOutputStream os = null; try { in = new BufferedInputStream(new FileInputStream(oriStr)); os = new BufferedOutputStream(new FileOutputStream(destStr)); //一次读取1K 数据 byte[] bytes = new byte[1024]; while(in.read(bytes)!=-1){ os.write(bytes); } } catch (Exception e) { e.printStackTrace(); }finally { try { in.close(); os.close(); } catch (IOException e) { e.printStackTrace(); } } }
QA 很多人看了上述2段代码后,可能要反驳啦,customRead有缓存啊,不是一次性读取1024个字节到byte数组了吗
QA bufferRead 为啥一定比customeRead 速度快
其实要理解上面2个问题,很简单,
①:fileInputStream.read(byte[]) 是一个骗人的方法,他是通过循环操作调用read()方法,每次读取一个字节,然后填满byte数组。
②:BufferedInputStream.read(byte[]) 他是预先从磁盘读取最大8K的数据,然后保存在本地的一个叫buf 数组中,后面读取数据的时候直接从这个buf中获取
fileInputStream 每次从磁盘获取数据,BufferedInputStream 每次才从本地缓存拿数据,一目了然,BufferedInputStream 明显快多啦(源码不是很复杂,有兴趣的朋友可以看看源码就知道啦)
网络IO
网络IO主要延时由: 服务器响应延时 + 带宽限制 + 网络延时 + 跳转路由延时 + 本地接收延时 决定。(一般为几十到几千毫秒,受环境干扰极大),考虑到网络IO瓶颈,就有很多传输模型,BIO,NIO等等 在这些模型中就不得不提 ByteBuffer,
那ByteBuffer 是啥,其实这个东西经常用在网络传输NIO模型上,网络传输为了提高效率,不可能一个一个在磁盘上读取,那么需要读取一块数据存储到内存里,ByteBuffer(ByteBuffer内部维护一个byte[],是不是很熟悉,这个套路)就是应用缓存,负责在内存中保存读取的那一整块数据
//伪代码
public void nioRead() { FileChannel channel=null; try{ RandomAccessFile raf=new RandomAccessFile("src/exp_io.txt","rw"); //获取通道 channel=raf.getChannel(); if(0==raf.length()){ return; } //分配缓存空间 ByteBuffer buffer=ByteBuffer.allocate((int)raf.length()); byte [] bytes=new byte[(int)raf.length()]; //将数据写入缓存 int bytesRead=channel.read(buffer); int i=0; while (bytesRead!=-1){ buffer.flip(); while (buffer.hasRemaining()){ //从缓存读取数据 bytes[i]=buffer.get(); i++; } //将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面 //现在向缓存中写入数据时不会覆盖未读的数据 buffer.compact(); bytesRead=channel.read(buffer); } System.out.println(new String(bytes)); }catch (Exception e){ }finally { try{ if (channel!=null){ channel.close(); } }catch (Exception e){ } } }
总结
java 为提高IO读写的效率,就一个办法,想办法一次性多读取数据,然后缓存起来。其实ByteBuffer 和BufferedInputStream的根本方法都是一致的,就是搞一个本地的应用缓存,来解决IO瓶颈。
考虑一个问题 ByteBuffer 是堆内分配内存,如果,我直接用堆外内存(减少系统缓存copy到应用缓存中)作为应用缓存,那么能不能提高IO瓶颈。(netty的套路,有兴趣大家可以研究一下哈)
参考资料
网络IO和磁盘IO详解 https://www.cnblogs.com/sunsky303/p/8962628.html
BufferByte用法小结 https://blog.csdn.net/mrliuzhao/article/details/89453082