非常简单的C# CSV读取器

38

我想从一个CSV文件中创建一个数组。

这个问题非常简单,CSV文件只有一行,包含以下数值:

Device, SignalStrength, Location, Time, Age.

我想把这些值放到一维数组中。

我尝试了一些示例,但它们都比需要的更复杂。


3
有点像作业的味道 - 给我们展示一下你目前为止做了什么。 - annakata
“转换为一维数组”听起来有些奇怪,你确定要丢失换行符信息吗? - H H
@HenkHolterman,原帖中指出只会有一行。 - KingCronus
@KingCronus 我们已经到了2012年,那个问题已经有了一个被接受的答案,何必再费心呢? - Adrian Iftode
@AdrianIftode,我注意到这个问题是因为新答案(底部)将其放入了C#活动列表中。我没有挖掘这个问题,是他们挖掘的。 - KingCronus
请检查 https://dev59.com/nnA75IYBdhLWcg3wB0VP。 - adam
7个回答

65

你可以尝试类似下面的LINQ代码:

string[] allLines = File.ReadAllLines(@"E:\Temp\data.csv");

    var query = from line in allLines
                let data = line.Split(',')
                select new
                {
                    Device = data[0],
                    SignalStrength = data[1],
                    Location = data[2], 
                    Time = data[3],
                    Age = Convert.ToInt16(data[4])
                };

更新:随着时间的推移,事情发生了变化。目前,我更倾向于使用这个库http://www.aspnetperformance.com/post/LINQ-to-CSV-library.aspx


7
嗨Ramesh,你的解决方案非常棒,但是你如何转义逗号? - andrew0007
@andrew007 - 我脑海中首先想到的是使用正则表达式进行分割。请查看http://regexadvice.com/blogs/wayneking/archive/2004/01/12/271.aspx。 - Ramesh
14
@ramesh "有些人遇到问题时,会想:'我知道了,我来用正则表达式解决。' 现在他们又多了一个问题。" - Jamie Zawinski。请查看http://secretgeek.net/csv_trouble.asp。 - David Keaveny
LINQ-to-CSV绝对是最好的选择。使用起来非常简单! - Jonathon Reinhart

52
如果只有一行,可以像这样操作:
using System;
using System.IO;

class Program
{
    static void Main()
    {
        String[] values = File.ReadAllText(@"d:\test.csv").Split(',');
    }
}

73
不,不,不,这太过复杂了!! - ChaosPandion
106
如果任何一个字段包含逗号,这将失败。 - Petrus Theron
5
你如何处理换行? - nonsensickle
17
CSV列的值可以包含逗号,只要它们被双引号括起来,例如:"this is, a test"。 - Petrus Theron
13
这个答案是错误的,不应该被接受,因为它只能处理最基本的 CSV 文件,大多数能够创建 CSV 的系统将能够创建这个解决方案无法加载的文件类型。我正在寻找一个解决方案的文件属于其中之一。 - David Mårtensson
显示剩余6条评论

8

我写了一个简单的函数。它接受一个字符串CSV行并返回字段数组:

它可以很好地处理Excel生成的CSV文件以及许多其他变体。

    public static string[] ParseCsvRow(string r)
    {

        string[] c;
        string t;
        List<string> resp = new List<string>();
        bool cont = false;
        string cs = "";

        c = r.Split(new char[] { ',' }, StringSplitOptions.None);

        foreach (string y in c)
        {
            string x = y;


            if (cont)
            {
                // End of field
                if (x.EndsWith("\""))
                {
                    cs += "," + x.Substring(0, x.Length - 1);
                    resp.Add(cs);
                    cs = "";
                    cont = false;
                    continue;

                }
                else
                {
                    // Field still not ended
                    cs += "," + x;
                    continue;
                }
            }

            // Fully encapsulated with no comma within
            if (x.StartsWith("\"") && x.EndsWith("\""))
            {
                if ((x.EndsWith("\"\"") && !x.EndsWith("\"\"\"")) && x != "\"\"")
                {
                    cont = true;
                    cs = x;
                    continue;
                }

                resp.Add(x.Substring(1, x.Length - 2));
                continue;
            }

            // Start of encapsulation but comma has split it into at least next field
            if (x.StartsWith("\"") && !x.EndsWith("\""))
            {
                cont = true;
                cs += x.Substring(1);
                continue;
            }

            // Non encapsulated complete field
            resp.Add(x);

        }

        return resp.ToArray();

    }

