【问题标题】:Indexing columns in a csv file索引 csv 文件中的列
【发布时间】:2016-07-05 12:10:33
【问题描述】:

我有一个大的 csv 文件,其中每一行都有不同的列,例如 ID、用户名、电子邮件、工作职位等。

我想通过完全匹配(用户名 == David)或通配符(jobPosition == %admin)来搜索一行。

我想为此文件中的列建立索引以加快搜索速度,但我不知道应该选择哪种算法(尤其是通配符)。

【问题讨论】:

  • 您无权访问可以将其导入的数据库系统吗?
  • @MartinBroadhurst,这是一个自学项目,我只是想了解通配符索引算法,我写了2个cmets来解释它,在btilly的回答下。

标签: algorithm performance csv search indexing


【解决方案1】:

如何索引 csv 文件:

要索引 csv 文件,您需要将其作为二进制文件而不是文本文件读取。使用 128、256 或 512 块大小。要构建索引,您扫描文件以查找每条记录的开头,然后创建一个索引文件,如下所示:

  key-value-1, 0, 0
   ........
   ........
  key-value-n, block, offset

key-value 是您索引的键的值。可以是复合键。 block 是记录开始的区块编号(请注意,您的记录可以跨越多个区块),offset 是介于0 和 block-size-1 是该块的偏移量。要检索您的记录,您可以在索引文件中查找键(可能使用binary search),然后使用块偏移来访问您的记录directly。

如果需要搜索不同的条件,也可以同时创建多个索引文件。

如果您将CR-LF 作为行尾标记,请注意CR 可以位于块的确切末尾,而LF 将位于下一个块的开头。您需要注意一些像这样的特殊情况。创建此索引文件(或多个文件)后,您可以按键对其进行排序,一切顺利。

或者,如果您的软件允许快速内存块移动(如 C++ memmove),您可以将 insertion sort 与二分搜索结合使用。这样,在您完成构建索引后,它们就已经排序。如果索引条目是从使用慢速输入设备(例如,keyboard)捕获的文件中添加的,这将特别有效。如果您要管理大量记录,请考虑为您的索引使用 B-Tree 结构。

此架构允许您的 csv 数据库 接受记录添加、删除和更新。 添加在文件末尾。要删除记录,只需将记录的第一个字符更改为 0x0 这样的唯一字符,当然还可以从索引文件中删除条目。 更新可以通过在文件末尾删除然后添加更新的记录来实现。

这将在您的数据库上创建一些garbage collection 的需求,但大多数DBMS(如果不是全部)都这样做。定期重建索引并清除已删除的记录。

注意:对于代码实现look at this answer.,索引 6867839 行的 9 Gb csv 文件大约需要 6 分钟。 Joblib 在磁盘上存储索引非常慢。索引文件为 134 Mb。

让我们使用一个玩具 csv 示例。我们将按记录号索引文件。为了示例的目的,我们将记录号存储在索引的关键部分,尽管这显然是不必要的。

strings,numbers,colors
string1,1,blue
string2,2,red
string3,3,green
string4,4,yellow

索引文件将存储在列表idx:

 idx

 [[0, 0, 0], [1, 0, 24], [2, 0, 40], [3, 0, 55], [4, 0, 72], [5, 0, -1]]

注意最后一个索引元素处的 -1 表示在顺序访问的情况下索引文件的结尾。您可以使用这样的代码按记录编号访问 csv 文件的任何单独行:

def get_rec(n=1,binary=False):
    n=1 if n<0 else n+1
    s=b'' if binary else '' 
    if len(idx)==0:return ''
    if idx[n-1][2]==-1:return ''
    f.seek(idx[n-1][1]*BLKSIZE+idx[n-1][2])
    buff=f.read(BLKSIZE)
    x=buff.find(b'\r')
    while x==-1:
        s=s+buff if binary else s+buff.decode()
        buff=f.read(BLKSIZE)
        x=buff.find(b'\r')
    return s+buff[:x]+b'\r\n' if binary else s+buff[:x].decode()

【讨论】:

  • 不错的答案。你能澄清一下“插入排序”是什么意思吗?为什么使用不同的行尾字符会有所帮助?假设您有一个仅获取插入(没有更新或删除)的 CSV,为了更新您需要将其加载到内存中的索引,插入新值并将其写回文件,对吗?
  • 绝对。对于小文件,索引可以一直保存在内存中,并且仅在作业结束时写入磁盘。对于大型索引文件,您需要使用 B-Tree 模式或类似模式来管理索引文件。 B-Tree 实现页面模式并在必要时将“脏”页面写入磁盘,因此您不需要将整个索引文件加载到内存中。你可能会发现更多关于插入排序here
【解决方案2】:

短版。将 CSV 加载到 SQLite 中,然后进行查询。您可以在https://www.sqlite.org/ 了解 SQLite,但我建议您寻找一个您的语言中已有的库。

长版。

在弄清楚如何编写代码之前,您可以将数据加载到 SQLite 中,对其进行索引、查询并完成。如果您当前不知道如何编写 SQL,这甚至是正确的。 (相信我,我知道你需要的算法,而且学习它们比学习 SQL 更难。)

在您真正完成编写代码之前,您的替代自我将完成其他几个项目。

编写完代码后,就可以进行调试了。我保证你不会成功调试它。与此同时,在平行宇宙中,您继续构建更多项目。

一旦您调试了代码并将其投入生产(仍然存在未知错误),您就可以跳过初始加载步骤。同时,你的另一个宇宙自己甚至不必考虑 SQLite 是用非常高效的 C 语言实现的,它的优化器可能与“真实”数据库不匹配,但比你自己可以滚动的任何东西都要好。

鉴于此,您确实应该考虑使用 SQLite。

PS:https://www.sqlite.org/fts3.html 解释了如何在 SQLite 中进行通配符匹配。

【讨论】:

  • 总的来说,我同意并喜欢 SQLite,但具体来说,我过去曾这样做过,发现在某些情况下导入可能需要几十分钟 - 它以大约 30 次插入/秒的速度达到顶峰来自现代 SSD 硬件的 CSV 输入。所以这是一个很好的起点(下次我需要那个项目时,我会尝试更快地获得它)但并不总是一个可行的选择。
  • @eftpotrm 我发现“30 次插入/秒”非常低。我会立即怀疑问题出在触发器、索引和外键的某种组合上。您是否执行了标准加载数据然后创建索引而不是尝试在存在索引的情况下加载它?
  • 我同意,这让我很惊讶!非常小的数据库,当然没有触发器 - 无法说出我脑海中的键和索引。下次需要时我会研究优化,因为我当然喜欢 SQLite 作为一种解决方案(并且认为它可能适合 OP),但我会注意性能。
  • 你试过 .import 命令了吗?请参阅stackoverflow.com/questions/697004/bulk-load-data-into-sqlite 了解更多信息。
  • 谢谢你的回答和你的时间,我已经编程了我一半的年龄,我对 SQL 和 NoSQL 数据库非常熟悉,实际上我使用过 MySQL、MS SQL、PostgreSQL ,SQLite,Radis,Memcached 等等,并使用不同的 ORM ;)... 实际上,该文件已导入 MySQL,但我在这里要求的是一个自我培训项目。我一直使用数据库索引,但从未使用过索引算法来了解它们是如何编写的以及它们是如何在内部工作的。另外,我不擅长这种算法,我真的很想学习它们。
猜你喜欢
  • 2022-08-11
  • 1970-01-01
  • 2014-02-14
  • 2015-08-12
  • 2016-11-12
  • 2021-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多