delphi CSV到字符串网格内存不足

cedebl8k  于 2023-02-12  发布在  其他
关注(0)|答案(1)|浏览(175)

我在将CSV加载到StringGrid中时遇到了问题。有时候,它会耗尽内存,而且似乎每个值后面都有空白列。我还没有真正从CSV中读取数据,而不是输出到CSV中,所以我在线获取了一个股票示例,并根据需要对其进行了修改。
这是我目前得到的:

procedure x.LoadCSVtoGrid(ACSVFile : String; AStringGrid: TStringGrid)
var
  LRowIndex, LColIndex: Integer;
  LStrLine: string;
  LFile: TStringList;
begin
  AStringGrid.RowCount := 0;
  AStringGrid.ColCount := 0;
  if not FileExists(ACSVFile) then
    exit;
  LFile := TStringList.Create;
  try
    LFile.LoadFromFile(ACSVFile);
      if LFile.Count = 0 then
        exit;
      AStringGrid.ColCount := Max(AStringGrid.ColCount, WordCount(LFile[0], [',', '"'], '"'));
      AStringGrid.RowCount := LFile.Count;
      for LRowIndex := 0 to LFile.Count - 1 do
      begin
        LStrLine := LFile[LRowIndex];
        LColIndex := 0;
        while LStrLine <> '' do
        begin
          if Pos('"', LStrLine) = 1 then
          begin
            Delete(LStrLine, 1, 1);
            AStringGrid.Cells[LColIndex, LRowIndex] := Copy(LStrLine, 1, Pos('"', LStrLine) - 1);
            Delete(LStrLine, 1, Pos('"', LStrLine));
          end
          else
          begin
            AStringGrid.Cells[LColIndex, LRowIndex] := Copy(LStrLine, 1, Pos(',', LStrLine) - 1);
            Delete(LStrLine, 1, Pos(',', LStrLine));
          end;
          Inc(LColIndex);
        end;
      end;
  finally
    LFile.Free;
  end;

对于较小的CSV文件,它做得很好。我想它以前阅读250-300行。它现在要处理的一些文件是500+。
老实说,在CSV导入到StringGrid之前,我不会对它的数据做太多的处理,但是一旦它进入StringGrid,它就被验证了。我必须确保语音标记中的逗号,即"text, here",被忽略,因为它是值的一部分。同样,这看起来很好地处理了阅读。
另一个我认为我可能会遇到的问题是AStringGrid.RowCount := LFile.Count;,因为一些CSV文件有空行。如果有办法处理这个问题,我很乐意接受建议。
有几个版本的CSV文件,它应该能够读取,即计算列计数等。代码WordCount:

function x.WordCount(const S: string; const WordDelims: TSysCharSet; const QuoteChar: Char) : Integer;
var
  LInWord: Boolean;
  LQuoteOpen: Boolean;
  i: Integer;
begin
  Result := 0;
  LInWord := False;
  LQuoteOpen := False;
  for i := 1 to Length(S) do
  begin
    if S[i] in WordDelims then
    begin
      if not LInWord or LQuoteOpen then
        LInWord := False
      else
      begin
        LInWord := True;
          Inc(Result);
      end;
    end
    else
    begin
      if S[i] = QuoteChar then
        LQuoteOpen := not LQuoteOpen;
      LInWord := True;
    end;
  end;
  if LInWord and (not LQuoteOpen) then
    Inc(Result);

我尝试了多个文件,大多数情况下,这个问题只发生在包含更多内容的较大CSV文件中。我尝试了各种版本的CSV到StringGrid过程,以查看上面的示例是否存在一些固有的错误。该示例工作正常,但仅适用于较小的文件。
如果你需要更多的信息告诉我。

gcuhipw9

gcuhipw91#

1.内存问题
首先创建一个TStringList,然后向其加载数据
一米一米一x一米二米一x
因为要将整个文件加载到字符串列表中,所以需要这么多内存,另外还要在TStringGrid中保存同样多的数据。
通过分块读取文件来减少内存需求,比如说,一次读取1000行,然后可以在将它们移动到字符串网格后将其丢弃。
OTOH,你的"内存不足"问题也可能是由你的代码中的错误引起的。我在运行你的未修改代码时,我的非常小的测试文件遇到了"内存不足"的错误。
1.代码问题
在我的测试中,我使用了一个简单的文件,其中包含几条记录和一个位于不同位置的带引号的字段。

