读取CSV文件并将值存储到数组中

437

我正试图读取一个 *.csv 文件。

*.csv 文件由两列组成,用分号(;)分隔。

我可以使用 StreamReader 读取 *.csv 文件,并使用 Split() 函数将每一行分开。我希望将每个列存储到不同的数组中,然后显示出来。

这种操作是否可行?


2
@Marc:不幸的是,在非英语文化中(例如意大利),当您将Excel保存为CSV时,它使用“;”作为分隔符...我认为这使得CSV成为了一种非标准格式 :( - digEmAll
31
即使文件中没有使用逗号作为分隔符,人们仍然称其为CSV,因此我通常将其读作字符分隔值。而且,实际应用中有许多不同的引用或转义规则口音,所以即使在理论上有RFC,你也无法真正谈论一个标准。 - CodesInChaos
6
CSV文件扩展名现在应更改为DSV - 分隔符分隔的值 文件。 - Ambuj
2
对于那些仅仅在分隔符字符上拆分字符串的答案,这不是最好的方法。CSV格式有更多规则,这种方法无法涵盖所有情况。最好使用第三方解析器。更多信息请参见- https://dotnetcoretutorials.com/2018/08/04/csv-parsing-in-net-core/ - John Smith
21个回答

576

你可以这样做:

using System.IO;

static void Main(string[] args)
{
    using(var reader = new StreamReader(@"C:\test.csv"))
    {
        List<string> listA = new List<string>();
        List<string> listB = new List<string>();
        while (!reader.EndOfStream)
        {
            var line = reader.ReadLine();
            var values = line.Split(';');

            listA.Add(values[0]);
            listB.Add(values[1]);
        }
    }
}

8
谢谢您,我忘记了如何在CSV文件中拆分行(我真是太蠢了!),但是您的解决方案帮助了我 :) - Hallaghan
10
现在已经过去3年多了,这个问题仍在帮助着某些人。我感到很难过你没有得到采纳。 - AdamMc331
26
无法处理带有逗号等的字段值。 - Mike
14
这里应该使用 using 语句,或者至少手动调用 Close() 方法关闭 reader 对象,因为它是一个可被释放的资源(IDisposable)。 - Assaf Israel
47
这也无法正确解析类似于 column1;"字符串中有特殊;字符";column3 的CSV格式 - https://tools.ietf.org/html/rfc4180 - Ole K
显示剩余6条评论

278

我最喜欢的CSV解析器是.NET库内置的解析器。它是Microsoft.VisualBasic命名空间中的一个隐藏宝藏。 以下是样例代码:

using Microsoft.VisualBasic.FileIO;

var path = @"C:\Person.csv"; // Habeeb, "Dubai Media City, Dubai"
using (TextFieldParser csvParser = new TextFieldParser(path))
{
 csvParser.CommentTokens = new string[] { "#" };
 csvParser.SetDelimiters(new string[] { "," });
 csvParser.HasFieldsEnclosedInQuotes = true;

 // Skip the row with the column names
 csvParser.ReadLine();

 while (!csvParser.EndOfData)
 {
  // Read current line fields, pointer moves to the next line.
  string[] fields = csvParser.ReadFields();
  string Name = fields[0];
  string Address = fields[1];
 }
}

请记得添加对 Microsoft.VisualBasic 的引用

关于解析器的更多细节,请参见此处:http://codeskaters.blogspot.ae/2015/11/c-easiest-csv-parser-built-in-net.html


7
我最喜欢这个选项。因为这个类是一个CSV解析器,而不是手动构建的东西,所以我不需要担心转义字符。 - Timothy Gonzalez
30
如果有人遇到这个问题并且想知道解决方法,那么你需要包含对 Microsoft.VisualBasic 框架程序集的引用,因为它通常不会被默认引用。 - apokryfos
3
如果我能记起以前使用VB6的经验,那么这些年来就可以省下很多时间了。尽管有些人可能会对VB进行抱怨,但如果它有价值,我在我的代码中添加dll和命名空间并没有任何问题。这个工具有很大的价值。 - Walter
3
这个解决方案非常成功。从我的经验来看,它是一个非常可靠的解析器。 - Glenn Ferrie
4
为什么只能使用VB DLL? - Mark Choi
显示剩余2条评论

91

LINQ的写法:

