查找8GB +文本文件中的"密钥"

WeG*_*ars 13 delphi sorting algorithm search

我有一些'小'文本文件,包含大约500000个条目/行.每行还有一个"键"列.我需要在一个大文件中找到这个密钥(8GB,至少2.19亿条目).找到后,我需要将大文件中的'Value'附加到小文件中,在行的末尾作为新列.

像这样的大文件:

KEY                 VALUE
"WP_000000298.1"    "abc"
"WP_000000304.1"    "xyz"
"WP_000000307.1"    "random"
"WP_000000307.1"    "text"
"WP_000000308.1"    "stuff"
"WP_000000400.1"    "stuffy"
Run Code Online (Sandbox Code Playgroud)

简单地说,我需要在大文件中查找"密钥".

显然我需要在RAM中加载整个表(但这不是我有32GB可用的问题).大文件似乎已经排序了.我得检查一下.
问题是我无法使用类似TDictionary的快速查找,因为正如您所看到的,密钥并不是唯一的.

注意:这可能是一次性计算.我将使用该程序一次,然后扔掉它.因此,它不一定是最佳算法(难以实现).它只需要在适当的时间内完成(如1-2天).PS:我更喜欢没有DB这样做.

我正在考虑这个可能的解决方案:TList.BinarySearch.但似乎TList仅限于134,217,727(MaxInt div 16)项目.所以TList不会工作.


结论:
我选择了Arnaud Bouchez解决方案.他的TDynArray令人印象深刻!如果你需要处理大文件,我完全推荐它.
AlekseyKharlanov提供了另一个不错的解决方案,但TDynArray已经实现.

Arn*_*hez 17

不要重新发明二进制搜索或B-Tree的轮子,而是尝试使用现有的实现.

将内容提供给SQLite3内存数据库(使用正确的索引,并且每10,000次INSERT执行一次事务),您就完成了.确保您定位Win64,以便在RAM中有足够的空间.您甚至可以使用基于文件的存储:创建速度稍慢,但使用索引时,按键查询将是即时的.如果您的Delphi版本中没有SQlite3支持(通过最新的FireDAC),您可以使用我们的OpenSource单元及其相关文档.

使用SQlite3将明确更快,并且使用比常规客户端 - 服务器SQL数据库更少的资源 - BTW"免费"版本的MS SQL无法处理您需要的大量数据,AFAIR.

更新:我已经编写了一些示例代码来说明如何使用SQLite3和我们的ORM层来解决您的问题 - 请参阅github中的源代码文件.

以下是一些基准信息:

  with index defined before insertion:
    INSERT 1000000 rows in 6.71s
    SELECT 1000000 rows per Key index in 1.15s

  with index created after insertion:
    INSERT 1000000 rows in 2.91s
    CREATE INDEX 1000000 in 1.28s
    SELECT 1000000 rows per Key index in 1.15s

  without the index:
    INSERT 1000000 rows in 2.94s
    SELECT 1000000 rows per Key index in 129.27s
Run Code Online (Sandbox Code Playgroud)

因此,对于庞大的数据集,索引是值得的,并且在数据插入后创建索引会减少使用的资源!即使插入速度较慢,选择每个键时索引的增益也很大.您可以尝试对MS SQL执行相同的操作,或使用其他ORM,我猜您会哭.;)

  • SQL Server Express Edition的限制目前是每个数据库10 GB.请参阅https://www.microsoft.com/en-us/cloud-platform/sql-server-editions-express (4认同)
  • 我怀疑当存储在MS SQL中时,原始的8GB输入最终将超过10 GB,包括索引和所有存储开销. (4认同)
  • MS SQL Server是一个服务器数据库.SQLite是嵌入式数据库.奇怪,你试图比较它们.在SQL Server中完全可以使用SQLite完成所有操作.如果你不知道它是如何完成的,那并不意味着它无法完成.期.但是,是的,如果滥用,任何数据库都可以扼杀性能. (3认同)

Arn*_*hez 10

另一个答案,因为它是另一个解决方案.

我使用了TDynArray包装器及其排序和二进制搜索方法,而不是使用SQLite3数据库.

type
  TEntry = record
    Key: RawUTF8;
    Value: RawUTF8;
  end;
  TEntryDynArray = array of TEntry;

const
  // used to create some fake data, with some multiple occurences of Key
  COUNT = 1000000; // million rows insertion !
  UNIQUE_KEY = 1024; // should be a power of two

procedure Process;

var
  entry: TEntryDynArray;
  entrycount: integer;
  entries: TDynArray;

  procedure DoInsert;
  var i: integer;
      rec: TEntry;
  begin
    for i := 0 to COUNT-1 do begin
      // here we fill with some data
      rec.Key := FormatUTF8('KEY%',[i and pred(UNIQUE_KEY)]);
      rec.Value := FormatUTF8('VALUE%',[i]);
      entries.Add(rec);
    end;
  end;

  procedure DoSelect;
  var i,j, first,last, total: integer;
      key: RawUTF8;
  begin
    total := 0;
    for i := 0 to pred(UNIQUE_KEY) do begin
      key := FormatUTF8('KEY%',[i]);
      assert(entries.FindAllSorted(key,first,last));
      for j := first to last do
        assert(entry[j].Key=key);
      inc(total,last-first+1);
    end;
    assert(total=COUNT);
  end;
Run Code Online (Sandbox Code Playgroud)

以下是时间结果:

one million rows benchmark:
INSERT 1000000 rows in 215.49ms
SORT ARRAY 1000000 in 192.64ms
SELECT 1000000 rows per Key index in 26.15ms

ten million rows benchmark:
INSERT 10000000 rows in 2.10s
SORT ARRAY 10000000 in 3.06s
SELECT 10000000 rows per Key index in 357.72ms
Run Code Online (Sandbox Code Playgroud)

它比SQLite3内存解决方案快10倍以上.1000万行保留在Win32进程的内存中没有问题.

并且很好地了解了TDynArray包装器在实践中的工作原理,以及SSE4.2优化的字符串比较功能如何提供良好的结果.

我们的github存储库中提供了完整的源代码.

编辑:在Win64下有100,000,000行(1亿行),在此过程中使用超过10GB的RAM:

INSERT 100000000 rows in 27.36s
SORT ARRAY 100000000 in 43.14s
SELECT 100000000 rows per Key index in 4.14s
Run Code Online (Sandbox Code Playgroud)


Ale*_*nov 7

由于这是一次性任务.最快的方法是将整个文件加载到内存中,逐行扫描内存,解析密钥并将其与搜索键(键)进行比较并打印(保存)找到的位置.

UPD:如果您在源文件中已排序列表.并假设您有411000个键来查找.您可以使用此技巧:按源文件的顺序对搜索键进行排序.从两个列表中读取第一个密钥并进行比较.如果它们不同,请从源头读取,直到它们相等.保存位置,如果源中的下一个键也相等,也保存它.等等.如果下一个键不同,请从搜索键列表中读取下一个键.继续直到EOF.

  • 如果您逐行扫描,则无需将其全部加载到内存中.只是逐行阅读.缓冲使用的文件内存:4 KB. (4认同)