我正在尝试阅读一些文本文件,其中每行都需要处理。目前我只是使用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;
}
}
}
答案 0 :(得分:94)
您可以使用迭代器块轻松编写基于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;
}
}
}
或让Jon高兴:
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”有一些花哨的代码,允许你对数据进行聚合但丢弃它(没有缓冲)。 Jon的解释is here。
答案 1 :(得分:23)
读取一行并检查它是否为null比检查EndOfStream更简单。
但是,我在MiscUtil中也有一个LineReader
类,这使得所有这一切变得更加简单 - 基本上它暴露了一个文件(或Func<TextReader>
作为IEnumerable<string>
这让你可以做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>
)。
答案 2 :(得分:1)
注意:您需要注意IEnumerable<T>
解决方案,因为这会导致文件在处理期间处于打开状态。
例如,有Marc Gravell的回复:
foreach(var record in ReadFrom("myfile.csv")) {
DoLongProcessOn(record);
}
文件将在整个处理过程中保持打开状态。
答案 3 :(得分:0)
谢谢大家的回答!我决定采用混合物,主要关注Marc's,因为我只需要从文件中读取行。我想你可以争辩说到处都需要分离,但是,嘿,生命太短暂了!
关于保持文件打开,在这种情况下不会出现问题,因为代码是桌面应用程序的一部分。
最后我注意到你们都使用了小写字符串。我知道在Java中大写字母和非大写字符串之间存在差异,但我认为在C#中,lowercase字符串只是对大写字符串的引用?
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();
}
}
}
答案 4 :(得分:0)
从.NET 4.0开始,File.ReadLines()
方法可用。
int count = File.ReadLines(filepath).Count(line => line.StartsWith(">"));