V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
JoshOY
V2EX  ›  Python

关于读超大文件的问题

  •  
  •   JoshOY ·
    JoshOY · 2014-12-27 18:47:07 +08:00 · 5348 次点击
    这是一个创建于 3636 天前的主题,其中的信息可能已经有所发展或是发生改变。

    今天碰到几个学长在做数据仓库,需要把一个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下会不会有同样的情况出现?

    36 条回复    2014-12-28 11:30:07 +08:00
    lululau
        1
    lululau  
       2014-12-27 19:23:09 +08:00
    Unix 不区分文本和二进制
    lsylsy2
        2
    lsylsy2  
       2014-12-27 19:32:59 +08:00
    8GB的话,会不会有32位的问题?
    9hills
        3
    9hills  
       2014-12-27 19:35:04 +08:00
    一般来说是你这个文本文件有问题,内部有了一个EOF
    BGLL
        4
    BGLL  
       2014-12-27 19:40:32 +08:00
    是不是文本里有\0
    JoshOY
        5
    JoshOY  
    OP
       2014-12-27 19:51:58 +08:00
    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?
    jox
        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
    JoshOY
        7
    JoshOY  
    OP
       2014-12-27 20:38:49 +08:00
    @jox 非常感谢 我找了找其他的文章 应该是只有windows会出这种问题,。

    确实和EOF没什么关系,应该是遇到0之类的特殊字符导致的。
    UNIX下没有问题。
    BGLL
        8
    BGLL  
       2014-12-27 20:42:02 +08:00
    @jox
    但是在文本模式中遇到SUB(ASCII 26)字符会当作文本结束
    BGLL
        9
    BGLL  
       2014-12-27 20:43:49 +08:00
    @JoshOY
    你应该 找到断点位置,看看断点是什么
    jox
        10
    jox  
       2014-12-27 20:47:15 +08:00
    @BGLL 不同的操作系统有不同的实现,以前有系统会使用某个特殊的字符来标记文件的结束,现在只是读完所以可读字符之后返回一个数值或者一个特殊字符而已,文件中并不存在实际的EOF字符。
    mringg
        11
    mringg  
       2014-12-27 20:51:27 +08:00 via Android
    设条件断点,非得把这个问题弄明白!!!!!
    BGLL
        12
    BGLL  
       2014-12-27 21:04:41 +08:00
    @jox EOF只是找到末尾后返回的“信号”,我说的是那个末尾
    你说的“以前的系统”就是Windows,所有Windows中SUB(ASCII 26)都会被当作文本末尾
    jox
        13
    jox  
       2014-12-27 21:14:02 +08:00
    @BGLL 现在windows也这么做么?这样做有缺陷啊,如果使用某个特殊字符来标记文件末尾,那么就不能向文件中输入这个字符了。现在的操作系统会单独保存一个文件的meta数据,可读字符有多少,文件大小之类的,我的理解是现代C在读文件的时候是根据这个meta数据来确定是否到达文件尾或者读完了所有可读字符(有的时候可读字符数并不一定等于文件实际的大小)
    BGLL
        14
    BGLL  
       2014-12-27 21:33:52 +08:00
    @jox
    没错,所有的,包括现在的Windows8.1,SUB(ASCII 26)都是被当作文本末尾的
    以标准的文本模式打开文件遇到SUB(ASCII 26)就会被当作结尾了,以二进制文件模式打开就不会有这个问题。

    记录文件大小那是磁盘系统的事情了。
    jox
        15
    jox  
       2014-12-27 21:47:25 +08:00
    @BGLL 这样啊,我的意思是在读文件的时候去读文件的meta数据,然后根据这个来判断是否到达了文件尾。

    如果以二进制模式打开文件,那程序要如何判断是否到达了文件尾?
    Neveroldmilk
        16
    Neveroldmilk  
       2014-12-27 22:25:38 +08:00
    二进制文件的末尾应该就是文件流的末尾,没有字符了吧。
    jox
        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.
    BGLL
        18
    BGLL  
       2014-12-27 23:02:35 +08:00
    @jox

    系统从磁盘系统得到文件就知道文件首尾指针了。
    具体怎么实现是磁盘系统的事了,程序不用管,大多数是把文件尺寸放在文件分配表里(FAT)。

    以文本模式打开文件是特别的存在,有这个模式主要为了兼容性(该死的换行符,就因为这个换行符耗费了多少人的生命)顺便方便处理简单的文本。DOS特别为了兼容过去的CP / M系统(它的磁盘系统没法精确定位文件结束位置,因为它的记录文件大小只到扇区,一个扇区128字节,一个文件的结束可能在第1个字节也可以在第111字节,所以要一个结束符标记(关键他磁盘系统不把这个操作封装起来,交给人们在文件末尾写额外标识符))
    然后Windows为了兼容DOS......
    jox
        19
    jox  
       2014-12-27 23:11:44 +08:00
    @BGLL 现在的windows系统的内核不再是DOS了吧?

    我感觉楼主的这个问题引出的关于文件系统的问题对我来说要想彻底整明白暂时知识储备不够。。。。你说的这些我也看不太懂。。。。不过还是很感谢分享信息
    BGLL
        20
    BGLL  
       2014-12-27 23:21:05 +08:00
    @jox
    跟内核是什么没关系,只是为了兼容过去的标准,微软特别注重兼容性(比如为了兼容CR、LF两种换行标准,windows下换行符直接就是CR+LF)。

    简单的来说,程序从系统得到文件就是文件起始指针和结束指针,Windows下为了兼容性在文本模式时特别的把ASCII 26的位置当结束位置了。
    jox
        21
    jox  
       2014-12-27 23:25:55 +08:00
    @BGLL 是windows这么实现的还是其他的系统也是这么做的?如果使用打开文件操作得到了两个指针,然后就不再检查是否达到文件尾了吗?如果一个程序在读一个文件的同时,另外一个程序在往这个文件写数据该怎么处理?
    msg7086
        22
    msg7086  
       2014-12-27 23:26:16 +08:00
    @jox 简单说,以前的文件大小只能是128字节的整数。要终结一个文件,就必须在数据里加上这个^Z。
    而后续所有的操作系统都必须兼容这个约定,否则就会破坏很多依赖这个约定的程序了。
    msg7086
        23
    msg7086  
       2014-12-27 23:29:45 +08:00
    @jox 文本文件模式本来就是历史遗留问题。
    如果一个文件是正确的文本文件,那里面就不应该存在^Z。
    如果文件里有^Z而且你打算正确处理文件,那就不该以文本文件模式打开。
    你说的这些本来就你有矛盾啊。
    msg7086
        24
    msg7086  
       2014-12-27 23:30:23 +08:00
    @msg7086 s/就你有/就有/
    jox
        25
    jox  
       2014-12-27 23:44:41 +08:00
    @msg7086 ??是不是回错人了?

    按照windows的标准,如果有^Z字符出现在不是文件尾的地方,那这就不是个符合windows标准的文本文件,就要使用rb模式来打开,这可能是LZ遇到的问题。

    我在考虑的是在Unix系统下,流程序如何确定它达到了文件尾,我考虑的是这里的实现细节,然后意识到这个问题并不是简单两句话能解释清楚的,于是就放弃了。
    lululau
        26
    lululau  
       2014-12-27 23:49:22 +08:00
    jox
        27
    jox  
       2014-12-27 23:54:28 +08:00
    @lululau 。。都是我已经知道的东西。。。。

    我没想明白的是,feof这样的调用是如何实现的,多个文件同时对同一个文件读写这样的情况又是怎么处理的,之类的。
    BGLL
        28
    BGLL  
       2014-12-28 00:04:22 +08:00
    @jox
    @jox

    现在主流系统就Windows还兼容这种老掉牙的规则了。

    也就是说现在的系统下程序得到一个文件就从磁盘系统知道文件的精确到字节起始和结束位置了(也就是起始指针和结束指针),文件尾的定义不就是结束指针的位置吗。

    同时写入读取不矛盾啊,当然没有绝对的同时,还是有先后顺序的。有时候会用因为条件限制会用一个文件进行程序间的通信。

    Unix下也和Windows一样通过磁盘系统得到文件头、尾啊。



    @msg7086 现在的系统里文件也一样,一个扇区也只能存一个文件,只是磁盘系统封装了记录找到文件尾的操作。
    9hills
        29
    9hills  
       2014-12-28 00:11:17 +08:00   ❤️ 1
    @jox 在Linux上,EOF其实就是一个宏: -1。由于以文本模式读文件出来的肯定是unsigned char,所以可以保证没有-1被读出来。这个-1是系统碰到文件结束后返回的(根据文件大小)

    Windows上,有些特殊的动作,某个字符(上面有人说,但我记不清楚了)也可以充当EOF的作用,塞到文本中,用非二进制方式读到这里就断了。

    所以lz的问题,应该在Linux上不会复现
    9hills
        30
    9hills  
       2014-12-28 00:14:04 +08:00
    @jox 文件头文件尾是文件系统中存储的,这个精确定位,不存在模糊。。。具体参见各种文件系统的实现。
    jox
        31
    jox  
       2014-12-28 00:14:09 +08:00
    @BGLL 这位兄弟看来对操作系统很了解啊。

    如果两个进程同时对一个文件进行读写,其中一个进程打开文件之后另一个进程又往这个文件里写入了一些数据,那第一个进程打开时的文件尾与第二个进程打开文件时的文件尾就不一样了,另外结束指针是什么?这个结束指针是怎么用的?通过这个指针能够得到文件当前的文件尾是什么?

    Is it a thing like (int) or a (int *)? What is it?
    jox
        32
    jox  
       2014-12-28 00:15:09 +08:00
    @9hills 我知道的。。。。。。。
    denghongcai
        33
    denghongcai  
       2014-12-28 01:29:45 +08:00 via Android
    实际上就是操作系统如何实现open这个系统调用,你可以看linux下open如何实现的。实际上fd里只有文件的偏移,没有尾指针,多个程序追加同一个文件是由文件系统实现的原子操作来保证一致性,而且这种追加要求open时以“a”的方式打开文件。
    msg7086
        34
    msg7086  
       2014-12-28 01:55:59 +08:00
    @BGLL 一扇区只能存一文件这是文件系统的约定而不是公理。
    先不说有些文件系统已经支持在inode之类的地方内嵌小文件了。我记得是有文件系统可以实现单个扇区多个文件的。
    BGLL
        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的东西我不了解。
    msg7086
        36
    msg7086  
       2014-12-28 11:30:07 +08:00
    @BGLL 是的主流文件系统是单个簇只放文件,主要是提高操作效率,但是并非是硬性规定。
    典型的,路由器或者嵌入式设备通常会把文件紧凑存放以节约空间。
    Linux下有在inode树内部内嵌文件的FS。没记错的话Reiser系就有这个习惯。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4939 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 09:13 · PVG 17:13 · LAX 01:13 · JFK 04:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.