CSV表最合适的数据结构?

时间:2011-11-18 18:33:20

标签: delphi csv

我正在寻找关于在内存中保存CSV(逗号分隔值)表的最合适数据结构的建议。 它应该涵盖两种情况:带有和没有标题的表。 如果表包含标题,则所有行的所有字段都由key->值对确定,其中键是标题中的名称,value是字段的适当内容。 如果表格不包含标题,则行只是字符串列表或者生成键名称的key->值对(如'COL1','COL2',...'COLn')。

我正在寻找同时最简单(代码少)和最通用的解决方案。

我正在考虑以下子类化,但怀疑它是否是正确/有效的实现方式:

TCSV = class (TObjectList<TDictionary<string, string>>)
  ...
public
  constructor Create(fileName: string; header: Boolean; encoding: string = '';
                     delimiter: Char = ';'; quoteChar: Char = '"'); overload;
  ...
end;

看起来我必须为每一行字段保留键。那么TDictionary<string, TStringList>呢?这会是一个更好的解决方案吗?

6 个答案:

答案 0 :(得分:5)

TClientDataset怎么样?似乎很容易。

可以在此处找到关于如何use TClientDataSet as an in-memory dataset的简单指南。

答案 1 :(得分:3)

您提议的结构意味着您的csv文件中的每一行都有一个TDictionary实例。实质上是复制每一行的列名。看起来有点浪费。

假设使用TDictionary<string, TStringList>,您将使用单个列中的值填充每个TStringList。这可能有效,但是对于每行数据迭代所有列仍然不容易。

正如GolezTrol建议的那样,TClientDataSet是Delphi的标准配置,非常强大,并且是一个用于柱状数据的数据集。此外,虽然它是一个数据集,但它不需要数据库(连接),并且在许多应用程序中用于您要实现的目标:内存数据集。

答案 2 :(得分:3)

我建议您尝试使用TJvCsvDataSet,我编写并贡献给JEDI JVCL。它适用于带或不带标题的CSV文件。它适用于数据感知控件,包括DB Grids。

它解析CSV数据,完全类似于其他人建议的客户数据集。

在内部,它使用一个字节记录数组并解析每一行并保持整数“查找”,以便它知道每个列在该特定行上的开始位置。这样可以将另一个值(修改一行中的字段)的一个值更改为非常快速的操作。

它支持大多数常见的字段类型(虽然现在不是blob或货币),它解析CSV功能,包括字段值内的嵌入式回车+换行符,以及嵌入式CSV“转义码”,以便您可以放置​​双倍例如,在字符串中引用字符。

它有一个名为FieldDef的属性,可以用来定义列的类型,或者它可以简单地读取文件的标题,并将每个值作为字符串处理(如果你不另外告诉它)

它可以通过添加或删除列来修改CSV,并执行您想要对CSV表执行的大多数常见操作。我已经使用它并对其进行了大量测试,并且工作正常。

答案 3 :(得分:1)

根据使用情况而不是TDataSet,您也可以使用Synopse TSynBigTable,它更具有性能并且具有更少的限制。

对于没有“时间或大小关键”的应用程序,TDataSet是可以的。

答案 4 :(得分:0)

所以你基本上希望能够访问以下元素:

for RowNum := 0 to csv.Count - 1 do
begin
  Name := csv[RowNum]['Name'];
  // Do something
end;

TObjectList<TDictionary<string, string>>肯定会完成这项工作,但效率不高。

将csv加载到数据集中可能是代码量最少但会有更多开销。

您可能需要考虑标头的简单TstringlistTList<string>的组合,并将数据分解为在其构造函数中采用标头列表的新类。你会得到相同的结果:

TCSVRow = class
private
  FHeaders: TList<string>;
  FFields: TList<string>;
public
  constructor(Headers: TList<string>);
  function GetField(index: string): string;
  property Fields[index: string]: string read GetField; default;
end;

TCSV = class
private
  FHeaders: TList<string>;
  FRows:TList<TCSVRow>;
public
  function GetRow(Index: integer):TCSVRow;
  property Rows[index: integer]:TCSVRow read GetRow; default;
end;

implementation

function TCSVRow.GetField(index: string): string;
begin
  Result := FFields[FHeaders.IndexOf(index)];
end;

function TCSV.GetRow(Index:integer):TCSVRow;
begin
  Result := FRows[Index];
end;

这是不完整的,我直接输入浏览器,所以我没有测试它的正确性,但你得到了一般的想法。这样,标题信息只存储一次,而不是每行重复。

您可以通过使FFields成为字符串数组而不是TList<string>来保存一小部分内存,但TList<string>更容易与恕我直言合作。

<强>更新 第二个想法David有一点意义。 CSVRow类可以被删除。你可以简单地拥有TList<TList<string>>或2d数组。无论哪种方式,我仍然认为你应该将标题保存在单独的列表中。在这种情况下,TCSV看起来更像:

TCSV = class
private
  FHeaders: TList<string>;
  FData:TList<TList<string>>;
public
  function GetData(Row: integer; Column:string):string;
  property Data[Row: integer; Column:string]:string read GetData; default;
end;

function TCSV.GetData(Row: integer; Column:string):string;
begin
  Result := FData[Row][FHeaders.IndexOf(Column)];
end;

答案 5 :(得分:0)

有很多可能的解决方案。 如果你想要一些非常简单和通用的东西(根据你的要求)(不一定是最好的解决方案),为什么不只是...

TMyRec =
record
  HeaderNames: array of string;
  StringValues: array of array of string
end;

只需根据需要设置数组的长度(使用SetLength)。