C#实现读写CSV文件的方法详解

目录
  • CSV文件标准
    • 文件示例
    • RFC 4180
    • 简化标准
  • 读写CSV文件
    • 使用CsvHelper
    • 使用自定义方法
  • 总结

项目中经常遇到CSV文件的读写需求,其中的难点主要是CSV文件的解析。本文会介绍CsvHelperTextFieldParser正则表达式三种解析CSV文件的方法,顺带也会介绍一下CSV文件的写方法。

CSV文件标准

在介绍CSV文件的读写方法前,我们需要了解一下CSV文件的格式。

文件示例

一个简单的CSV文件:

?

1

2

3

Test1,Test2,Test3,Test4,Test5,Test6

str1,str2,str3,str4,str5,str6

str1,str2,str3,str4,str5,str6

一个不简单的CSV文件:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

"Test1

"",""","Test2

"",""","Test3

"",""","Test4

"",""","Test5

"",""","Test6

"","""

" 中文,D23 ","3DFD4234""""""1232""1S2","ASD1"",""23,,,,213

23F32","

",,asd

" 中文,D23 ","3DFD4234""""""1232""1S2","ASD1"",""23,,,,213

23F32","

",,asd

你没看错,上面两个都是CSV文件,都只有3行CSV数据。第二个文件多看一眼都是精神污染,但项目中无法避免会出现这种文件。

RFC 4180

CSV文件没有官方的标准,但一般项目都会遵守 RFC 4180 标准。这是一个非官方的标准,内容如下:

Each record is located on a separate line, delimited by a line break (CRLF).

The last record in the file may or may not have an ending line break.

There maybe an optional header line appearing as the first line of the file with the same format as normal record lines. This header will contain names corresponding to the fields in the file and should contain the same number of fields as the records in the rest of the file (the presence or absence of the header line should be indicated via the optional "header" parameter of this MIME type).

Within the header and each record, there may be one or more fields, separated by commas. Each line should contain the same number of fields throughout the file. Spaces are considered part of a field and should not be ignored. The last field in the record must not be followed by a comma.

Each field may or may not be enclosed in double quotes (however some programs, such as Microsoft Excel, do not use double quotes at all). If fields are not enclosed with double quotes, then double quotes may not appear inside the fields.

Fields containing line breaks (CRLF), double quotes, and commas should be enclosed in double-quotes.

If double-quotes are used to enclose fields, then a double-quote appearing inside a field must be escaped by preceding it with another double quote.

翻译一下:

  • 每条记录位于单独的行上,由换行符 (CRLF) 分隔。
  • 文件中的最后一条记录可能有也可能没有结束换行符。
  • 可能有一个可选的标题行出现在文件的第一行,格式与普通记录行相同。此标题将包含与文件中的字段对应的名称,并且应包含与文件其余部分中的记录相同数量的字段(标题行的存在或不存在应通过此 MIME 类型的可选“标头”参数指示)。
  • 在标题和每条记录中,可能有一个或多个字段,以逗号分隔。在整个文件中,每行应包含相同数量的字段。空格被视为字段的一部分,不应忽略。记录中的最后一个字段后面不能有逗号。
  • 每个字段可以用双引号括起来,也可以不用双引号(但是某些程序,例如 Microsoft Excel,根本不使用双引号)。如果字段没有用双引号括起来,那么双引号可能不会出现在字段内。
  • 包含换行符 (CRLF)、双引号和逗号的字段应该用双引号括起来。
  • 如果使用双引号将字段括起来,则出现在字段中的双引号必须在其前面加上另一个双引号。

简化标准

上面的标准可能比较拗口,我们对它进行一些简化。要注意一下,简化不是简单的删减规则,而是将类似的类似进行合并便于理解。
后面的代码也会使用简化标准,简化标准如下:

  • 每条记录位于单独的行上,由换行符 (CRLF) 分隔。
  • 注:此处的行不是普通文本意义上的行,是指符合CSV文件格式的一条记录(后面简称为CSV行),在文本上可能占据多行。
  • 文件中的最后一条记录需有结束换行符,文件的第一行为标题行(标题行包含字段对应的名称,标题数与记录的字段数相同)。
  • 注:原标准中可有可无的选项统一规定为必须有,方便后期的解析,而且没有标题行让别人怎么看数据。
  • 在标题和每条记录中,可能有一个或多个字段,以逗号分隔。在整个文件中,每行应包含相同数量的字段空格被视为字段的一部分,不应忽略。记录中的最后一个字段后面不能有逗号
  • 注:此标准未做简化,虽然也有其它标准使用空格、制表符等做分割的,但不使用逗号分割的文件还叫逗号分隔值文件吗。
  • 每个字段都用双引号括起来,出现在字段中的双引号必须在其前面加上另一个双引号
  • 注:原标准有必须使用双引号和可选双引号的情况,那全部使用双引号肯定不会出错。*

读写CSV文件

在正式读写CSV文件前,我们需要先定义一个用于测试的Test类。代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

class Test

{

    public string Test1{get;set;}

    public string Test2 { get; set; }

    public string Test3 { get; set; }

    public string Test4 { get; set; }

    public string Test5 { get; set; }

    public string Test6 { get; set; }

    //Parse方法会在自定义读写CSV文件时用到

    public static Test Parse (string[]fields )

    {

        try

        {

            Test ret = new Test();

            ret.Test1 = fields[0];

            ret.Test2 = fields[1];

            ret.Test3 = fields[2];

            ret.Test4 = fields[3];

            ret.Test5 = fields[4];

            ret.Test6 = fields[5];

            return ret;

        }

        catch (Exception)

        {

            //做一些异常处理,写日志之类的

            return null;

        }

    }

}

生成一些测试数据,代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

static void Main(string[] args)

{

    //文件保存路径

    string path = "tset.csv";

    //清理之前的测试文件

    File.Delete("tset.csv");

       

    Test test = new Test();

    test.Test1 = " 中文,D23 ";

    test.Test2 = "3DFD4234\"\"\"1232\"1S2";

    test.Test3 = "ASD1\",\"23,,,,213\r23F32";

    test.Test4 = "\r";

    test.Test5 = string.Empty;

    test.Test6 = "asd";

    //测试数据

    var records = new List<Test> { test, test };

    //写CSV文件

    /*

    *直接把后面的写CSV文件代码复制到此处

    */

    //读CSV文件

     /*

    *直接把后面的读CSV文件代码复制到此处

    */

    

    Console.ReadLine();

}

使用CsvHelper

CsvHelper 是用于读取和写入 CSV 文件的库,支持自定义类对象的读写。

github上标星最高的CSV文件读写C#库,使用MS-PL、Apache 2.0开源协议。

使用NuGet下载CsvHelper,读写CSV文件的代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

//写CSV文件

using (var writer = new StreamWriter(path))

using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))

{

    csv.WriteRecords(records);

}

using (var writer = new StreamWriter(path,true))

using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))

{

    //追加

    foreach (var record in records)

    {

        csv.WriteRecord(record);

    }

}

//读CSV文件

using (var reader = new StreamReader(path))

using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))

{

    records = csv.GetRecords<Test>().ToList();

    //逐行读取

    //records.Add(csv.GetRecord<Test>());

}

如果你只想要拿来就能用的库,那文章基本上到这里就结束了。

使用自定义方法

为了与CsvHelper区分,新建一个CsvFile类存放自定义读写CSV文件的代码,最后会提供类的完整源码。CsvFile类定义如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

/// <summary>

/// CSV文件读写工具类

/// </summary>

public class CsvFile

{

    #region 写CSV文件

    //具体代码...

    #endregion

    #region 读CSV文件(使用TextFieldParser)

    //具体代码...

    #endregion

    #region 读CSV文件(使用正则表达式)

    //具体代码...

    #endregion

}

基于简化标准的写CSV文件

根据简化标准(具体标准内容见前文),写CSV文件代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

#region 写CSV文件

//字段数组转为CSV记录行

private static string FieldsToLine(IEnumerable<string> fields)

{

    if (fields == null) return string.Empty;

    fields = fields.Select(field =>

    {

        if (field == null) field = string.Empty;

        //简化标准,所有字段都加双引号

        field = string.Format("\"{0}\"", field.Replace("\"", "\"\""));

        //不简化标准

        //field = field.Replace("\"", "\"\"");

        //if (field.IndexOfAny(new char[] { ',', '"', ' ', '\r' }) != -1)

        //{

        //    field = string.Format("\"{0}\"", field);

        //}

        return field;

    });

    string line = string.Format("{0}{1}", string.Join(",", fields), Environment.NewLine);

    return line;

}

//默认的字段转换方法

private static IEnumerable<string> GetObjFields<T>(T obj, bool isTitle) where T : class

{

    IEnumerable<string> fields;

    if (isTitle)

    {

        fields = obj.GetType().GetProperties().Select(pro => pro.Name);

    }

    else

    {

        fields = obj.GetType().GetProperties().Select(pro => pro.GetValue(obj)?.ToString());

    }

    return fields;

}

/// <summary>

/// 写CSV文件,默认第一行为标题

/// </summary>

/// <typeparam name="T"></typeparam>

/// <param name="list">数据列表</param>

/// <param name="path">文件路径</param>

/// <param name="append">追加记录</param>

/// <param name="func">字段转换方法</param>

/// <param name="defaultEncoding"></param>

public static void Write<T>(List<T> list, string path,bool append=true, Func<T, bool, IEnumerable<string>> func = null, Encoding defaultEncoding = null) where T : class

{

    if (list == null || list.Count == 0) return;

    if (defaultEncoding == null)

    {

        defaultEncoding = Encoding.UTF8;

    }

    if (func == null)

    {

        func = GetObjFields;

    }

    if (!File.Exists(path)|| !append)

    {

        var fields = func(list[0], true);

        string title = FieldsToLine(fields);

        File.WriteAllText(path, title, defaultEncoding);

    }

    using (StreamWriter sw = new StreamWriter(path, true, defaultEncoding))

    {

        list.ForEach(obj =>

        {

            var fields = func(obj, false);

            string line = FieldsToLine(fields);

            sw.Write(line);

        });

    }

}

#endregion

使用时,代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

//写CSV文件

//使用自定义的字段转换方法,也是文章开头复杂CSV文件使用字段转换方法

CsvFile.Write(records, path, true, new Func<Test, bool, IEnumerable<string>>((obj, isTitle) =>

{

    IEnumerable<string> fields;

    if (isTitle)

    {

        fields = obj.GetType().GetProperties().Select(pro => pro.Name + Environment.NewLine + "\",\"");

    }

    else

    {

        fields = obj.GetType().GetProperties().Select(pro => pro.GetValue(obj)?.ToString());

    }

    return fields;

}));

//使用默认的字段转换方法

//CsvFile.Write(records, path);

你也可以使用默认的字段转换方法,代码如下:

?

1

CsvFile.Save(records, path);

使用TextFieldParser解析CSV文件

TextFieldParser是VB中解析CSV文件的类,C#虽然没有类似功能的类,不过可以调用VB的TextFieldParser来实现功能。

TextFieldParser解析CSV文件的代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

#region 读CSV文件(使用TextFieldParser)

/// <summary>

/// 读CSV文件,默认第一行为标题

/// </summary>

/// <typeparam name="T"></typeparam>

/// <param name="path">文件路径</param>

/// <param name="func">字段解析规则</param>

/// <param name="defaultEncoding">文件编码</param>

/// <returns></returns>

public static List<T> Read<T>(string path, Func<string[], T> func, Encoding defaultEncoding = null) where T : class

{

    if (defaultEncoding == null)

    {

        defaultEncoding = Encoding.UTF8;

    }

    List<T> list = new List<T>();

    using (TextFieldParser parser = new TextFieldParser(path, defaultEncoding))

    {

        parser.TextFieldType = FieldType.Delimited;

        //设定逗号分隔符

        parser.SetDelimiters(",");

        //设定不忽略字段前后的空格

        parser.TrimWhiteSpace = false;

        bool isLine = false;

        while (!parser.EndOfData)

        {

            string[] fields = parser.ReadFields();

            if (isLine)

            {

                var obj = func(fields);

                if (obj != null) list.Add(obj);

            }

            else

            {

                //忽略标题行业

                isLine = true;

            }

        }

    }

    return list;

}

#endregion

使用时,代码如下:

?

1

2

//读CSV文件

records = CsvFile.Read(path, Test.Parse);

使用正则表达式解析CSV文件

如果你有一个问题,想用正则表达式来解决,那么你就有两个问题了。

正则表达式有一定的学习门槛,而且学习后不经常使用就会忘记。正则表达式解决的大多数是一些不易变更需求的问题,这就导致一个稳定可用的正则表达式可以传好几代。

本节的正则表达式来自 《精通正则表达式(第3版)》 第6章 打造高效正则表达式——简单的消除循环的例子,有兴趣的可以去了解一下,表达式说明如下:

注:这本书最终版的解析CSV文件的正则表达式是Jave版的使用占有优先量词取代固化分组的版本,也是百度上经常见到的版本。不过占有优先量词在C#中有点问题,本人能力有限解决不了,所以使用了上图的版本。不过,这两版正则表达式性能上没有差异。

正则表达式解析CSV文件代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

#region 读CSV文件(使用正则表达式)

/// <summary>

/// 读CSV文件,默认第一行为标题

/// </summary>

/// <typeparam name="T"></typeparam>

/// <param name="path">文件路径</param>

/// <param name="func">字段解析规则</param>

/// <param name="defaultEncoding">文件编码</param>

/// <returns></returns>

public static List<T> Read_Regex<T>(string path, Func<string[], T> func, Encoding defaultEncoding = null) where T : class

{

    List<T> list = new List<T>();

    StringBuilder sbr = new StringBuilder(100);

    Regex lineReg = new Regex("\"");

    Regex fieldReg = new Regex("\\G(?:^|,)(?:\"((?>[^\"]*)(?>\"\"[^\"]*)*)\"|([^\",]*))");

    Regex quotesReg = new Regex("\"\"");

    bool isLine = false;

    string line = string.Empty;

    using (StreamReader sr = new StreamReader(path))

    {

        while (null != (line = ReadLine(sr)))

        {

            sbr.Append(line);

            string str = sbr.ToString();

            //一个完整的CSV记录行,它的双引号一定是偶数

            if (lineReg.Matches(sbr.ToString()).Count % 2 == 0)

            {

                if (isLine)

                {

                    var fields = ParseCsvLine(sbr.ToString(), fieldReg, quotesReg).ToArray();

                    var obj = func(fields.ToArray());

                    if (obj != null) list.Add(obj);

                }

                else

                {

                    //忽略标题行业

                    isLine = true;

                }

                sbr.Clear();

            }

            else

            {

                sbr.Append(Environment.NewLine);

            }                  

        }

    }

    if (sbr.Length > 0)

    {

        //有解析失败的字符串,报错或忽略

    }

    return list;

}

//重写ReadLine方法,只有\r\n才是正确的一行

private static string ReadLine(StreamReader sr)

{

    StringBuilder sbr = new StringBuilder();

    char c;

    int cInt;

    while (-1 != (cInt =sr.Read()))

    {

        c = (char)cInt;

        if (c == '\n' && sbr.Length > 0 && sbr[sbr.Length - 1] == '\r')

        {

            sbr.Remove(sbr.Length - 1, 1);

            return sbr.ToString();

        }

        else

        {

            sbr.Append(c);

        }

    }

    return sbr.Length>0?sbr.ToString():null;

}

private static List<string> ParseCsvLine(string line, Regex fieldReg, Regex quotesReg)

{

    var fieldMath = fieldReg.Match(line);

    List<string> fields = new List<string>();

    while (fieldMath.Success)

    {

        string field;

        if (fieldMath.Groups[1].Success)

        {

            field = quotesReg.Replace(fieldMath.Groups[1].Value, "\"");

        }

        else

        {

            field = fieldMath.Groups[2].Value;

        }

        fields.Add(field);

        fieldMath = fieldMath.NextMatch();

    }

    return fields;

}

#endregion

使用时代码如下:

?

1

2

//读CSV文件

records = CsvFile.Read_Regex(path, Test.Parse);

目前还未发现正则表达式解析有什么bug,不过还是不建议使用。

完整的CsvFile工具类

完整的CsvFile类代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

using Microsoft.VisualBasic.FileIO;

using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Text;

using System.Text.RegularExpressions;

namespace ConsoleApp4

{

    /// <summary>

    /// CSV文件读写工具类

    /// </summary>

    public class CsvFile

    {

        #region 写CSV文件

        //字段数组转为CSV记录行

        private static string FieldsToLine(IEnumerable<string> fields)

        {

            if (fields == null) return string.Empty;

            fields = fields.Select(field =>

            {

                if (field == null) field = string.Empty;

                //所有字段都加双引号

                field = string.Format("\"{0}\"", field.Replace("\"", "\"\""));

                //不简化

                //field = field.Replace("\"", "\"\"");

                //if (field.IndexOfAny(new char[] { ',', '"', ' ', '\r' }) != -1)

                //{

                //    field = string.Format("\"{0}\"", field);

                //}

                return field;

            });

            string line = string.Format("{0}{1}", string.Join(",", fields), Environment.NewLine);

            return line;

        }

        //默认的字段转换方法

        private static IEnumerable<string> GetObjFields<T>(T obj, bool isTitle) where T : class

        {

            IEnumerable<string> fields;

            if (isTitle)

            {

                fields = obj.GetType().GetProperties().Select(pro => pro.Name);

            }

            else

            {

                fields = obj.GetType().GetProperties().Select(pro => pro.GetValue(obj)?.ToString());

            }

            return fields;

        }

        /// <summary>

        /// 写CSV文件,默认第一行为标题

        /// </summary>

        /// <typeparam name="T"></typeparam>

        /// <param name="list">数据列表</param>

        /// <param name="path">文件路径</param>

        /// <param name="append">追加记录</param>

        /// <param name="func">字段转换方法</param>

        /// <param name="defaultEncoding"></param>

        public static void Write<T>(List<T> list, string path,bool append=true, Func<T, bool, IEnumerable<string>> func = null, Encoding defaultEncoding = null) where T : class

        {

            if (list == null || list.Count == 0) return;

            if (defaultEncoding == null)

            {

                defaultEncoding = Encoding.UTF8;

            }

            if (func == null)

            {

                func = GetObjFields;

            }

            if (!File.Exists(path)|| !append)

            {

                var fields = func(list[0], true);

                string title = FieldsToLine(fields);

                File.WriteAllText(path, title, defaultEncoding);

            }

            using (StreamWriter sw = new StreamWriter(path, true, defaultEncoding))

            {

                list.ForEach(obj =>

                {

                    var fields = func(obj, false);

                    string line = FieldsToLine(fields);

                    sw.Write(line);

                });

            }

        }

        #endregion

        #region 读CSV文件(使用TextFieldParser)

        /// <summary>

        /// 读CSV文件,默认第一行为标题

        /// </summary>

        /// <typeparam name="T"></typeparam>

        /// <param name="path">文件路径</param>

        /// <param name="func">字段解析规则</param>

        /// <param name="defaultEncoding">文件编码</param>

        /// <returns></returns>

        public static List<T> Read<T>(string path, Func<string[], T> func, Encoding defaultEncoding = null) where T : class

        {

            if (defaultEncoding == null)

            {

                defaultEncoding = Encoding.UTF8;

            }

            List<T> list = new List<T>();

            using (TextFieldParser parser = new TextFieldParser(path, defaultEncoding))

            {

                parser.TextFieldType = FieldType.Delimited;

                //设定逗号分隔符

                parser.SetDelimiters(",");

                //设定不忽略字段前后的空格

                parser.TrimWhiteSpace = false;

                bool isLine = false;

                while (!parser.EndOfData)

                {

                    string[] fields = parser.ReadFields();

                    if (isLine)

                    {

                        var obj = func(fields);

                        if (obj != null) list.Add(obj);

                    }

                    else

                    {

                        //忽略标题行业

                        isLine = true;

                    }

                }

            }

            return list;

        }

        #endregion

        #region 读CSV文件(使用正则表达式)

        /// <summary>

        /// 读CSV文件,默认第一行为标题

        /// </summary>

        /// <typeparam name="T"></typeparam>

        /// <param name="path">文件路径</param>

        /// <param name="func">字段解析规则</param>

        /// <param name="defaultEncoding">文件编码</param>

        /// <returns></returns>

        public static List<T> Read_Regex<T>(string path, Func<string[], T> func, Encoding defaultEncoding = null) where T : class

        {

            List<T> list = new List<T>();

            StringBuilder sbr = new StringBuilder(100);

            Regex lineReg = new Regex("\"");

            Regex fieldReg = new Regex("\\G(?:^|,)(?:\"((?>[^\"]*)(?>\"\"[^\"]*)*)\"|([^\",]*))");

            Regex quotesReg = new Regex("\"\"");

            bool isLine = false;

            string line = string.Empty;

            using (StreamReader sr = new StreamReader(path))

            {

                while (null != (line = ReadLine(sr)))

                {

                    sbr.Append(line);

                    string str = sbr.ToString();

                    //一个完整的CSV记录行,它的双引号一定是偶数

                    if (lineReg.Matches(sbr.ToString()).Count % 2 == 0)

                    {

                        if (isLine)

                        {

                            var fields = ParseCsvLine(sbr.ToString(), fieldReg, quotesReg).ToArray();

                            var obj = func(fields.ToArray());

                            if (obj != null) list.Add(obj);

                        }

                        else

                        {

                            //忽略标题行业

                            isLine = true;

                        }

                        sbr.Clear();

                    }

                    else

                    {

                        sbr.Append(Environment.NewLine);

                    }                  

                }

            }

            if (sbr.Length > 0)

            {

                //有解析失败的字符串,报错或忽略

            }

            return list;

        }

        //重写ReadLine方法,只有\r\n才是正确的一行

        private static string ReadLine(StreamReader sr)

        {

            StringBuilder sbr = new StringBuilder();

            char c;

            int cInt;

            while (-1 != (cInt =sr.Read()))

            {

                c = (char)cInt;

                if (c == '\n' && sbr.Length > 0 && sbr[sbr.Length - 1] == '\r')

                {

                    sbr.Remove(sbr.Length - 1, 1);

                    return sbr.ToString();

                }

                else

                {

                    sbr.Append(c);

                }

            }

            return sbr.Length>0?sbr.ToString():null;

        }

        

        private static List<string> ParseCsvLine(string line, Regex fieldReg, Regex quotesReg)

        {

            var fieldMath = fieldReg.Match(line);

            List<string> fields = new List<string>();

            while (fieldMath.Success)

            {

                string field;

                if (fieldMath.Groups[1].Success)

                {

                    field = quotesReg.Replace(fieldMath.Groups[1].Value, "\"");

                }

                else

                {

                    field = fieldMath.Groups[2].Value;

                }

                fields.Add(field);

                fieldMath = fieldMath.NextMatch();

            }

            return fields;

        }

        #endregion

    }

}

使用方法如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

//写CSV文件

CsvFile.Write(records, path, true, new Func<Test, bool, IEnumerable<string>>((obj, isTitle) =>

{

    IEnumerable<string> fields;

    if (isTitle)

    {

        fields = obj.GetType().GetProperties().Select(pro => pro.Name + Environment.NewLine + "\",\"");

    }

    else

    {

        fields = obj.GetType().GetProperties().Select(pro => pro.GetValue(obj)?.ToString());

    }

    return fields;

}));

//读CSV文件

records = CsvFile.Read(path, Test.Parse);

//读CSV文件

records = CsvFile.Read_Regex(path, Test.Parse);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/52034.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QGIS3.28的二次开发一:编译工程

环境&#xff1a;VS2019OSGeo4WCMake_3.26Cygwin64QGIS_3.28 注意&#xff1a;一定要按照步骤顺序来&#xff01; 一、配置环境 &#xff08;一&#xff09;VS2019 VS2019下载链接https://my.visualstudio.com/Downloads?qvisual%20studio%202019&wt.mc_ido~msft~vsco…

Java面向对象编程实战详解(图书管理系统示例)

文章目录 面向编程概念图书管理系统示例需求分析设计阶段编码实现创建目录结构Book类的编码BookList类的编码User类的编码AdminUser类的编码NormalUser类的编码启动类的编写具体的操作实现IOperation接口新增图书的实现借阅图书的实现删除图书的实现显示图书的实现查找图书的实…

1.netty介绍

1.介绍 是JBOSS通过的java开源框架是异步的,基于事件驱动(点击一个按钮调用某个函数)的网络应用框架,高性能高可靠的网络IO程序基于TCP,面向客户端高并发应用/点对点大量数据持续传输的应用是NIO框架 (IO的一层层封装) TCP/IP->javaIO和网络编程–>NIO—>Netty 2.应用…

一文讲清楚地图地理坐标系

前言 我最近在做一个和地图有关的项目&#xff0c;这里本人地图采用的是mapbox&#xff0c;其中涉及一个功能需要根据用户输入的地点直接定位到地图上的对应的位置&#xff0c;本人开始想的是直接调用百度的接口根据地名直接获取坐标&#xff0c;发现在地图上的位置有偏移不够…

一、Postfix[安装与配置、smtp认证、Python发送邮件以及防垃圾邮件方法、使用腾讯云邮件服务]

Debian 11 一、安装 apt install postfix 二、配置 1.dns配置 解释&#xff1a;搭建真实的邮件服务器需要在DNS提供商那里配置下面的dns 配置A记录mail.www.com-1.x.x.x配置MX记录www.com-mail.www.com 解释&#xff1a;按照上面的配置通常邮件格式就是adminwww.com其通过…

使用BERT分类的可解释性探索

最近尝试了使用BERT将告警信息当成一个文本去做分类&#xff0c;从分类的准召率上来看&#xff0c;还是取得了不错的效果&#xff08;非结构化数据强标签训练&#xff0c;BERT确实是一把大杀器&#xff09;。但准召率并不是唯一追求的目标&#xff0c;在安全场景下&#xff0c;…

python 自动化数据提取之正则表达式

>>>> 前 言 我们在做接口自动化的时候&#xff0c;处理接口依赖的相关数据时&#xff0c;通常会使用正则表达式来进行提取相关的数据&#xff0c;今天在这边和大家聊聊如何在python中使用正则表达式。 正则表达式&#xff0c;又称正规表示式、正规表示法、正规…

K8S:容器日志收集与管理

Kubernetes 里面对容器日志的处理方式&#xff0c;都叫作 cluster-level-logging&#xff0c;即&#xff1a;这个日志处理系统&#xff0c;与容器、Pod 以及 Node 的生命周期都是完全无关的。这种设计当然是为了保证&#xff0c;无论是容器挂了、Pod 被删除&#xff0c;甚至节点…

RabbitMQ部署指南

RabbitMQ部署指南 1.单机部署 我们在Centos7虚拟机中使用Docker来安装。 1.1.下载镜像 方式一&#xff1a;在线拉取 docker pull rabbitmq:3-management方式二&#xff1a;从本地加载 已经提供了镜像包&#xff1a; 上传到虚拟机中后&#xff0c;使用命令加载镜像即可&…

文档管理NAS储存安全吗?

关键词&#xff1a;私有化、知识管理系统、文档管理、群晖NAS、协同编辑 随着企业不断发展扩大&#xff0c;企业的知识文档也逐渐增多&#xff0c;很多企业方便管理及考虑数据安全问题会将文件数据储存至NAS。 但将企业文档数据放在NAS上就足够安全的吗&#xff1f; 天翎文档管…

集成学习概述

集成学习 1. 集成学习概念 集成学习是解决有监督机器学习任务的一类方法,它的思路是基于多个学习算法的集成来提升预测结果,它通过多个模型的组合形成一个精度更高的模型,参与组合的模型成为弱学习器(基学习器)。训练时,使用训练集依次训练出这些弱学习器,对未知的样本…

yolov8系列[五]-项目实战-yolov8模型无人机检测

yolov8系列[五]-项目实战-yolov8模型无人机检测 项目介绍项目展示功能简介代码结构如何启动 开发者模式1. 安装依赖环境2. 启动程序 源代码下载其他 项目介绍 无人机识别项目,无人机搭载nvidia jetson边缘计算板子,进行实时识别。使用yolov8算法&#xff0c;训练了识别无人机的…

用于永磁同步电机驱动器的自适应SDRE非线性无传感器速度控制(MatlabSimulink实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码&Simulink仿真实现 &#x1f4a5;1 概述 本文方法基于状态依赖的里卡蒂方程&#xff08;SDRE&#xff09;控制技术及其梯度型神经网络的实时计算方法&#xff0c;允许…

c++里的基础类 is_empty_v<_Ty1>

&#xff08;1&#xff09;为什么要研究这个问题&#xff0c;因为包括智能指针等很多源代码里都会使用 _Compressed_pair 这个类&#xff0c;其是一对值。研究这个类&#xff0c;就牵涉另一个更基础的类 is_empty_v<_Ty1> &#xff08;2&#xff09; is_empty_v<_Ty1&…

APP自动化测试-Python+Appium+Pytest+Allure框架实战封装(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 pytest只是单独的…

西安电子科技大学

前言 本篇文章投稿与以下活动 【西安城市开发者社区】探索西安高校&#xff1a;展现历史与创新的魅力 资料参考与百度百科 学校简介 西安电子科技大学&#xff08;Xidian University&#xff09;&#xff0c;简称“西电”&#xff0c;位于陕西省西安市&#xff0c;是中央部…

二、SQL-5.DQL-8).案例练习

1、查询年龄为20,21,22,23岁的员工信息 select * from emp where age in(20, 21, 22, 23) and gender 女; 2、查询性别为男&#xff0c;并且年龄在20-40岁&#xff08;含&#xff09;以内的姓名为三个字的员工 select * from emp where gender 男 && age between 2…

ICLR 2023 | 用于分布外泛化的拓扑感知鲁棒优化

论文链接&#xff1a;https://openreview.net/pdf?idylMq8MBnAp 代码链接&#xff1a;GitHub - joffery/TRO: The Pytorch implementation for "Topology-aware Robust Optimization for Out-of-Distribution Generalization" (ICLR 2023) 01. 研究背景 近年来&…

Linux搭建Promtail + Loki + Grafana 轻量日志监控系统

一、简介 日志监控告警系统&#xff0c;较为主流的是ELK&#xff08;Elasticsearch 、 Logstash和Kibana核心套件构成&#xff09;&#xff0c;虽然优点是功能丰富&#xff0c;允许复杂的操作。但是&#xff0c;这些方案往往规模复杂&#xff0c;资源占用高&#xff0c;操作苦…

【Java基础教程】(四十四)IO篇 · 上:File类、字节流与字符流,分析字节输出流、字节输入流、字符输出流和字符输入流的区别~

Java基础教程之IO操作 上 &#x1f539;本节学习目标1️⃣ 文件操作类&#xff1a;File2️⃣ 字节流与字符流2.1 字节输出流&#xff1a;OutputStream2.2 字节输入流&#xff1a;InputStream2.3 字符输出流&#xff1a;Writer2.4 字符输入流&#xff1a;Reader2.5 字节流与字符…