1
当你有cell1,"cell2",",,,,,,,,"和"previous fails"时,这将失败。 - Mustafa

4

这个修复后的代码可以记住CSV行的最后一个元素 ;-)

(在一个包含5400行和每行26个元素的CSV文件中进行了测试)

   public static string[] CSVRowToStringArray(string r, char fieldSep = ',', char stringSep = '\"')  {
            bool bolQuote = false;
            StringBuilder bld = new StringBuilder();
            List<string> retAry = new List<string>();

            foreach (char c in r.ToCharArray())
                if ((c == fieldSep && !bolQuote))
                {
                    retAry.Add(bld.ToString());
                    bld.Clear();
                }
                else
                    if (c == stringSep)
                        bolQuote = !bolQuote;
                    else
                        bld.Append(c);

            /* to solve the last element problem */
            retAry.Add(bld.ToString()); /* added this line */
            return retAry.ToArray();
        }

3
这是我在一个项目中使用的内容,用于解析单行数据。
    private string[] csvParser(string csv, char separator = ',')
    {
        List <string> = new <string>();
        string[] temp = csv.Split(separator);
        int counter = 0;
        string data = string.Empty;
        while (counter < temp.Length)
        {
            data = temp[counter].Trim();
            if (data.Trim().StartsWith("\""))
            {
                bool isLast = false;
                while (!isLast && counter < temp.Length)
                {
                    data += separator.ToString() + temp[counter + 1];
                    counter++;
                    isLast = (temp[counter].Trim().EndsWith("\""));
                }
            }
            parsed.Add(data);
            counter++;
        }

        return parsed.ToArray();

    }

http://zamirsblog.blogspot.com/2013/09/c-csv-parser-csvparser.html


我认为第三行应该写成: List<string> parsed = new List<string>(); - Matiaan
很不错的尝试,但是在一行格式良好的代码上,由于数组越界而导致“out of array boundary”异常失败了 :(。 - AFract

2

我的解决方案处理引号、覆盖字段和字符串分隔符等问题。它简短而精炼。

    public static string[] CSVRowToStringArray(string r, char fieldSep = ',', char stringSep = '\"')
    {
        bool bolQuote = false;
        StringBuilder bld = new StringBuilder();
        List<string> retAry = new List<string>();

        foreach (char c in r.ToCharArray())
            if ((c == fieldSep && !bolQuote))
            {
                retAry.Add(bld.ToString());
                bld.Clear();
            }
            else
                if (c == stringSep)
                    bolQuote = !bolQuote;
                else
                    bld.Append(c);

        return retAry.ToArray();
    }

这个解决方案似乎会丢失行末的最后一个值,即使在没有引号的非常简单的测试中也是如此。 - AFract

0

首先需要了解什么是CSV以及如何编写它。

(大多数答案(目前全部)都没有使用这些要求,所以它们都是错误的!)

  1. 每个下一个字符串(/r/n)都是下一个“表”行。
  2. “表”单元格由某个分隔符号分隔。
  3. 作为分隔符可以使用任何符号。通常这是\t,
  4. 每个单元格可能包含该分隔符号在单元格内的情况(在这种情况下,单元格必须以双引号符号开头,并在结尾处有双引号)
  5. 每个单元格可能包含单元格内的/r/n符号(在这种情况下,单元格必须以双引号符号开头,并在结尾处有双引号)

不久前,我编写了一个基于标准Microsoft.VisualBasic.FileIO库的CSV读/写简单类。使用这个简单的类,您将能够像使用2维数组一样使用CSV。

使用我的库的简单示例:

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

您可以通过以下链接找到我的类,并了解它的编写方式: https://github.com/ukushu/DataExporter

这个库的代码非常快速,源代码也非常简短。

PS:同时,这个解决方案不适用于Unity。

PS2:另一个解决方案是使用“LINQ-to-CSV”库进行操作。它也应该能够很好地工作。但它会更大一些。


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