V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ranxi
V2EX  ›  程序员

请教 Python 大佬,遍历一个 5GB 大小的 txt 文件,用什么方式效率比较高

  •  
  •   ranxi · 2022-07-22 20:09:29 +08:00 · 4340 次点击
    这是一个创建于 630 天前的主题,其中的信息可能已经有所发展或是发生改变。

    python 新手,现在有个需求,大概几百万条数据需要翻译,映射关系存在一个 5GB 左右大小的 txt 文件里,也就是拿几百万个值去几亿条数据里去匹配,怎么做比较好

    35 条回复    2022-07-25 11:10:23 +08:00
    jarence
        1
    jarence  
       2022-07-22 20:19:08 +08:00
    介绍的太笼统了,匹配是怎么匹配的?
    HankLu
        2
    HankLu  
       2022-07-22 20:19:38 +08:00
    社工库?
    Richard14
        3
    Richard14  
       2022-07-22 20:20:28 +08:00
    5GB 不大,全部载入内存也能抗
    如果是 500G 的话就要建立索引了,可以考虑使用数据库。不过无论如何如果要大量随机读写的话效率不会很美丽就是了
    rushpu
        4
    rushpu  
       2022-07-22 21:22:18 +08:00
    可以用 dask.bag.read_text
    https://examples.dask.org/bag.html
    VincentYoung
        5
    VincentYoung  
       2022-07-22 21:36:54 +08:00 via iPhone
    之前搞过这个。你不要 readlines 会卡死,readline 就行。建议切成 5 个 1G 的文件,这样效率更高。
    VincentYoung
        6
    VincentYoung  
       2022-07-22 21:37:54 +08:00 via iPhone
    用的数据库是 clickhouse ,查询速度还是挺快。
    ranxi
        7
    ranxi  
    OP
       2022-07-22 21:46:31 +08:00
    @jarence 两个文件 O1 和 O2 ,可以理解成两张表,每行\t 分隔,O1 每行是 a ,b ,c ,其中 c 需要映射成另一个值 d ,这个映射关系在 O2 文件里( c ,d ),O2 文件大概有 5GB ,几亿行数据。
    ranxi
        8
    ranxi  
    OP
       2022-07-22 21:48:01 +08:00
    @Richard14 试了下 pandas ,查一次要 3 ,4 秒,查几百万次完全查不动啊
    ranxi
        9
    ranxi  
    OP
       2022-07-22 21:49:02 +08:00
    暂不考虑数据库
    Juszoe
        10
    Juszoe  
       2022-07-22 21:52:02 +08:00
    对 O2 建哈希表完事了,5 个 G 绰绰有余
    liuhan907
        11
    liuhan907  
       2022-07-22 22:30:59 +08:00
    这么朴素的任务弄个 hash 表不就完了。啥你说内存不够,不够就加。内存多便宜.
    blankmiss
        12
    blankmiss  
       2022-07-22 22:33:07 +08:00
    @ranxi 给个模拟数据
    ytmsdy
        13
    ytmsdy  
       2022-07-22 22:47:06 +08:00
    清洗出来,自己先按照数据的逻辑做一个大的基本划分,然后丢到 pandas !
    SenLief
        14
    SenLief  
       2022-07-22 22:47:46 +08:00
    多加几根内存条就可以吧? 5g 不是很大
    liprais
        15
    liprais  
       2022-07-22 22:52:32 +08:00 via iPhone
    整到 sqlite 里
    一个 join 完事
    有你问的时间都跑完了
    ranxi
        16
    ranxi  
    OP
       2022-07-22 23:05:35 +08:00
    @liprais 用不了 sql
    ranxi
        17
    ranxi  
    OP
       2022-07-22 23:11:53 +08:00
    O1 按需求是要逐行读取的,所以只能对 O2 操作。我明天试试 hash 表吧
    wxf666
        18
    wxf666  
       2022-07-22 23:49:08 +08:00   ❤️ 1
    @ranxi 按理说你都能用 python 了,sqlite 是内置的标准库,应该是用得了的

    # 根据 O2 构造 hash 表(字典)
    with open(r'O2.txt', encoding='utf-8') as fp:
       table = dict((row.split('\t') for row in fp))

    # O1 逐行替换并输出
    with open(r'O1.txt', encoding='utf-8') as in_fp, open(r'out.txt', 'w', encoding='utf-8') as out_fp:
       for row in in_fp:
         cols = row.split('\t')
         cols[2] = table[cols[2].rstrip('\n')]
         out_fp.write('\t'.join(cols))

    内存若不够,考虑构造 hash 表时,仅将 hash(str) 作为键名,然后 cols[2] = table[hash(cols[2].rstrip('\n'))] ?
    Trim21
        19
    Trim21  
       2022-07-23 04:03:25 +08:00 via Android
    说不定 python 编译的时候没带 SQLite 🐶
    renmu123
        20
    renmu123  
       2022-07-23 08:41:33 +08:00 via Android
    扔哈希或者 Redis 里再查
    wxf666
        21
    wxf666  
       2022-07-23 09:42:48 +08:00   ❤️ 2
    @ranxi 可以试试用 sqlite ,几行搞定 导入+翻译+输出,感觉速度应该也不慢


    1. 生成 O2.txt (从小写字母,映射至大写字母,共 26 行)

    printf "%s\n" {a..z} | sed 's/^.*$/&\t\U&/' | tee O2.txt

    『输出』
    a    A
    b    B
    …  …
    z    Z


    2. 生成 O1.txt (也是 26 行,字段内容是:主键 ID 、预期转换成啥样、待转换内容)

    printf "%s\n" {a..z} | awk -v OFS=$'\t' '{print NR, toupper($0), $0}' | tee O1.txt

    『输出』
    1    A    a
    2    B    b
    …  …  …
    26    Z    z


    3. 导入映射表至数据库

    sqlite3 -tabs O2.db 'CREATE TABLE O2 (key PRIMARY KEY, value) WITHOUT ROWID' '.import O2.txt O2'


    4. 逐行查数据库进行翻译

    4.1 为 SQLite 启用 csv 扩展

    ①下载 csv.c: https://www.sqlite.org/src/file?name=ext/misc/csv.c&ci=tip
    ②编译扩展:参考 https://sqlite.org/loadext.html

    4.2 翻译

    SQLITE_CSV_LIB_PATH='./csv' # 编译好的 CSV 模块库路径(可省略后缀)
    SQLITE_CACHE_SIZE_MB=512 # 数据库最大缓存页面大小(单位:MB )

    # sed -E 's/"/""/g; s/^|$/"/g; s/\t/","/g' O1.txt |
    tr '\t' ',' < O1.txt | # 制表符 转成 逗号(要求 O1.txt 每列内容,都不包含『"』『,』,否则用上面那行)

    sqlite3 -tabs O2.db \
      ".load $SQLITE_CSV_LIB_PATH" \
      "PRAGMA cache_size = -$((SQLITE_CACHE_SIZE_MB << 10))" \
      'CREATE VIRTUAL TABLE TEMP.O1 USING csv(filename="/dev/stdin", columns=3)' \
      'SELECT O1.c0 id, O1.c1 expect, O2.value replaced FROM O1 LEFT JOIN O2 ON O1.c2 = O2.key'

    『输出』
    id   expect   replaced
    1    A    A
    2    B    B
    …  …  …
    26    Z    Z
    FYFX
        22
    FYFX  
       2022-07-23 10:15:51 +08:00
    以前我还会考虑怎么做,现在碰到大点的数据,现在就两张表都塞 hadoop 然后 spark 跑个 join 就好
    winglight2016
        23
    winglight2016  
       2022-07-23 10:40:23 +08:00
    前面有人提过 dask 、dtable 这些照理够用了,还想通用一点,上 spark 、flink 这种大数据平台
    krixaar
        24
    krixaar  
       2022-07-23 10:42:32 +08:00
    5GB 左右几亿条,数据条目本身不大而且规整的话,按某种方式(比如先 hash 一下 key )切一下建文件夹和文件,比如 abcdef 对应 123456 就建 ab/cd/ef 路径,文件名 123456 ,建好之后直接 path 判断下有没有,有就把路径里唯一的文件名写进去。
    wxf666
        25
    wxf666  
       2022-07-23 16:41:27 +08:00
    @krixaar 这个开销有点大噢

    比如 Linux ext4 ,每个文件所需的一个 inode 要 256 字节(存各种属主、权限、时间信息,还有数据分布在哪儿等),

    且不说应该不会预留几亿个 inode 可用,光是建一亿个文件就要 23.8 GB 的 inode ,还没算目录……

    以及长文件名、特殊符号等其他问题

    那还不如用数据库呢,MySQL 的 innodb 下,一行数据仅额外需要至少 18 字节(存事务、回滚信息等),SQLite 更少

    若这个表的 B+树 3~4 层高,前两层容易缓存至内存,那么翻译一行数据一般只需额外读取 1~2 次硬盘,绝对比文件系统开销小
    krixaar
        26
    krixaar  
       2022-07-23 17:01:54 +08:00
    @wxf666 #25 说了不考虑数据库啊🤣
    另外这个提法是为了黑 Windows 版 QQ 的群图片缓存( Image\Group2 ),腾讯是把文件名开头的四个字符比如 abcdef.jpg 建成 ab/cd 路径然后把 abcdef.jpg 文件塞里面。
    wxf666
        27
    wxf666  
       2022-07-23 17:50:45 +08:00
    @krixaar 不知为何不考虑数据库,权限不足?不熟悉?

    连 1MB 、无需额外进程 的 SQLite 也要排除。。

    那上面提到的大数据平台就更离谱了


    眼拙,丝毫没看出原来是在讽刺

    其他做得好的类似软件,是如何存储这些小文件的?数据库?

    SQLite 确实提到过,数据库中存储小文件,可比文件系统快 35%,减少 20% 磁盘占用

    https://sqlite.org/fasterthanfs.html
    documentzhangx66
        28
    documentzhangx66  
       2022-07-23 20:04:55 +08:00   ❤️ 1
    这种场景,你说你不用数据库,我猜测,你应该是 C++ 内嵌汇编的大佬,自己能写出比数据库引擎更好的分析、算法、索引,结果,你起手式却是 Python ?????

    你用 Python 处理数据,不用数据库,反向做方案,是贵司钱多?还是工作量不饱和,要给自己加任务?

    其次,你这最大的数据量,几亿条,才 5GB ,另一个几百万条的文件,估计才几十 MB 吧?不说内存,一些大缓存的洋垃圾 CPU ,说不定都能把你这几百万条文件给一口吃掉。

    不用数据库,这道题就是如何使用 C++,选取极致的字符串匹配算法,实现数据切分到每个核 与 内存,使其处理带宽最大化。

    用数据库,这道题就是怎么安装、配置数据库,进行性能调优。

    这两道题的难度完全不是一个等级的,不是大佬当然选择数据库,最好是地球上最强数据库 Oracle 。

    至于 Python ?如果你的目标是,追求极致的处理性能,那么 Python 在这个问题上,就是个笑话。有些数据库引擎内部能优化到把数据量按 node 与 内存条的对应关系给负载均衡 + 并行起来,Python 却还在研究代码是不是写错了,怎么按行读取这种事情上,画风都不一样。

    当然了,如果只是想叼根烟,翘着二郎腿,在空调房里,摸摸鱼,顺便在上百万的全闪存储 + 王思聪级的服务器上,用 Python 跑个数据玩玩,那当我没说。
    edk24
        29
    edk24  
       2022-07-24 01:22:17 +08:00
    fopen, 控制游标, 一次读入 n 行, 以此类推 只到遇到文件结尾符


    大文件处理大多是这种套路, windows 的很多设计也是, 因为不可能几个 gb 的文件全部读到内存里面处理.
    mikewang
        30
    mikewang  
       2022-07-24 02:44:35 +08:00
    ```
    import mmap
    ```
    ?
    zlstone
        31
    zlstone  
       2022-07-24 08:48:58 +08:00
    估计是个面试题,不让用数据库,只能用 Python 解决
    ranxi
        32
    ranxi  
    OP
       2022-07-24 10:17:48 +08:00
    抱歉各位,昨天有事没看回复,我再说明一下。

    首先我之前是写 java 的,临时过来写 python ,所以很多用法不了解。
    然后 O1 文件其实不是一个规则的 txt ,而是需要解析的 xml 文件,我是用 sax 逐行读取的。这点之前没有详细说明
    O2 文件跟之前描述一样,是个表形式的 txt 文件,通过第一列映射第二、三列
    wxf666
        33
    wxf666  
       2022-07-24 19:07:06 +08:00
    @ranxi 描述改个不停。。不如放几行数据出来

    我还是觉得 sqlite 足够你用,几行的事

    下载个 sqlite3.exe ( 1~2MB )就能用,也不用管理员权限,内存占用也随你设,1MB ,1GB ,都行

    xml 也很好解决,xmlstarlet
    crayygy
        34
    crayygy  
       2022-07-25 09:53:16 +08:00 via iPhone
    @krixaar Git 其实也是这种方式
    ranxi
        35
    ranxi  
    OP
       2022-07-25 11:10:23 +08:00
    @wxf666 感谢,已解决
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1007 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 23:11 · PVG 07:11 · LAX 16:11 · JFK 19:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.