在C#中逐行读取文件

我正在尝试阅读一些文本文件,每行需要处理。 目前,我只是使用StreamReader,然后逐一读取每一行。

我想知道是否有一个更有效的方法(在LoC和可读性方面)使用LINQ来做到这一点,而不会影响操作效率。 我所看到的例子涉及将整个文件加载到内存中,然后处理它。 不过,在这种情况下,我不认为这将是非常有效的。 在第一个例子中,文件可以达到大约50k,而在第二个例子中,不需要读取文件的所有行(大小通常<10k)。

你可以争辩说,现在这些小文件并不重要,但是我相信这种方法会导致代码效率低下。

第一个例子:

// Open file using(var file = System.IO.File.OpenText(_LstFilename)) { // Read file while (!file.EndOfStream) { String line = file.ReadLine(); // Ignore empty lines if (line.Length > 0) { // Create addon T addon = new T(); addon.Load(line, _BaseDir); // Add to collection collection.Add(addon); } } } 

第二个例子:

 // Open file using (var file = System.IO.File.OpenText(datFile)) { // Compile regexs Regex nameRegex = new Regex("IDENTIFY (.*)"); while (!file.EndOfStream) { String line = file.ReadLine(); // Check name Match m = nameRegex.Match(line); if (m.Success) { _Name = m.Groups[1].Value; // Remove me when other values are read break; } } } 

您可以使用迭代器块很容易地编写一个基于LINQ的线阅读器:

 static IEnumerable<SomeType> ReadFrom(string file) { string line; using(var reader = File.OpenText(file)) { while((line = reader.ReadLine()) != null) { SomeType newRecord = /* parse line */ yield return newRecord; } } } 

或让乔恩开心:

 static IEnumerable<string> ReadFrom(string file) { string line; using(var reader = File.OpenText(file)) { while((line = reader.ReadLine()) != null) { yield return line; } } } ... var typedSequence = from line in ReadFrom(path) let record = ParseLine(line) where record.Active // for example select record.Key; 

那么你有ReadFrom(...)作为一个懒惰评估序列没有缓冲,完美的Where等。

请注意,如果使用OrderBy或标准的GroupBy ,它将不得不缓冲内存中的数据; 如果你需要分组和聚合,“PushLINQ”有一些奇特的代码,允许你在数据上执行聚合,但丢弃它(无缓冲)。 乔恩的解释在这里 。

读取一行并检查是否为空比始终检查EndOfStream更简单。

然而,我在MiscUtil中也有一个LineReader类,这使得所有这一切变得更加简单 – 基本上它暴露了一个文件(或者一个IEnumerable<string>Func<TextReader> ,它可以让你做LINQ的东西,所以你可以做像:

 var query = from file in Directory.GetFiles("*.log") from line in new LineReader(file) where line.Length > 0 select new AddOn(line); // or whatever 

LineReader的核心是IEnumerable<string>.GetEnumerator这个实现:

 public IEnumerator<string> GetEnumerator() { using (TextReader reader = dataSource()) { string line; while ((line = reader.ReadLine()) != null) { yield return line; } } } 

几乎所有的源代码都是给出灵活的方法来设置dataSource (这是一个Func<TextReader> )。

注意 :您需要注意IEnumerable<T>解决scheme,因为它会导致文件在处理期间处于打开状态。

例如,Marc Gravell的回应是:

 foreach(var record in ReadFrom("myfile.csv")) { DoLongProcessOn(record); } 

该文件将保持打开的整个处理。

谢谢你的回答! 我决定去混合使用,主要关注Marc's,不过我只需要读取文件中的行。 我想你可以争论到处都需要分离,但是,生命太短了!

关于保持文件打开,在这种情况下,这不会是一个问题,因为代码是桌面应用程序的一部分。

最后我注意到你们都使用了小写string。 我知道在Java中有大写字母和非大写string之间的区别,但我认为在C#小写string只是大写string的引用?

 public void Load(AddonCollection<T> collection) { // read from file var query = from line in LineReader(_LstFilename) where line.Length > 0 select CreateAddon(line); // add results to collection collection.AddRange(query); } protected T CreateAddon(String line) { // create addon T addon = new T(); addon.Load(line, _BaseDir); return addon; } protected static IEnumerable<String> LineReader(String fileName) { String line; using (var file = System.IO.File.OpenText(fileName)) { // read each line, ensuring not null (EOF) while ((line = file.ReadLine()) != null) { // return trimmed line yield return line.Trim(); } } }