【问题标题】:The best solution to process the huge text file data in Delphi 7Delphi 7中处理海量文本文件数据的最佳解决方案
【发布时间】:2013-07-18 01:01:42
【问题描述】:

我有这样的文本文件:

"01","AAA","AAAAA" 
"02","BBB","BBBBB","BBBBBBBB" 
"03","CCC" 
"04","DDD","DDDDD"

我想将此文本文件数据加载到 sybase db 中的临时表中。所以,我需要构建一个程序来逐行读取这个文本文件,直到 eof。如果文本文件很小,则逐行读取的过程很快。但是如果文本文件太大(可以超过500M),逐行读取的过程太慢了。我认为逐行读取方法不适合大型文本文件。因此,需要找到其他解决方案将文本文件数据加载到 db 中,而不是逐行读取文本文件的方法。有什么建议吗? 示例代码:

var
  myFile : TextFile;
  text   : string;

begin
  // Try to open the Test.txt file for writing to
  AssignFile(myFile, 'Test.txt');

  // Display the file contents
  while not Eof(myFile) do
  begin
    ReadLn(myFile, text);
    TempTable.append;
    TempTable.FieldByName('Field1').asstring=Copy(text,2,2);
    TempTable.FieldByName('Field2').asstring=Copy(text,7,3);
    TempTable.FieldByName('Field3').asstring=Copy(text,13,5);
    TempTable.FieldByName('Field4').asstring=Copy(text,21,8);
    TempTable.post;
  end;

  // Close the file for the last time
  CloseFile(myFile);
end;

【问题讨论】:

  • “目前我的 Delphi 程序执行此操作”意味着您应该有一些代码可以与您的问题一起发布。如果您不向我们展示您现在正在做什么,我们怎么可能建议另一种方式?请edit您的问题包括有关您现在正在做什么以及“处理”意味着什么的信息。我们愿意提供帮助,但如果我们要提供替代方案,您必须实际描述问题并展示您正在做什么。谢谢。
  • 另外,您应该包括您正在使用的数据库 (DBMS),以及您正在使用哪些库/组件来处理它。一些数据库引擎具有命令行工具,可让您将文本文件中的数据直接导入数据库(或临时数据库)。
  • 感谢肯·怀特。我将根据您的评论编辑我的问题。
  • 对于非常快速的逗号分隔变量数据转换和加载,请使用数据库工具。如果您的文件对于内存来说太大,那么您应该在任何转换之前将数据加载到 SQL 数据库中,并将转换作为第二阶段。
  • Sybase 的 bcp 版本可以处理这些数据吗? infocenter.sybase.com/help/index.jsp?topic=/…

标签: delphi text-files delphi-7


【解决方案1】:

一些一般提示:

  • 确保您的 TempTable 在内存中,或使用快速数据库引擎 - 看看 SQlite3 或其他方式(如 FireBird 嵌入式、NexusDB 或 ElevateDB)作为可能的数据库替代方案;
  • 如果您不使用TTable,而是使用真正的数据库,请确保将插入嵌套在事务中
  • 对于真正的数据库,请检查您是否不能使用 ArrayDML 功能,该功能可以更快地在远程数据库(如 Sybase)中插入大量数据 - such Array DML is handled for instance with FireDAC AFAIK;
  • 已知FieldByName('...') 方法非常慢:改用本地变量TField
  • 使用 TextFile 时,分配更大的临时缓冲区;
  • 如果您使用的是较新的 Unicode 版本的 Delphi (2009+),则使用 TextFile 不是最佳选择。

所以你的代码可能是:

var
  myFile : TextFile;
  myFileBuffer: array[word] of byte;
  text   : string;
  Field1, Field2, Field3, Field4: TField;
begin

  // Set Field* local variables for speed within the main loop
  Field1 := TempTable.FieldByName('Field1');
  Field2 := TempTable.FieldByName('Field2');
  Field3 := TempTable.FieldByName('Field3');
  Field4 := TempTable.FieldByName('Field4');

  // Try to open the Test.txt file for writing to
  AssignFile(myFile, 'Test.txt');
  SetTextBuf(myFile, myFileBuffer); // use 64 KB read buffer

  // Display the file contents
  while not Eof(myFile) do
  begin
    ReadLn(myFile, text);
    TempTable.append;
    Field1.asInteger := StrToInt(Copy(text,2,2));
    Field2.asString := Copy(text,7,3);
    Field3.asString := Copy(text,13,5);
    Field4.asString := Copy(text,21,8);
    TempTable.post;
  end;

  // Close the file for the last time
  CloseFile(myFile);
end;

您可以使用嵌入式引擎实现非常高的速度,几乎没有大小限制,但您的存储空间。例如,参见how fast we can add content to a SQLite3 database in our ORM:数据库文件中每秒大约 130,000 / 150,000 行,包括所有 ORM 编组。我还发现 SQLite3 生成的数据库文件比其他方法小得多。如果您想快速检索任何字段,请不要忘记在您的数据库中定义 INDEXes,如果可能插入行数据之后(以提高速度)。对于SQLite3,我想已经有一个ID/RowID 整数主键可用,它映射您的第一个数据字段。这个ID/RowID 整数主键已经被SQLite3 索引了。顺便说一句,我们的 ORM 现在支持FireDAC / AnyDAC and its advanced Array DML feature

【讨论】:

  • 对于 Unicode 版本的 Delphi,有什么比使用 TextFile 更好的选择?
  • 看看TStreamReader:你可以使用TStreamReader.Create(aFileName[,encoding])然后使用ReadLine方法。但请注意,默认 TEncoding 在较新版本中确实发生了变化:现在是 TEncoding.UTF8 - 在您的情况下,您可能会受益于使用 TEncoding.ASCII
