开个话题,C\C++文件操作对比
网上多是介绍几种文件操作方式,很少见到比较两种操作的,开个话题有时间研究总结一下:
C与C++文件操作的对比,包括各自特点、效率,C++编程里面究竟应该使用stdio还是stream的文件操作?
搜集的一些材料:
1. http://www.parashift.com/c++-faq-lite/input-output.html
[15.1] Why should I use <iostream> instead of the traditional <cstdio> ?
Increase type safety, reduce errors, allow extensibility, and provide inheritability.
- More type-safe: With
<iostream> , the type of object being I/O'd is known statically by the compiler. In contrast,<cstdio> uses"%" fields to figure out the types dynamically. - Less error prone: With
<iostream> , there are no redundant"%" tokens that have to be consistent with the actual objects being I/O'd. Removing redundancy removes a class of errors. - Extensible: The C++
<iostream> mechanism allows new user-defined types to be I/O'd without breaking existing code. Imagine the chaos if everyone was simultaneously adding new incompatible"%" fields toprintf() andscanf() ?! - Inheritable: The C++
<iostream> mechanism is built from real classes such asstd::ostream andstd::istream . Unlike<cstdio> 'sFILE* , these are real classes and hence inheritable. This means you can have other user-defined things that look and act like streams, yet that do whatever strange and wonderful things you want. You automatically get to use the zillions of lines of I/O code written by users you don't even know, and they don't need to know about your "extended stream" class.
[15.7] Should I end my output lines with std::endl or '\n' ?
Using
This code simply outputs a
void f()
{
std::cout << ...stuff... << '\n';
}
This code outputs a
void g()
{
std::cout << ...stuff... << std::endl;
}
This code simply flushes the output buffer:
void h()
{
std::cout << ...stuff... << std::flush;
}
Note: all three of the above examples require
[15.16] Why can't I open a file in a different directory such as "..\test.dat" ?
Because
You should use forward slashes in your filenames, even on operating systems that use backslashes (DOS, Windows, OS/2, etc.). For example:
#include <iostream>
#include <fstream>
int main()
{
#if 1
std::ifstream file("../test.dat"); // RIGHT!
#else
std::ifstream file("..\test.dat"); // WRONG!
#endif
...
}
Remember, the backslash ("\") is used in string literals to create special characters: "\n" is a newline, "\b" is a backspace, and "\t" is a tab, "\a" is an "alert", "\v" is a vertical-tab, etc. Therefore the file name "\version\next\alpha\beta\test.dat" is interpreted as a bunch of very funny characters. To be safe, use "/version/next/alpha/beta/test.dat" instead, even on systems that use a "\" as the directory separator. This is because the library routines on these operating systems handle "/" and "\" interchangeably.
Of course you could use
2. 读取效率
http://www.byvoid.com/blog/fast-readfile/
为确保准确性,我又换到Windows平台上测试了一下。结果如下表:
方法/平台/时间(秒) | Linux gcc | Windows mingw | Windows VC2008 |
scanf | 2.010 | 3.704 | 3.425 |
cin | 6.380 | 64.003 | 19.208 |
cin取消同步 | 2.050 | 6.004 | 19.616 |
fread | 0.290 | 0.241 | 0.304 |
read | 0.290 | 0.398 | 不支持 |
mmap | 0.250 | 不支持 | 不支持 |
Pascal read | 2.160 | 4.668 |
从上面可以看出几个问题
- Linux平台上运行程序普遍比Windows上快。
- Windows下VC编译的程序一般运行比MINGW(MINimal Gcc for Windows)快。
- VC对cin取消同步与否不敏感,前后效率相同。反过来MINGW则非常敏感,前后效率相差8倍。
- read本是linux系统函数,MINGW可能采用了某种模拟方式,read比fread更慢。
- Pascal程序运行速度实在令人不敢恭维。
3. 提高速度
(1)内存映射
(2)使用WINAPI
(3)#优化算法 (这才是王道)
http://dev.firnow.com/course/3_program/c++/cppjs/20090403/163891.html
FILE自己维护了一套缓存机制
FILE会使用默认的一个缓存值作为io缓存(4k),或者也可以通过setbuf来设置这个缓存的大小
假
设你fread 1字节 会导致ReadFile
4k,然后fread再将要读取的数据copy到指定的缓冲区中。以后访问只要不过这个边界,就一直从该io缓存中读取,fwrite也是,直到超过io
缓存边界才真正的调用WriteFile。可以调用flush主动强制刷新从而调用WriteFile
或者fclose被动刷新调用WriteFile(这时fclose会阻塞)。
再说一下硬盘的 硬盘的cache由硬盘控制器管理和使用 就像处理器的cache没法直接操作一样 写硬盘的时候会先写入cache 然后硬盘内部会把数据慢慢写入磁盘 这个过程中没有优化 也就是说硬盘驱动按什么顺序写的 写入磁盘就是什么顺序
而实际上 硬盘是个随机访问设备 先写哪个后写哪个无所谓 所以一般在把应用层的io访问转化为底层的io请求后 内核层会做io请求优化排序
假
设一个io队列上目前挂着10个请求 内核层会事先计算每个请求在物理上的位置 然后进行排序
以保证磁头转动一周,尽量让10个请求中的多个在一周内完成,想像一下 最好的情况 10个请求都在一个盘面上 磁头旋转1周 10个请求全部完成
最坏的情况 要转10周 10周的原因是一次只能操作一个磁头 而10个请求可能不幸的在10个盘面上(这时候内核也不会再排序了)
因此让自己的io操作尽可能维持在连续的磁盘空间 且在物理上不跨越盘面 这样效果最好。为此你可能需要硬盘的准确的参数 并精确计算。
缓
存的优势在高强度的io操作会被抵消 因为硬盘的写入速度始终跟不上处理器的请求 cache只能帮助缓冲一下 cache越大 缓冲的时间越长
当cache填满 硬件上ready信号为无效 硬盘驱动不能再写了 只能挂起内核的io队列 这时候上层还在不停的请求
内核层要么继续往io请求队列上挂装请求 要么阻塞发起io的进程 等到cache有空间了 硬件使能ready信号
驱动重新从内河的io请求队列上摘取io请求 再填cache 又满 。。。。 也就是说cache的优势只在一开始的缓存时间上
这个优势对于小的io请求特别有好处 因为能在填满cache之前不会遭到阻塞或挂起
纵上所述 软件上其实做的很有限而且也很累 何必呐 orz。。。。