在.NET中处理大型csv的最有效方法

use*_*003 8 .net c# vb.net csv search

原谅我的愚蠢,但我只需要一些指导,我找不到另一个问题来回答这个问题.我有一个相当大的csv文件(约300k行),我需要确定给定的输入,csv中的任何行是否以该输入开头.我按字母顺序对csv进行了排序,但我不知道:

1)如何处理csv中的行 - 我应该将其作为列表/集合读取,还是使用OLEDB,嵌入式数据库或其他?

2)如何从按字母顺序排列的列表中有效地找到一些东西(使用它进行排序以加快速度的事实,而不是搜索整个列表)

Lou*_*cci 9

你没有提供足够的细节给你一个具体的答案,但......


如果CSV文件经常更改,则使用OLEDB并根据您的输入更改SQL查询.

string sql = @"SELECT * FROM [" + fileName + "] WHERE Column1 LIKE 'blah%'";
using(OleDbConnection connection = new OleDbConnection(
          @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + fileDirectoryPath + 
          ";Extended Properties=\"Text;HDR=" + hasHeaderRow + "\""))
Run Code Online (Sandbox Code Playgroud)

如果CSV文件不经常更改并且您对它运行了很多"查询",请将其加载到内存中并每次快速搜索它.

如果希望搜索与列完全匹配,请使用字典,其中键是要匹配的列,值是行数据.

Dictionary<long, string> Rows = new Dictionar<long, string>();
...
if(Rows.ContainsKey(search)) ...
Run Code Online (Sandbox Code Playgroud)

如果你想让你的搜索成为像StartsWith这样的部分匹配,那么就有1个数组包含你的可搜索数据(即:第一列)和另一个包含行数据的列表或数组.然后使用C#内置的二进制搜索http://msdn.microsoft.com/en-us/library/2cy9f6wb.aspx

string[] SortedSearchables = new string[];
List<string> SortedRows = new List<string>();
...
string result = null;
int foundIdx = Array.BinarySearch<string>(SortedSearchables, searchTerm);
if(foundIdx < 0) {
    foundIdx = ~foundIdx;
    if(foundIdx < SortedRows.Count && SortedSearchables[foundIdx].StartsWith(searchTerm)) {
        result = SortedRows[foundIdx];
    }
} else {
    result = SortedRows[foundIdx];
}
Run Code Online (Sandbox Code Playgroud)

注意代码是在浏览器窗口中编写的,可能包含语法错误,因为它未经过测试.


Ste*_*art 5

如果可以将数据缓存在内存中,并且只需要在一个主键列上搜索列表,我建议将数据作为Dictionary对象存储在内存中。在Dictionary类存储的数据作为在哈希表中的键/值对。您可以使用主键列作为字典中的键,然后使用其余列作为字典中的值。在哈希表中按键查找项目通常非常快。

例如,您可以将数据加载到字典中,如下所示:

Dictionary<string, string[]> data = new Dictionary<string, string[]>();
using (TextFieldParser parser = new TextFieldParser("C:\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData)
    {
        try
        {
            string[] fields = parser.ReadFields();
            data[fields[0]] = fields;
        }
        catch (MalformedLineException ex)
        {
            // ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以获取任何项目的数据,如下所示:

string fields[] = data["key I'm looking for"];
Run Code Online (Sandbox Code Playgroud)


小智 5

如果你每次运行程序只执行一次,这看起来非常快.(根据以下评论更新为使用StreamReader而不是FileStream)

    static string FindRecordBinary(string search, string fileName)
    {
        using (StreamReader fs = new StreamReader(fileName))
        {
            long min = 0; // TODO: What about header row?
            long max = fs.BaseStream.Length;
            while (min <= max)
            {
                long mid = (min + max) / 2;
                fs.BaseStream.Position = mid;

                fs.DiscardBufferedData();
                if (mid != 0) fs.ReadLine();
                string line = fs.ReadLine();
                if (line == null) { min = mid+1; continue; }

                int compareResult;
                if (line.Length > search.Length)
                    compareResult = String.Compare(
                        line, 0, search, 0, search.Length, false );
                else
                    compareResult = String.Compare(line, search);

                if (0 == compareResult) return line;
                else if (compareResult > 0) max = mid-1;
                else min = mid+1;
            }
        }
        return null;
    }
Run Code Online (Sandbox Code Playgroud)

对于600,000记录测试文件(50 megs),这在0.007秒内运行.相比之下,文件扫描平均超过半秒,具体取决于记录的位置.(100倍的差异)

显然,如果你不止一次这样做,缓存会加快速度.一种简单的部分缓存方法是保持StreamReader打开并重新使用它,每次只重置min和max.这样可以节省您在内存中存储50兆的时间.

编辑:添加了knaki02的建议修复.