【解决方案2】:

文本文件通常有一个非常小的缓冲区。考虑使用SetTextBuf function 来提高您的性能。

var
  myFile : TextFile;
  text   : string;
  myFileBuffer: Array[1..32768] of byte;
begin
// Try to open the Test.txt file for writing to
  AssignFile(myFile, 'Test.txt');
  SetTextBuf(MyFile, myFileBuffer);
  Reset(MyFile);

// Display the file contents
  while not Eof(myFile) do
    begin
      ReadLn(myFile, text);
    end;

// Close the file for the last time
  CloseFile(myFile);
end;

【讨论】:

  • 我是编程新手,英语不是很好,请原谅我。
  • 旧的 pascal 文件接口已弃用。由于使用全局变量来控制打开和共享模式,因此它不是线程安全的。使用 TStream 及其后代。
  • @MadHatter TStream + TTextReader 会增加您的灵活性,但不会增加速度。所以ReadLn 在这里很好。
  • 不,ReadLn 已经不行了……现在是 Delphi 开发人员进入二十一世纪并停止 TurboPascal 思维方式的时候了。他们昨天还在编写单线程应用程序,就好像他们的应用程序仍然在 DOS 上运行......
  • 嗨,我有一个问题。如果我将 myFileBuffer 的数组增加到 64k 会有什么影响?
【解决方案3】:

除了已经说过的,我也会避免使用任何 TTable 组件。您最好使用 TQuery 类型的组件(取决于您使用的访问层)。像这样的东西:-

qryImport.SQL := 'Insert Into MyTable Values (:Field1, :Field2, :Field3, :Field4);';

Procedure ImportRecord(Const pField1, pField2, pField3, pField4 : String);
Begin
  qryImport.Close;
  qryImport.Params[0].AsString := pField1;      
  qryImport.Params[1].AsString := pField2;`
  qryImport.Params[2].AsString := pField3;
  qryImport.Params[3].AsString := pField4;
  qryImport.ExecSQL;
End;

希望这会有所帮助。

【讨论】:

    【解决方案4】:

    另一种方法是使用内存映射文件(您可以 google 或访问 torry.net 来查找实现)。它不适用于> 1gb的文件(在win32中,在win64中,您几乎可以映射任何文件)。它会将您的所有文件转换为PAnsiChar,您可以像一个大缓冲区一样进行扫描,搜索#10 和#13(单独或成对)并手动拆分字符串。

    【讨论】:

    • 当从头到尾读取一次大文件时(这是 OP 的兴趣所在),如果输入缓冲区足够大,内存映射文件将比使用文件 API 直接读取操作慢(例如大于 256 KB)。根据设计,内存映射仅在随机访问时更快。
    • @ArnaudBouchez 1GB 文件适用于 Win32 空间,但适用于 Win64 地址空间。平台会有所作为吗?另外 - 是的,它可能不会比 win32 上的缓冲读取更快 - 但在连续缓冲区上解析变得更容易。当然,地址空间的耗尽和最大大小约为 1.5 GB 的限制是为这种简单性付出的代价
    • 我怀疑 Win64 不会更快。代价是页面丢失和低级 CPU 异常迫使操作系统经常更改其内部状态,而普通的 API 调用将更加稳定并且不会中断操作系统流程。在 mORMot 中,我使用了很多内存映射文件,例如用于查看日志文件,但只有在我开始随机浏览文件时才方便,一旦我通读它以计算索引和统计信息。见blog.synopse.info/post/2011/08/20/Enhanced-Log-viewer
    【解决方案5】:

    如果您使用(或不介意开始使用)JEDI Jvcl,他们有一个TJvCSVDataSet,它允许您像 Delphi 中的任何其他数据集一样简单地使用您的 CSV 文件,包括能够定义持久字段并使用“标准”Delphi 数据库功能:

    JvCSVDataSet1.FileName := 'MyFile.csv';
    JvCSVDataSet1.Open;
    while not JvCSVDataSet1.Eof do
    begin
      TempTable.Append; // Posts last appended row automatically;
                        // no need to call Post here.
    
      // Assumes TempTable has same # of fields in the
      // same order
      for i := 0 to JvCSVDataSet1.FieldCount - 1 do
        TempTable.Fields[i].AsString := JvCSVDataSet1.Fields[i].AsString;
      JvCSVDataSet1.Next;  
    end;
    
    // Post the last row appended when the loop above exited
    if TempTable.State in dsEditModes then
      TempTable.Post;
    

    【讨论】:

      【解决方案6】:

      在 Delphi 7 中,您可以使用 Turbo Power SysTools TStAnsiTextStream() 以面向行的方式读取和写入,但使用线程安全的 TStream 实现而不是不安全的旧 pascal 文件接口。在以后的 Delphi 版本中,您会在标准 RTL 中找到类似的东西(尽管它们的实现略有不同),但 Delphi 7 并没有提供太多的文本文件操作。

      【讨论】:

      • 继续像 1983 年一样继续编程,自 1995 年以来不要在多线程操作系统中使用线程,并确保您的函数不是线程安全的。然后问问自己为什么 Delphi 对大多数人来说已经过时了。是时候忘记您的 Turbo Pascal“技能”并学习如何编写 XXI 世纪的应用程序了。 Embarcadero 不应该删除指针和其他低端但强大的功能,而是应该开始删除旧的、不安全的 Turbo Pascal 功能,如 Assign() 和朋友。但是可怜的、老的、过时的“Delphi”开发人员需要学习如何编码......
      猜你喜欢
      • 2018-06-07
      • 2011-01-17
      • 1970-01-01
      • 2012-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-29
      相关资源
      最近更新 更多