直接io和缓存io,缓冲IO和非缓冲IO的区别

1,缓冲IO和非缓冲IO的区别标准IO库提供缓冲的目的是尽可能减少使用read和write调用的次数,降低执行IO的时间,它提供三种类型的缓冲:全缓冲 。在填满标准IO缓冲区后才进行实际IO操作,对于磁盘文件通常就是全缓冲,上面的示例就是采用缓冲 。行缓冲 。在输入和输出中遇到换行符时进行实际的IO操作 , 当涉及到一个终端时,通常使用行缓冲 。使用最频繁的printf函数就是采用行缓冲,所以感觉不出缓冲的存在 。不带缓冲 。标准IO库不对字符进行缓冲存储 。标准出错流stderr通常是不带缓冲的 。你问的问题很有研究性,要处理的文件很小的时候这样的确看不出来你说的区别,缓冲区是个可以重用的内存区,这个主要是用于cpu提高读取文件的效率,而cpu的速度是远远高与内存的,假设你没有这个缓冲区的话,cpu就会要不停的读取文件,当在读取较大的文件时,这样处理效率是不是很低呢?内存速度慢,你这样一个字节一个字节的送的话是不是整体的效率都低了啊?当你有缓冲的时候cpu在等缓冲区写满时是不是可以处理其它的信息呢?
2 , 带缓存与不带缓存函数的区别以 ssize_t write(int filedes, const void *buff, size_t nbytes)和size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp)来讲讲自己对unix系统下带缓存的I/O和不带缓存的I/O的区别 。首先要清楚一个概念,所谓的代缓存并不是指上面两个函数的buff参数,而是指unix系统在内核中所设的缓冲存储器 。当将数据写到文件上时,内核先将该数据写到缓存,如果该缓存未满 , 则并不将其排入输出队列,直到缓存写满或者内核再次需要重新使用此缓存时才将其排入输入队列 , 待其到达对首,在进行实际的I/O操作,也就是此时才把数据真正写到磁盘,这种技术叫延迟写 。现在假设内核所设的缓存是100个字节,如果你使用write,且buff的size为10,当你要把9个同样的buff写到文件时 , 你需要调用9次write,也就是9次系统调用,此时也并没有写到硬盘,如果想立即写到硬盘,调用fsync , 可以进行实际的I/O操作 。标准I/O,也就是带缓存的I/O采用FILE*,FILE实际上包含了为管理流所需要的所有信息:实际I/O的文件描述符,指向流缓存的指针(标准I/O缓存,由malloc分配,又称为用户态进程空间的缓存,区别于内核所设的缓存),缓存长度,当前在缓存中的字节数,出错标志等 , 假设流缓存的长度为50字节,把以上的数据写到文件,则只需要2次系统调用(fwrite调用write系统调用),因为先把数据写到流缓存,当其满以后或者调用fflush时才填入内核缓存,所以进行了2次的系统调用write 。fflush将流所有未写的数据送入(刷新)到内核(内核缓冲区),fsync将所有内核缓冲区的数据写到文件(磁盘) 。不带缓存的read和write是相对于fread/fwrite等流函数来说明的 , 因为fread和fwrite是用户函数(3),所以他们会在用户层进行一次数据的缓存 , 而read/write是系统调用(2)所以他们在用户层是没有缓存的,所以称read和write是无缓存的IO,其实对于内核来说还是进行了缓存,不过用户层看不到罢了 。直接使用系统调用的read和write方法读写就是不带缓存的 。
3 , 谁知道IO系统性能之二缓存和RAID如何提高IO从前面的计算中我们可以看到,如果IO响应时间在10ms左右的话是很正常的 , 但是当IO响应时间比这个值超出太多的时候,你就要开始注意了,很可能就意味着此时你的磁盘系统已经成为了一个瓶颈 。IOPS 综合上面两个部分的讨论我们来估算一下阵列下的磁盘总体IOPS,在这里我们先假设组成阵列的单个磁盘的随机读写的IOPS为140,读写缓存命中率都为10% , 组成阵列的磁盘个数为4 。因为不管是那种阵列,磁盘的读取性能都是所有磁盘之和,所以可以得出下面的读取IOPS: read IOPS = disk_IOPS/(1-read_cache_hit_ratio)*disk_num = 140/(1-10%)*4 = 622 而写入性能就完全不一样了 , 根据上面的讨论我们可以得出下面结论: RAID0: 1 IO request => need 1 actual IO on diskRAID1: 1 IO request => need 2 actual IO on diskRAID5: 1 IO request => need 4 actual IO on diskRAID6: 1 IO request => need 6 actual IO on disk 由此我们也可以计算出写入IOPS估算公式: RAID0 write IOPS = disk_IOPS/(1-write_cache_hit_ratio)*disk_num/acture_IO_num = 140/(1-10%)*4/1 = 622RAID1 write IOPS = disk_IOPS/(1-write_cache_hit_ratio)*disk_num/acture_IO_num = 140/(1-10%)*4/2 = 311RAID6 write IOPS = disk_IOPS/(1-write_cache_hit_ratio)*disk_num/acture_IO_num = 140/(1-10%)*4/6 = 103 实际上从通过上面的计算方法我们还可以估算当给定一个要求的IOPS的情况下,估计下使用各个阵列级别所需要的磁盘的数量 。当然我们上面的计算方法只是一个估算,我们忽略很多其他的因素,得出的只是一个大概的数值 , 不过在实际的应用还是有一定的参考作用的 。本篇最后附送一个计算磁盘系统IOPS的网站――wmarows disk & disk array calculator,这个网站提供的计算公式还考虑了诸如阵列条带大小以及主机方面的因素 , 很有参考价值,至于怎么选择合适的条带大小,以后还会撰文解释 。【直接io和缓存io,缓冲IO和非缓冲IO的区别】
4,IO的提高缓存衡量性能的几个指标的计算中我们可以看到一个15k转速的磁盘在随机读写访问的情况下IOPS竟然只有140左右,但在实际应用中我们却能看到很多标有5000IOPS甚至更高的存储系统,有这么大IOPS的存储系统怎么来的呢?这就要归结于各种存储技术的使用了,在这些存储技术中使用最广的就是高速缓存(Cache)和磁盘冗余阵列(RAID)了,本文就将探讨缓存和磁盘阵列提高存储IO性能的方法 。在当下的各种存储产品中,按照速度从快到慢应该就是内存>闪存>磁盘>磁带了,然而速度越快也就意味着价格越高,闪存虽然说是发展势头很好 , 磁盘的速度无疑是计算机系统中最大的瓶颈了,所以在必须使用磁盘而又想提高性能的情况下,人们想出了在磁盘中嵌入一块高速的内存用来保存经常访问的数据从而提高读写效率的方法来折中的解决,这块嵌入的内存就被称为高速缓存 。说到缓存,到操作系统层,再到磁盘控制器 , 还有CPU内部 , 单个磁盘的内部也都存在缓存,所有这些缓存存在的目的都是相同的,就是提高系统执行的效率 。当然在这里我们只提跟IO性能相关的缓存,与IO性能直接相关的几个缓存分别是文件系统缓存(File SySTem Cache)、磁盘控制器缓存(Disk CONtroller Cache)和磁盘缓存(Disk Cache,也称为Disk Buffer),不过当在计算一个磁盘系统性能的时候文件系统缓存也是不会考虑在内的,我们重点考察的就是磁盘控制器缓存和磁盘缓存 。不管是控制器缓存还是磁盘缓存,他们所起的作用主要是分为三部分:缓存数据、预读(Read-ahead)和回写(Write-back) 。缓存数据首先是系统读取过的数据会被缓存在高速缓存中 , 这样下次再次需要读取相同的数据的时候就不用在访问磁盘 , 直接从缓存中取数据就可以了 。当然使用过的数据也不可能在缓存中永久保留的,缓存的数据一般那是采取LRU算法来进行管理,目的是将长时间不用的数据清除出缓存,那些经常被访问的却能一直保留在缓存中 , 直到缓存被清空 。预读预读是指采用预读算法在没有系统的IO请求的时候事先将数据从磁盘中读入到缓存中,然后在系统发出读IO请求的时候,就会实现去检查看看缓存里面是否存在要读取的数据 , 如果存在(即命中)的话就直接将结果返回,这时候的磁盘不再需要寻址、旋转等待、读取数据这一序列的操作了,这样是能节省很多时间的;如果没有命中则再发出真正的读取磁盘的命令去取所需要的数据 。缓存的命中率跟缓存的大小有很大的关系,理论上是缓存越大的话,所能缓存的数据也就越多,这样命中率也自然越高,当然缓存不可能太大,毕竟成本在那儿呢 。如果一个容量很大的存储系统配备了一个很小的读缓存的话,这时候问题会比较大的,因为小缓存缓存的数据量非常?。啾日龃娲⑾低忱此当壤浅5?,这样随机读取(数据库系统的大多数情况)的时候命中率也自然就很低,这样的缓存不但不能提高效率(因为绝大部分读IO都还要读取磁盘),反而会因为每次去匹配缓存而浪费时间 。执行读IO操作是读取数据存在于缓存中的数量与全部要读取数据的比值称为缓存命中率(Read Cache Hit Radio),假设一个存储系统在不使用缓存的情况下随机小IO读取能达到150IOPS,而它的缓存能提供10%的缓存命中率的话,那么实际上它的IOPS可以达到150/(1-10%)=166 。回写要先说一下,用于回写功能的那部分缓存被称为写缓存(Write Cache) 。在一套写缓存打开的存储中,操作系统所发出的一系列写IO命令并不会被挨个的执行,这些写IO的命令会先写入缓存中,然后再一次性的将缓存中的修改推到磁盘中,这就相当于将那些相同的多个IO合并成一个,多个连续操作的小IO合并成一个大的IO , 还有就是将多个随机的写IO变成一组连续的写IO,这样就能减少磁盘寻址等操作所消耗的时间,大大的提高磁盘写入的效率 。读缓存虽然对效率提高是很明显的,但是它所带来的问题也比较严重,因为缓存和普通内存一样,掉电以后数据会全部丢失,当操作系统发出的写IO命令写入到缓存中后即被认为是写入成功,而实际上数据是没有被真正写入磁盘的,此时如果掉电,缓存中的数据就会永远的丢失了,这个对应用来说是灾难性的,目前解决这个问题最好的方法就是给缓存配备电池了,保证存储掉电之后缓存数据能如数保存下来 。和读一样,写缓存也存在一个写缓存命中率(Write Cache Hit Radio),不过和读缓存命中情况不一样的是,尽管缓存命中,也不能将实际的IO操作免掉,只是被合并了而已 。控制器缓存和磁盘缓存除了上面的作用之外还承当着其他的作用,比如磁盘缓存有保存IO命令队列的功能,单个的磁盘一次只能处理一个IO命令,但却能接收多个IO命令,这些进入到磁盘而未被处理的命令就保存在缓存中的IO队列中 。RAID(Redundant ArrayOf Inexpensive Disks)如果你是一位数据库管理员或者经常接触服务器 , 那对RAID应该很熟悉了 , 作为最廉价的存储解决方案,RAID早已在服务器存储中得到了普及 。在RAID的各个级别中,应当以RAID10和RAID5(不过RAID5已经基本走到头了,RAID6正在崛起中 , 看看这里了解下原因)应用最广了 。下面将就RAID0 , RAID1,RAID5,RAID6,RAID10这几种级别的RAID展开说一下磁盘阵列对于磁盘性能的影响,当然在阅读下面的内容之前你必须对各个级别的RAID的结构和工作原理要熟悉才行,这样才不至于满头雾水,推荐查看wikipedia上面的如下条目:RAID,Standard RAID levels,Nested RAID levels 。RAID0将数据条带化(striping)将连续的数据分散在多个磁盘上进行存取,系统发出的IO命令(不管读IO和写IO都一样)就可以在磁盘上被并行的执行,每个磁盘单独执行自己的那一部分请求,这样的并行的IO操作能大大的增强整个存储系统的性能 。假设一个RAID0阵列有n(n>=2)个磁盘组成,每个磁盘的随机读写的IO能力都达到140的话,那么整个磁盘阵列的IO能力将是140*n 。同时如果在阵列总线的传输能力允许的话RAID0的吞吐率也将是单个磁盘的n倍 。其他RAID区域· RAID1镜像磁盘,使用2块硬盘,一般做系统盘的镜像,读IO为一块硬盘的IO,写IO为2块硬盘的IO 。RAID10既能增加IO的读写性能又能实现数据的冗余,使用盘的数量为2的倍数且要大于等于4,且硬盘空间相同,这样的缺点是要实现IO扩展就必须增加相应的硬盘数量,实现同样的性能硬盘成本要成倍增长 。允许不同硬盘数据的任何一块丢失 。RAID3拿出单独一块盘做奇偶校验盘,做到数据的冗余这种情况下允许一块硬盘损坏 。由于磁盘的任何数据发生改变都会重新对校验盘进行改写,所以过多的写操作会成为整个系统的瓶颈,此种RAID级别只能用于对读请求相对较高,写请求不多的环境 。RAID3已基本淘汰,一般用RAID5技术替代 。5,java io流的典型使用方式有几种Java中IO流分成两大类,一种是输入流,所有的输入流都直接或间接继承自InputStream抽象类,输入流作为数据的来源,我们可以通过输入流的read方法读取字节数据;另一种是输出流 , 所有的输出流都直接或间接继承自OutputStream抽象类,输出流接收数据,可以通过write方法写入字节数据 。Java的IO流类中,大部分的输入流和输出流都是成对存在的 , 即如果存在XXXInputStream,那么就存在XXXOutputStream,反之亦然 。SequenceInputStream和StringBufferInputStream是特例,没有对应的SequenceOutputStream类和StringBufferOutputStream类,许多IO操作都可能会抛出IOException异常,比如read、write、close操作 。以下是Java的IO流中常见的输入流,由于每个输入流都有其对应的输出流,所以此处就不再列出输出流的继承结构图 。1、ByteArrayInputStream & ByteArrayOutputStream: ByteArrayInputStream构造函数中需要传入一个byte数组作为数据源 , 当执行read操作时,就会从该数组中读取数据,正如其名,是一种基于字节数组实现的一种简单输入流 , 显而易见的是,如果在构造函数中传入了null作为字节数据,那么在执行read操作时就会出现NullPointerException异常,但是在构造函数初始化阶段不会抛出异常;与之相对应的是ByteArrayOutputStream,其内部也有一个字节数组用于存储write操作时写入的数据,在构造函数中可以传入一个size指定其内部的byte数组的大小 , 如果不指定,那么默认它会将byte数组初始化为32字节,当持续通过write向ByteArrayOutputStream中写入数据时,如果其内部的byte数组的剩余空间不能够存储需要写入的数据,那么那么它会通过调用内部的ensureCapacity方法对其内部维护的byte数组进行扩容以存储所有要写入的数据,所以不必担心其内部的byte数组太小导致的IndexOutOfBoundsException之类的异常 。2、FileInputStream & FileOutputStream FileInputStream 能够将文件作为数据源,读取文件中的流,通过File对象或文件路径等初始化,在其构造函数中 , 如果传入的File对象(或与其相对应的文件路径所表示的File对象)不存在或是一个目录而不是文件或者由于其他原因无法打开读取数据,都会导致在初始化阶段导致抛出FileNotFoundException异常;与FileInputStream 相对应的是FileOutputStream,可以通过FileOutputStream向文件中写入数据,也需要通过File对象或文件路径对其初始化,如同FileInputStream,如果传入的File对象(或与其相对应的文件路径所表示的File对象)是一个目录而不是文件或者由于其他原因无法创建该文件写入数据,都会导致在初始化阶段抛出FileNotFoundException异常 。3、PipedInputStream & PipedOutputStream PipedInputStream和PipedOutputStream一般是结合使用的,这两个类用于在两个线程间进行管道通信,一般在一个线程中执行PipedOutputStream 的write操作,而在另一个线程中执行PipedInputStream的read操作 。可以在构造函数中传入相关的流将PipedInputStream 和PipedOutputStream 绑定起来,也可以通过二者的connect方法将二者绑定起来,一旦二者进进行了绑定,那么PipedInputStream的read方法就会自动读取PipedOutputStream写入的数据 。PipedInputStream的read操作是阻塞式的,当执行PipedOutputStream的write操作时,PipedInputStream会在另一个线程中自动读取PipedOutputStream写入的内容 , 如果PipedOutputStream一直没有执行write操作写入数据,那么PipedInputStream的read方法会一直阻塞PipedInputStream的read方法所运行的线程直至读到数据 。单独使用PipedInputStream或单独使用PipedOutputStream时没有任何意义的,必须将二者通过connect方法(或在构造函数中传入对应的流)进行连接绑定,如果单独使用其中的某一个类,就会触发IOException: Pipe Not Connected.4、ObjectInputStream & ObjectOutputStream ObjectOutputStream具有一系列writeXXX方法,在其构造函数中可以掺入一个OutputStream,可以方便的向指定的输出流中写入基本类型数据以及String,比如writeBoolean、writeChar、writeInt、writeLong、writeFloat、writeDouble、writeCharts、writeUTF等 , 除此之外,ObjectOutputStream还具有writeObject方法 。writeObject方法中传入的类型必须实现了Serializable接口,从而在执行writeObject操作时将对象进行序列化成流,并将其写入指定的输出流中 。与ObjectOutputStream相对应的是ObjectInputStream , ObjectInputStream有与OutputStream中的writeXXX系列方法完全对应的readXXX系列方法,专门用于读取OutputStream通过writeXXX写入的数据 。5、SequenceInputStream SequenceInputStream 主要是将两个(或多个)InputStream在逻辑上合并为一个InputStream,比如在构造函数中传入两个InputStream,分别为in1和in2,那么SequenceInputStream在读取操作时会先读取in1,如果in1读取完毕,就会接着读取in2 。在我们理解了SequenceInputStream 的作用是将两个输入流合并为一个输入流之后,我们就能理解为什么不存在对应的SequenceOutputStream 类了,因为将一个输出流拆分为多个输出流是没有意义的 。6、StringBufferInputStream StringBufferInputStream允许通过在构造函数中传入字符串以读取字节,在读取时内部主要调用了String的charAt方法 。与SequenceInputStream类似,StringBufferInputStream也没有对应的OutputStream,即不存在StringBufferOutputStream类 。Java没有设计StringBufferOutputStream类的理由也很简单,我们假设StringBufferOutputStream存在,那么StringBufferOutputStream应该是内部通过执行write操作写入数据更新其内部的String对象 , 比如有可能是通过StringBuilder来实现,但是这样做毫无意义 , 因为一旦我们String的构造函数中可以直接传入字节数组构建字符串,简单明了,所以设计StringBufferOutputStream就没有太大的必要了 。StringBufferInputStream这个类本身存在一点问题,它不能很好地将字符数组转换为字节数组,所以该类被Java标记为废弃的(Deprecated),其官方推荐使用StringReader作为代替 。7、FilterInputStream & FilterOutputStream FilterInputStream包含了其他的输入流,说具体点就是在其构造函数中需要传入一个InputStream并将其保存在其名为in的字段中,FilterInputStream只是简单的覆盖了所有的方法,之所说是简单覆盖是因为在每个覆盖函数中,它只是调用内部的保存在in字段中的InputStream所对应的方法,比如在其覆盖read方法时,内部只是简单调用了in.read()方法 。FilterInputStream的子类可以进一步覆盖某些方法以保持接口不变的情况下实现某一特性(比如其子类有的可以通过使用缓存优化读取的效率)或者提供一些其他额外的实用方法 。所以在使用时FilterInputStream可以让传入的InputStream具有一些额外的特性,即对构造函数传入的InputStream进行了一层包裹,使用了典型的装饰着模式 , 如果只看FilterInputStream本身这一个类的话,则该类自己本身意义不大 , 因为其只是通过内部的字段in简单覆写某些方法 。但是如果将FilterInputStream 和其子类结合起来使用话,那么就很有用了 。比如FilterInputStream 有两个子类BufferedInputStream和DataInputStream,这两个类在下面还会详细介绍 。BufferedInputStream对read操作做了优化,每次读操作时都读取一大块数据 , 然后将其放入内部维护的一个字节数组缓冲区中 。当外面调用BufferedInputStream的read方法时,首先去该缓冲区中读取数据,这样就避免了频繁的实际的读操作,BufferedInputStream对外没有暴露额外的其他方法 , 但是其内部的read方法已经经过优化了,所以在执行读操作的时候效率更高 。DataInputStream与ObjectInputStream有点类似,可以通过一些readXXX方法读取基本类型的数据,这是非常有用的一些方法 。8、BufferedInputStream & BufferedOutputStream 如上面所介绍的那样,在BufferedInputStream的构造函数中需要传入一个InputStream,BufferedInputStream内部有一个字节数组缓冲区 , 每次执行read操作的时候就从这buf中读取数据,从buf中读取数据没有多大的开销 。如果buf中已经没有了要读取的数据,那么就去执行其内部绑定的InputStream的read方法 , 而且是一次性读取很大一块数据,以便填充满buf缓冲区 。缓冲区buf的默认大小是8192字节,也就是8K,在构造函数中我们也可以自己传入一个size指定缓冲区的大小 。由于我们在执行BufferedInputStream的read操作的时候,很多时候都是从缓冲区中读取的数据,这样就大大减少了实际执行其指定的InputStream的read操作的次数,也就提高了读取的效率 。与BufferedInputStream 相对的是BufferedOutputStream 。在BufferedOutputStream的构造函数中我们需要传入一个OutputStream , 这样就将BufferedOutputStream与该OutputStream绑定在了一起 。BufferedOutputStream内部有一个字节缓冲区buf,在执行write操作时 , 将要写入的数据先一起缓存在一起,将其存入字节缓冲区buf中 , buf是有限定大小的,默认的大小是8192字节 , 即8KB,当然也可以在构造函数中传入size指定buf的大小 。该buf只要被指定了大小之后就不会自动扩容,所以其是有限定大小的,既然有限定大小,就会有被填充完的时刻 , 当buf被填充完毕的时候会调用BufferedOutputStream的flushBuffer方法,该方法会通过调用其绑定的OutputStream的write方法将buf中的数据进行实际的写入操作并将buf的指向归零(可以看做是将buf中的数据清空) 。如果想让缓存区buf中的数据理解真的被写入OutputStream中,可以调用flush方法,flush方法内部会调用flushBuffer方法 。由于buf的存在,会大大减少实际执行OutputStream的write操作的次数,优化了写的效率 。4种 。。。。http://www.2cto.com/kf/201312/262036.html

    推荐阅读