one,two,"text, including comma",four,five
one,two,three,four,five
"text, including comma",two,three,four,five
one,two,three,four,"text, including comma"

通过调用WordCount()函数(将字符串列表中的第一个字符串传递给该函数),可以确定TStringGrid中所需的列数。

WordCount(const S: string; const WordDelims: TSysCharSet; const QuoteChar: Char) : Integer;

当我传入第一个测试字符串时,

'one,two,three,four,five',

WordCount正确返回5
然后,控制返回到LoadCSVtoGrid(),在赋值AStringGrid.ColCountRowCount之后,for LRowIndex循环开始用当前行的数据填充网格。

AStringGrid.Cells[LColIndex, LRowIndex] := Copy(LStrLine, 1, Pos(',', LStrLine) - 1);
        Delete(LStrLine, 1, Pos(',', LStrLine));

Delete()从LStrLine的开头删除到Pos(',', LStrLine)。这对于项"一"、"二"、"三"和"四"是可以的,但对于"五"则不行,因为最后一项后面没有逗号。
这是代码中的主要缺陷,因为它不会删除最后一项。相反,由于循环运行while LString <> '',它只是继续递增LColIndex
在我的机器上,它在几分钟后停止,并出现内存不足错误。
下面是我对WordCount(重命名为WordCountNew)函数的看法:

function TForm50.WordCountNew(const s: string; const Delimiter: Char;
  const QuoteChar: Char): Integer;
var
  InWord, InQuote: boolean;
  i: integer;
begin
  if s = '' then              // Just in case we are fed an empty string
    Exit(0);

  Result := 1;                // Init, at least one data item
  InWord := False;            // Init
  InQuote:= False;            // Init

  for i := 1 to Length(s) do
  begin

    if s[i] = QuoteChar then  // The field is quoted
      InQuote := not InQuote; // make note about it

    if s[i] = Delimiter then  // Delimiter found
    begin
      if not InQuote then     // ... but only count it,
        inc(Result);          // if not within a quote
    end;

  end;
end;

然后执行LoadCSVtoGrid过程:

procedure TForm50.LoadCSVtoGrid(ACSVFile: String; AStringGrid: TStringGrid);
var
  LRowIndex, LColIndex: Integer;
  LStrLine: string;
  LFile: TStringList;
  CommaPos: integer;  // added 
begin
  AStringGrid.RowCount := 0;
  AStringGrid.ColCount := 0;
  if not FileExists(ACSVFile) then
    exit;
  LFile := TStringList.Create;
  try
    LFile.LoadFromFile(ACSVFile);
      if LFile.Count = 0 then
        exit;
//  When determining column count we should ONLY count the field separator, comma.
//  A quote character is not an indication of a new column / field.
//  Therefore we remove the array of chars, `[',', '"']` and replace with `','`
//      AStringGrid.ColCount := Max(AStringGrid.ColCount, WordCount(LFile[0], [',', '"'], '"'));
      AStringGrid.ColCount := Max(AStringGrid.ColCount, WordCountNew(LFile[0], ',', '"'));
      AStringGrid.RowCount := LFile.Count;
      for LRowIndex := 0 to LFile.Count - 1 do
      begin
        LStrLine := LFile[LRowIndex];
        LColIndex := 0;

        while LStrLine <> '' do
        begin
          if Pos('"', LStrLine) = 1 then
          begin
            Delete(LStrLine, 1, 1);
            AStringGrid.Cells[LColIndex, LRowIndex] := Copy(LStrLine, 1, Pos('"', LStrLine) - 1);
            AStringGrid.UpdateControlState;
            Delete(LStrLine, 1, Pos('"', LStrLine));
            Delete(LStrLine, 1, Pos(',', LStrLine));
          end
          else
          begin
            CommaPos := Pos(',', LStrLine);
            if CommaPos = 0 then CommaPos := Length(LStrLine)+1;
            AStringGrid.Cells[LColIndex, LRowIndex] := Copy(LStrLine, 1, CommaPos-1); //Pos(',', LStrLine) - 1);
            AStringGrid.UpdateControlState;
            Delete(LStrLine, 1, CommaPos); // Pos(',', LStrLine));
          end;
          Inc(LColIndex);
        end;
      end;
  finally
    LFile.Free;
  end;
end;

我添加了CommaPos变量,以便更容易人工模拟字符串末尾的逗号。
通过这些更改,测试文件将正确读入网格。

相关问题