将CSV加载到StringGrid时遇内存不足及空白列问题求助
问题分析与解决方案
核心问题诊断
1. 内存不足的原因
TStringList一次性加载整个大文件到内存,占用大量资源- 提前将
StringGrid.RowCount设为文件总行数(包含空行),造成不必要的内存预分配 - 频繁的
Copy/Delete字符串操作生成大量临时对象,加剧内存消耗
2. 空白列的根源
WordCount函数逻辑错误,导致列数统计偏大- 行末尾无逗号时,循环提前结束,未填充的列残留空白
3. 空行处理问题
- 直接用
LFile.Count设置行总数,空行占用Grid行资源,还可能引发后续解析异常
修正后的完整代码
优化后的LoadCSVtoGrid过程
procedure x.LoadCSVtoGrid(ACSVFile: String; AStringGrid: TStringGrid); var LRowIndex, LColIndex, LGridRow: Integer; LStrLine: string; LStream: TStreamReader; LMaxCols: Integer; LIsEmptyLine: Boolean; begin AStringGrid.RowCount := 0; AStringGrid.ColCount := 0; if not FileExists(ACSVFile) then Exit; // 第一步:遍历文件统计最大列数,同时过滤空行 LMaxCols := 0; LStream := TStreamReader.Create(ACSVFile); try while not LStream.EndOfStream do begin LStrLine := Trim(LStream.ReadLine); LIsEmptyLine := (LStrLine = ''); if not LIsEmptyLine then LMaxCols := Max(LMaxCols, WordCount(LStrLine, [',', '"'], '"')); end; finally LStream.Free; end; if LMaxCols = 0 then Exit; // 全是空行的情况 AStringGrid.ColCount := LMaxCols; AStringGrid.RowCount := 0; // 初始化为0,按需动态添加行 // 第二步:逐行读取并解析到Grid LStream := TStreamReader.Create(ACSVFile); try LGridRow := 0; while not LStream.EndOfStream do begin LStrLine := LStream.ReadLine; LIsEmptyLine := (Trim(LStrLine) = ''); if LIsEmptyLine then Continue; // 跳过空行 // 动态添加行 AStringGrid.RowCount := AStringGrid.RowCount + 1; LColIndex := 0; while LStrLine <> '' do begin if LColIndex >= AStringGrid.ColCount then Break; // 防止列越界 if Pos('"', LStrLine) = 1 then begin Delete(LStrLine, 1, 1); // 处理转义引号("") if Pos('""', LStrLine) > 0 then LStrLine := StringReplace(LStrLine, '""', '"', [rfReplaceAll]); // 提取引号内内容并移除结束引号 AStringGrid.Cells[LColIndex, LGridRow] := Copy(LStrLine, 1, Pos('"', LStrLine) - 1); Delete(LStrLine, 1, Pos('"', LStrLine)); // 跳过引号后的逗号(如果存在) if LStrLine <> '' and (LStrLine[1] = ',') then Delete(LStrLine, 1, 1); end else begin // 处理非引号包裹的内容 if Pos(',', LStrLine) > 0 then begin AStringGrid.Cells[LColIndex, LGridRow] := Copy(LStrLine, 1, Pos(',', LStrLine) - 1); Delete(LStrLine, 1, Pos(',', LStrLine)); end else begin // 行末尾的最后一个字段 AStringGrid.Cells[LColIndex, LGridRow] := LStrLine; LStrLine := ''; end; end; Inc(LColIndex); end; // 清空当前行剩余列,消除空白列 while LColIndex < AStringGrid.ColCount do begin AStringGrid.Cells[LColIndex, LGridRow] := ''; Inc(LColIndex); end; Inc(LGridRow); end; finally LStream.Free; end; end;
修正后的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] = QuoteChar then begin LQuoteOpen := not LQuoteOpen; // 跳过转义的引号("") if (i < Length(S)) and (S[i+1] = QuoteChar) then Inc(i); Continue; end; if (S[i] in WordDelims) and not LQuoteOpen then begin if LInWord then begin Inc(Result); LInWord := False; end; end else begin if not LInWord then begin LInWord := True; // 初始化第一个字段的计数 if Result = 0 then Result := 1; end; end; end; // 统计最后一个字段 if LInWord and not LQuoteOpen then begin if Result = 0 then Result := 1 else if not (S[Length(S)] in WordDelims) then Inc(Result); end; end;
关键优化说明
内存优化
- 用
TStreamReader逐行读取文件,避免一次性加载大文件到内存 - 动态添加Grid行,减少不必要的内存预分配
- 优化字符串操作逻辑,减少临时对象生成
- 用
空白列修复
- 修正
WordCount的列数统计逻辑,准确计算字段数量 - 解析完每行后清空剩余列,消除残留空白
- 处理行末尾无逗号的场景,避免循环提前结束
- 修正
空行处理
- 遍历和解析阶段均跳过空行,不占用Grid资源
CSV兼容性提升
- 支持标准CSV的转义引号(
"")解析 - 添加列越界保护,避免访问无效Grid列
- 支持标准CSV的转义引号(
内容的提问来源于stack exchange,提问作者Eddy
相关产品推荐
相关产品推荐

