我正在寻找关于在内存中保存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>
呢?这会是一个更好的解决方案吗?
答案 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加载到数据集中可能是代码量最少但会有更多开销。
您可能需要考虑标头的简单Tstringlist
或TList<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)。