2011-11-18 166 views
4

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

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

我在想下面的子类,但是怀疑它的实现的正确/有效的方法:

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

看起来有点像我的2D阵列........ –

+0

但是数组只能有一个整数索引。他希望能够按列名引用字段。 –

+0

@kenneth通过构建索引来解决这个问题 –

回答

5

TClientDataset怎么样?看起来很容易。

只是一个简单的指南,如何use TClientDataSet as an in-memory dataset,可以在这里找到。

+0

感谢您的回答。如果没有人提供更优雅的解决方案/想法,我会接受这个答案。 –

+0

就我个人而言,我发现[kbmMemTable](http://www.components4programmers.com/products/kbmmemtable/index.htm)比TClientDataSet更适合在内存数据集中使用。 –

+0

@LURD为什么呢? – GolezTrol

3

您提出的结构意味着您将在csv文件中的每一行都有一个TDictionary实例。本质上是复制每一行的列名称。似乎有点浪费。

假设与TDictionary<string, TStringList>您将填充每个TStringList与来自单个列的值。这可以起作用,但是要遍历每行数据的所有列仍然不是一件容易的事情。

正如GolezTrol所建议的,TClientDataSet来自Delphi的标准,功能非常强大,并且可以作为打算用于柱状数据的数据集。此外,虽然它是一个数据集,但它不需要数据库(连接),并且在许多应用程序中用于完全实现您要实现的目标:内存数据集。

+0

只是研究TClientDataSet的文档,它看起来很有前途。我也有点惊讶,如果这是在Delphi中的方式与Python中的字典/哈希用法和我从中提供的Ruby相反。我的确希望一些简单的类似于散列的数据类型,而不是像TClientDataSet那样的复杂类。 –

+0

这可能是因为德尔福的主要焦点始终是以数据(基础)为中心的业务应用程序。 IIRC Delphi是第一款使应用程序的数据库访问“轻而易举”的RAD IDE。并且不要被TClientDataSet的复杂性拖延。添加几个字段定义(列),然后“只”插入行,从您的csv读取。所有其他的东西,你可以忽略,直到你需要它。 –

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>更容易与IMHO一起使用。

更新 关于第二个想法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; 
1

根据使用情况,而不是TDataSet的你也可以使用Synopse TSynBigTable这是更perfomant而且具有更少的限制。

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

0

有很多可能的解决方案。 如果你想很简单的东西一般按你的要求(不一定是高档的解决方案),为什么不......

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

刚刚成立的数组长度根据需要(使用SetLength)。

+0

不幸的是,这也会重复存储每个记录的标题,并且在索引号中不能访问列名。 –

+0

嗨大卫。对不起,这个例子有点微不足道,因为我只描述了它的结构而不是背后的意图。它不会重复标题(它只是一个单维数组)。正如你正确指出的那样,StringValues确实需要通过整数索引来访问(这将是stringValues [recordnumber,fieldnumber],但是我们的想法是你会写一个函数,比如'FieldByName',它需要一个字符串,然后扫描HeaderNames对于fieldIndex,然后以这种方式访问​​数组。另外,您可能希望将其作为类和方法来执行,而不是记录和函数。 – Peter

3

我建议你尝试一下TJvCsvDataSet,它是我写的JEDI JVCL的贡献者。它适用于带或不带标题的CSV文件。它适用于数据感知控件,包括数据库网格。

它解析CSV数据,完全像其他人建议的客户端数据集一样工作。

它在内部使用一组字节记录并解析每一行并保留一个整数“lookup”,以便它知道每个单独列在该特定行上的开始位置。这使得改变一个值的另一个值(修改一行中的一个字段)是一个非常快的操作。

它支持大多数常见字段类型(尽管现在不是斑点或货币),它解析CSV功能,包括嵌入式回车+字段值内的换行和嵌入式CSV“转义代码”,以便您可以将例如,一个字符串中的双引号字符。

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

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