今天碰到几个学长在做数据仓库,需要把一个8GB的txt文件读出来处理,文件有7000多万行。
他们用C++写,然后碰到了问题:文件打开后while(!f.eof()){...}只读取了10万多行就停了。
于是我写了个python脚本,f=open('xxx.txt', 'r')发现同样只能读10万多行。
然后google了一下,看到StackOverflow上有人说用二进制打开,于是尝试了f=open('xxx.txt', 'rb')结果全读出来了…然后让他们用C++试试二进制打开于是也成功了。
请问其中的原理是什么?
本人当时用的是windows,话说unix下会不会有同样的情况出现?
1
lululau 2014-12-27 19:23:09 +08:00
Unix 不区分文本和二进制
|
2
lsylsy2 2014-12-27 19:32:59 +08:00
8GB的话,会不会有32位的问题?
|
3
9hills 2014-12-27 19:35:04 +08:00
一般来说是你这个文本文件有问题,内部有了一个EOF
|
4
BGLL 2014-12-27 19:40:32 +08:00
是不是文本里有\0
|
5
JoshOY OP http://stackoverflow.com/questions/9905874/python-does-not-read-entire-text-file
呃,我找了一下当时看的应该是这篇。 回复说: According to the docs, text mode vs. binary mode only has an impact on end-of-line characters. But (if I remember correctly) I believe opening files in text mode on Windows also does something with EOF (hex 1A). 难道是windows的原因导致读取时文本内出现了EOF? |
6
jox 2014-12-27 20:01:58 +08:00 1
你在什么系统读的这个文件?这个文件又是在什么系统生成的? 不同的操作系统使用不同的字符来标记文本文件的行,如果这个文件是在windows系统下生成的,你在linux系统下使用文本模式来打开这个文件会遇到这种问题,你试试先转换成当前操作系统的格式,然后再尝试使用文本模式打开,txt只是windows下一个文本处理软件生成的文件带的扩展名,这样的一个文件与linux下的text file是不同的,'r'模式只是按照当前操作系统对于行结束的定义返回\n
另外EOF并不是一个文件中实际存在的字符,现代C在打开文件的时候会在读完所有的字符之后返回一个EOF值,并不是说C读到了一个EOF字符。 看看这个: http://c-faq.com/stdio/textvsbinary.html |
7
JoshOY OP |
10
jox 2014-12-27 20:47:15 +08:00
@BGLL 不同的操作系统有不同的实现,以前有系统会使用某个特殊的字符来标记文件的结束,现在只是读完所以可读字符之后返回一个数值或者一个特殊字符而已,文件中并不存在实际的EOF字符。
|
11
mringg 2014-12-27 20:51:27 +08:00 via Android
设条件断点,非得把这个问题弄明白!!!!!
|
12
BGLL 2014-12-27 21:04:41 +08:00
@jox EOF只是找到末尾后返回的“信号”,我说的是那个末尾
你说的“以前的系统”就是Windows,所有Windows中SUB(ASCII 26)都会被当作文本末尾 |
13
jox 2014-12-27 21:14:02 +08:00
@BGLL 现在windows也这么做么?这样做有缺陷啊,如果使用某个特殊字符来标记文件末尾,那么就不能向文件中输入这个字符了。现在的操作系统会单独保存一个文件的meta数据,可读字符有多少,文件大小之类的,我的理解是现代C在读文件的时候是根据这个meta数据来确定是否到达文件尾或者读完了所有可读字符(有的时候可读字符数并不一定等于文件实际的大小)
|
14
BGLL 2014-12-27 21:33:52 +08:00
@jox
没错,所有的,包括现在的Windows8.1,SUB(ASCII 26)都是被当作文本末尾的 以标准的文本模式打开文件遇到SUB(ASCII 26)就会被当作结尾了,以二进制文件模式打开就不会有这个问题。 记录文件大小那是磁盘系统的事情了。 |
15
jox 2014-12-27 21:47:25 +08:00
|
16
Neveroldmilk 2014-12-27 22:25:38 +08:00
二进制文件的末尾应该就是文件流的末尾,没有字符了吧。
|
17
jox 2014-12-27 22:57:45 +08:00
我发现我也没有搞清楚这个问题,刚刚看了一下相关的资料,python的话,因为好多python实现都是C写的,可以认为在I/O这方面跟C是一样的,只考虑Unix类的系统的话,需要借助系统调用才能确定达到了end of file状态,当达到end of file的时候会返回宏EOF,Unix下的rb模式和r是一样的,根据fopen的手册,b只是为了兼容ISO/IEC 9899:1990 (``ISO C90'')标准:
>> The mode string can also include the letter ``b'' either as last character or as a character >> between the characters in any of the two-character strings described above. This is strictly >> for compatibility with ISO/IEC 9899:1990 (``ISO C90'') and has no effect; the ``b'' is ignored. |
18
BGLL 2014-12-27 23:02:35 +08:00
@jox
系统从磁盘系统得到文件就知道文件首尾指针了。 具体怎么实现是磁盘系统的事了,程序不用管,大多数是把文件尺寸放在文件分配表里(FAT)。 以文本模式打开文件是特别的存在,有这个模式主要为了兼容性(该死的换行符,就因为这个换行符耗费了多少人的生命)顺便方便处理简单的文本。DOS特别为了兼容过去的CP / M系统(它的磁盘系统没法精确定位文件结束位置,因为它的记录文件大小只到扇区,一个扇区128字节,一个文件的结束可能在第1个字节也可以在第111字节,所以要一个结束符标记(关键他磁盘系统不把这个操作封装起来,交给人们在文件末尾写额外标识符)) 然后Windows为了兼容DOS...... |
19
jox 2014-12-27 23:11:44 +08:00
@BGLL 现在的windows系统的内核不再是DOS了吧?
我感觉楼主的这个问题引出的关于文件系统的问题对我来说要想彻底整明白暂时知识储备不够。。。。你说的这些我也看不太懂。。。。不过还是很感谢分享信息 |
20
BGLL 2014-12-27 23:21:05 +08:00
@jox
跟内核是什么没关系,只是为了兼容过去的标准,微软特别注重兼容性(比如为了兼容CR、LF两种换行标准,windows下换行符直接就是CR+LF)。 简单的来说,程序从系统得到文件就是文件起始指针和结束指针,Windows下为了兼容性在文本模式时特别的把ASCII 26的位置当结束位置了。 |
21
jox 2014-12-27 23:25:55 +08:00
@BGLL 是windows这么实现的还是其他的系统也是这么做的?如果使用打开文件操作得到了两个指针,然后就不再检查是否达到文件尾了吗?如果一个程序在读一个文件的同时,另外一个程序在往这个文件写数据该怎么处理?
|
22
msg7086 2014-12-27 23:26:16 +08:00
@jox 简单说,以前的文件大小只能是128字节的整数。要终结一个文件,就必须在数据里加上这个^Z。
而后续所有的操作系统都必须兼容这个约定,否则就会破坏很多依赖这个约定的程序了。 |
23
msg7086 2014-12-27 23:29:45 +08:00
|
25
jox 2014-12-27 23:44:41 +08:00
@msg7086 ??是不是回错人了?
按照windows的标准,如果有^Z字符出现在不是文件尾的地方,那这就不是个符合windows标准的文本文件,就要使用rb模式来打开,这可能是LZ遇到的问题。 我在考虑的是在Unix系统下,流程序如何确定它达到了文件尾,我考虑的是这里的实现细节,然后意识到这个问题并不是简单两句话能解释清楚的,于是就放弃了。 |
26
lululau 2014-12-27 23:49:22 +08:00
|
27
jox 2014-12-27 23:54:28 +08:00
|
28
BGLL 2014-12-28 00:04:22 +08:00
|
29
9hills 2014-12-28 00:11:17 +08:00 1
@jox 在Linux上,EOF其实就是一个宏: -1。由于以文本模式读文件出来的肯定是unsigned char,所以可以保证没有-1被读出来。这个-1是系统碰到文件结束后返回的(根据文件大小)
Windows上,有些特殊的动作,某个字符(上面有人说,但我记不清楚了)也可以充当EOF的作用,塞到文本中,用非二进制方式读到这里就断了。 所以lz的问题,应该在Linux上不会复现 |
31
jox 2014-12-28 00:14:09 +08:00
@BGLL 这位兄弟看来对操作系统很了解啊。
如果两个进程同时对一个文件进行读写,其中一个进程打开文件之后另一个进程又往这个文件里写入了一些数据,那第一个进程打开时的文件尾与第二个进程打开文件时的文件尾就不一样了,另外结束指针是什么?这个结束指针是怎么用的?通过这个指针能够得到文件当前的文件尾是什么? Is it a thing like (int) or a (int *)? What is it? |
33
denghongcai 2014-12-28 01:29:45 +08:00 via Android
实际上就是操作系统如何实现open这个系统调用,你可以看linux下open如何实现的。实际上fd里只有文件的偏移,没有尾指针,多个程序追加同一个文件是由文件系统实现的原子操作来保证一致性,而且这种追加要求open时以“a”的方式打开文件。
|
34
msg7086 2014-12-28 01:55:59 +08:00
@BGLL 一扇区只能存一文件这是文件系统的约定而不是公理。
先不说有些文件系统已经支持在inode之类的地方内嵌小文件了。我记得是有文件系统可以实现单个扇区多个文件的。 |
35
BGLL 2014-12-28 09:32:45 +08:00
@jox
是啊文件结尾会改变 C++里 ios::end 代表文件末尾指针,f.seekg(0,ios::end) 把读取指针定位到文件末尾指针。 文件改变了,取到的文件结尾也就不同。 你例子中的while(!f.eof()){...},要是读取中文件变长了会接着读到新结尾,变短了还是会读到原来长度(因为有缓存机制) 要完全实时读取,得每写一个字节重新向系统获取一次文件结尾。 @msg7086 你说是簇吧簇是文件系统最小分配空间,扇区是磁盘最小分配空间,由硬盘上的固件决定。 要一个扇区存储多个文件对于文件系统来说貌似要多很多操作步骤间接完成感觉性能上不划算啊,各种小众文件系统就不知道了,反正NTFS、Ext 二大文件系统是只能一个扇区写一个文件。“inode之类的地方内嵌小文件了"把连续数据块一起处理省略 inode 的 extents?LLinux的东西我不了解。 |