var lines = File.ReadAllLines("test.txt").Select(a => a.Split(';'));
var csv = from line in lines
          select (from piece in line
                  select piece);

^^错误 - 由Nick编辑

原本回答者试图用一个二维数组(包含多个数组)来填充csv。第一个数组中的每个项目都包含一个表示该行号的数组,嵌套数组中的每个项目都包含特定列的数据。

var csv = from line in lines
          select (line.Split(',')).ToArray();

17
.NET 1.1?很抱歉听到这个消息。 - contactmatt
6
我不会反驳你的观点。 - B. Clay Shannon-B. Crow Raven
11
我想指出CSV文件的字段内容可能会用引号括起来,因此使用string.Split方法并不是一个可行的选项。 - Alxandr
5
我收到的错误信息是:'System.Array'不包含名为'Split'的定义,并且找不到接受类型为'System.Array'的第一个参数的任何扩展方法'Split'(是否缺少using指令或程序集引用?) - Kala J
4
您看到的“System.Array doesn't contain a definition”错误是因为变量“lines”是一个 IEnumerable(string []),因此“line”基本上是一个只包含一个元素的字符串数组。只需将代码更改为 var csv = from line in lines select (line.Split(',')).ToArray(); 即可解决问题。这将把每个元素拆分成一个字符串数组,并将结果存储在“csv”变量中。 - Zein Sleiman
显示剩余4条评论

58

3
这是最好的答案!强大的库易于应用和使用。 - Tyler Forsythe
3
CsvHelper库非常棒。使用起来超级快速和简单。 - Steve Parish
3
如果您正在寻找一种可以处理csv格式的每个方面(包括带引号的字符串)的库,请使用这个。太棒了! - Matt
谢谢,这个库真的很棒,易于使用且非常稳健。 - Sebastián Guerrero
3
性能如何与Microsoft.VisualBasic.FileIO.TextFieldParser相比较?(参考@Habeeb的答案) - bovender
那个维基链接已经被删除了。https://joshclose.github.io/CsvHelper/上的API参考仍然显示即将推出,尽管声称它会回退到与Excel相匹配的解析行为,但我无法让它打开一个Excel没有困难解析的csv文件。它无法处理逗号后跟空格字符分隔的值。 - RichardB

41

你不能立即创建一个数组,因为你需要从一开始就知道行数(这将需要两次读取csv文件)

你可以将值存储在两个List<T>中,然后使用它们或者使用List<T>.ToArray()将其转换成数组。

非常简单的示例:

var column1 = new List<string>();
var column2 = new List<string>();
using (var rd = new StreamReader("filename.csv"))
{
    while (!rd.EndOfStream)
    {
        var splits = rd.ReadLine().Split(';');
        column1.Add(splits[0]);
        column2.Add(splits[1]);
    }
}
// print column1
Console.WriteLine("Column 1:");
foreach (var element in column1)
    Console.WriteLine(element);

// print column2
Console.WriteLine("Column 2:");
foreach (var element in column2)
    Console.WriteLine(element);

请注意,这只是一个非常简单的例子。使用 string.Split 并不能解决某些记录内部包含分隔符 ; 的情况。为了更安全的方法,请考虑使用一些特定于 csv 的库,如 nuget 上的 CsvHelper。


1
不考虑 ; 作为值的一部分,例如 "value with ; inside it"。CSV 使用双引号包围包含特殊字符的值,以表示它是一个字面字符串。 - ChickenFeet
2
@ChickenFeet:当然,这就是标题为“非常简单的例子”的原因。无论如何,我可以添加一条注释;) - digEmAll
1
没关系,我注意到这里还有很多其他答案也没有考虑到它 :) - ChickenFeet
2
Regex.Split(sr.ReadLine(), ",(?=(?:[^"]"[^"]")[^"]$)"); //在SO上找到这个...比库更快。 - Pinch

36

我通常使用来自CodeProject的解析器,因为它可以处理许多字符转义和类似问题。


3
这个东西非常好而且速度很快。如果你处于商业环境下并需要高效工作,可以使用它。 - gjvdkamp
10
如果您不想注册CodeProject来下载它,可以在Nuget gallery中找到此解析器,名称为LumenWorks.Framework.IO。 - Greg McCoy

34

这是我对得票最高答案的改编:

var contents = File.ReadAllText(filename).Split('\n');
var csv = from line in contents
          select line.Split(',').ToArray();

