Previously on OOP:
To handle run time Exception, it is possible to solve the Exception through try-catch blocks, or to propagate it along the hierarchy of invocation. The parent class of all run time Exception is called Exception. We can define a new kind of Exception, which is a subclass of Exception.
在学习C语言编程和本课程的理论部分的时候,输入和输出又能分成两种类型,一种是在standard input / output上,另外一种是在file中。在都灵理工开设的《操作系统》课程中,本黄鸭曾经学习到standard input / output在Linux系统中也是一种file,而且file reference是固定的。
不管怎么样,输入输出都和文件脱不了关系。下面是an example of input file,被放在FirstExample directory中。
接下来,我们会分别使用block-based和byte-based两种方法来读取input.txt文件,再把文件的内容写入到output.txt文件中。output.txt文件是一个放在FirstExample directory中的新建文件,里面没有内容。
此外,我们还要分别统计两种方法的运行时间,以此来比较两种方法的优劣。
先分析一下结论:
Byte-based approach花费的时间比block-based approach长很多。所以对于(比较短的)file来说,使用byte-based approach读写更加高效。
再看一下代码:
main函数的声明中有“throws IOException”,这是什么鬼?正因为main函数是程序执行起来第一个被调用的函数,所以位于hierarchy of invocation的顶端,凡是Exception propagates到main函数还找不到handler的,那么只好认为没有handler。所以说,理论上main函数是没有办法propagate Exception出去的。
Block-based和byte-based的读写文件的方法分别是用copyFileBlock()和copyFileByte()函数来实现的。main函数的主要任务是统计时间。
在C语言编程中,文件操作分为下面的五个步骤。一般地,只有第四个步骤是可以省略的,其他步骤缺一不可。
(1)创建pointer to file,并且尝试打开文件
(2)判定文件是不是被成功打开了,即pointer to file不是空
(3)使用各种各样的函数,比如fcanf,来读取文件内容
(4)把程序的输出写到文件中
(5)关闭文件。之所以必须关闭文件是因为I/O线程在操作系统中可能被delay;对于开发者来说内容已经写入文件了,但是写入线程可能还在排队。为了保证buffer的内容全部写入secondary memory,避免output file内容的丢失,强烈建议一定要在程序最后关闭文件。
在Java编程中,也基本上遵循上述的五个步骤,但也不是完全相同。比如,在第一步中,我们要选择用合适的I/O class来打开文件。这种选择是基于读写文件的类型的,也就是说,the first point is to understand what kind of file we are copying。如果是characters类型的file,那么使用reader / writter;如果是bytes类型的file,那么使用InputStream/ OutputStream比较多;而txt file, encoding in unknown, etc类型的文件也有适合他们的I/O class。
在第二步中,可以用if条件句来判定文件指针非空,即文件已经被成功地打开了;也可以看一看有没有IOException。
在copyFileByte()函数中,我们想要采用的办法是read one byte at a time, and write one byte at a time。下面是代码:
读取文件使用的类是FileInputStream,写入文件使用的类是FileOutputStream,file的object references分别是fis and fos。当read()函数读到的byte不是“-1”时,用write()函数把这个byte写到输出文件里面。如果函数读到的byte是“-1”,说明文件到这里就结束了,那么读写文件的循环也应该结束。
*InputStream类中的read方法:
在copyFileBlock()函数中,我们想要采用的办法是read one block at a time, and write one block at a time。下面是代码:
读取和写入文件类不变,仍然是FileInputStream和FileOutputStream。接下来创建一个叫做buffer的数组,里面存放的数据类型是“byte”,大小是4096个。buffer数组的大小,就是block的大小。copyFileBlock()执行所需要的时间和block的大小有千丝万缕的关系,那么,block的大小应该定多少能使得copyFileBlock()的执行时间最短呢?这是一个复杂的mathematics + optimization的问题。现在,为了让copyFileBlock()和copyFileByte()有所区别,我们最好不要把block的大小设得接近于1。
本方案中采用的循环不是当循环,而是直到循环。两者的区别在于循环体的最少执行次数,前者是次,后者是1次。在前段代码中,当读取到的byte的值是“-1”的时候,文件结束,退出循环结构。在本段代码中,也是读到文件的结束,才退出循环结构。所以,循环的种类不影响读取数据的功能。
还需要注意的一点是,在write()函数中,最后一个参数是buffer数组中有效元素的个数,这个数不一定全部都是4096。当读取到文件的最后一段,可能只有前面96个元素是有数据的,后面4000个元素都没有数据。在这种情况下,read()函数的返回值,即读到的buffer数组中的有效字节总数,就非常有用了。这个返回值是必须要在write()函数中用到的。
在copyFileBlock()或者copyFileByte()执行结束以后,output.txt会和input.txt的内容完全相同。
欢迎使用本黄鸭编写的小程序~
微信公众号二维码:
领取专属 10元无门槛券
私享最新 技术干货