然后,csv变量可以像以下示例中使用:

int headerRows = 5;
foreach (var row in csv.Skip(headerRows)
    .TakeWhile(r => r.Length > 1 && r.Last().Trim().Length > 0))
{
    String zerothColumnValue = row[0]; // leftmost column
    var firstColumnValue = row[1];
}

你如何访问csv变量中的行和列? - Matthew Lock
1
你如何处理转义逗号? - Kuangwei Zhang
不处理列内的逗号。最好使用强大的库CsvHelper,如joshb的答案建议。 - Tim Partridge

12

如果您需要跳过行和/或列,可以使用以下代码创建一个二维数组:

    var lines = File.ReadAllLines(path).Select(a => a.Split(';'));
    var csv = (from line in lines               
               select (from col in line
               select col).Skip(1).ToArray() // skip the first column
              ).Skip(2).ToArray(); // skip 2 headlines

如果您需要在进一步处理数据之前对其进行整形,这将非常有用(假设前两行是标题,第一列是行标题 - 由于您只想考虑数据,因此不需要在数组中拥有行标题)。

N.B. 您可以使用以下代码轻松获取标题和第一列:

    var coltitle = (from line in lines 
                    select line.Skip(1).ToArray() // skip 1st column
                   ).Skip(1).Take(1).FirstOrDefault().ToArray(); // take the 2nd row
    var rowtitle = (from line in lines select line[0] // take 1st column
                   ).Skip(2).ToArray(); // skip 2 headlines
此代码示例假设您的*.csv文件具有以下结构:

CSV Matrix

注意: 如果您需要跳过空行 - 有时很方便,您可以通过插入进行操作。
    where line.Any(a=>!string.IsNullOrWhiteSpace(a))

在上面的LINQ代码示例中,fromselect语句之间。


11

您可以在C#中使用Microsoft.VisualBasic.FileIO.TextFieldParser dll以获得更好的性能。

从上述文章中获取下面的代码示例:

static void Main()
{
    string csv_file_path=@"C:\Users\Administrator\Desktop\test.csv";

    DataTable csvData = GetDataTabletFromCSVFile(csv_file_path);

    Console.WriteLine("Rows count:" + csvData.Rows.Count);

    Console.ReadLine();
}


private static DataTable GetDataTabletFromCSVFile(string csv_file_path)
{
    DataTable csvData = new DataTable();

    try
    {

    using(TextFieldParser csvReader = new TextFieldParser(csv_file_path))
        {
            csvReader.SetDelimiters(new string[] { "," });
            csvReader.HasFieldsEnclosedInQuotes = true;
            string[] colFields = csvReader.ReadFields();
            foreach (string column in colFields)
            {
                DataColumn datecolumn = new DataColumn(column);
                datecolumn.AllowDBNull = true;
                csvData.Columns.Add(datecolumn);
            }

            while (!csvReader.EndOfData)
            {
                string[] fieldData = csvReader.ReadFields();
                //Making empty value as null
                for (int i = 0; i < fieldData.Length; i++)
                {
                    if (fieldData[i] == "")
                    {
                        fieldData[i] = null;
                    }
                }
                csvData.Rows.Add(fieldData);
            }
        }
    }
    catch (Exception ex)
    {
    }
    return csvData;
}

9
这并不那么高效,因为Split无法像TextFieldParser那样做所有的事情。例如,跳过注释行、处理带引号的字段以及删除开头/结尾的空格。这不完全是一对一的比较。 - Robert McKee

8

我花了几个小时寻找一个合适的库,但最终我写了自己的代码 :) 你可以用任何工具读取文件(或数据库),然后对每一行应用以下例程:

private static string[] SmartSplit(string line, char separator = ',')
{
    var inQuotes = false;
    var token = "";
    var lines = new List<string>();
    for (var i = 0; i < line.Length; i++) {
        var ch = line[i];
        if (inQuotes) // process string in quotes, 
        {
            if (ch == '"') {
                if (i<line.Length-1 && line[i + 1] == '"') {
                    i++;
                    token += '"';
                }
                else inQuotes = false;
            } else token += ch;
        } else {
            if (ch == '"') inQuotes = true;
            else if (ch == separator) {
                lines.Add(token);
                token = "";
                } else token += ch;
            }
    }
    lines.Add(token);
    return lines.ToArray();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接