对于 Stream 对象,是否有 ReadAllLines 方法?

87

虽然存在一个File.ReadAllLines,但并没有一个Stream.ReadAllLines

using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Test_Resources.Resources.Accounts.txt"))
using (StreamReader reader = new StreamReader(stream))
{
    // Would prefer string[] result = reader.ReadAllLines();
    string result = reader.ReadToEnd();
}

是否存在一种方法来完成这个任务,或者我必须手动逐行循环文件?


reader.ReadToEnd().Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); 这个怎么样? - L.B
@LB,我也在考虑这个问题。但是这种方法看起来非常低效。 - Babak Naffas
6个回答

140

您可以编写逐行读取文本的方法,例如:

public IEnumerable<string> ReadLines(Func<Stream> streamProvider,
                                     Encoding encoding)
{
    using (var stream = streamProvider())
    using (var reader = new StreamReader(stream, encoding))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

然后将其称为:

var lines = ReadLines(() => Assembly.GetExecutingAssembly()
                                    .GetManifestResourceStream(resourceName),
                      Encoding.UTF8)
                .ToList();

Func<>部分的作用是为了在多次读取时进行处理,并避免不必要地保持流的打开状态。当然,你也可以轻松地将该代码封装在一个方法中。

如果你不需要一次性将所有内容都加载到内存中,则甚至不需要使用ToList...


1
你需要同时检查!reader.EndOfStream吗,还是不必要? - Paul C
2
@CodeBlend:不,当它到达流的末尾时,ReadLine 返回 null - Jon Skeet
我想提出修改建议,但编辑@JonSkeet的答案似乎有点...不敬之意;) - Brondahl
2
@Brondahl:是的,有一个好处:在尝试从流中读取数据之前,您可能不知道流是否已经结束。可能不会再有更多的数据,这才是您真正关心的。 (在这里进行编辑是不合适的-编辑不应该改变意图,而这个肯定会这样做。)(我怀疑在某些情况下,StreamReader.EndOfStream将基本上给出错误的结果,但我现在不打算花时间证明它的正确性。) - Jon Skeet
2
@JonSkeet 我觉得这已经涉及到了Streams的部分,我从未真正需要理解它们,因为我只在文件或Web请求中使用过它们。如果我理解你的意思正确,那么你是说有一些将数据加载到Stream中的方法,使得.EndOfStream可能返回false,但调用.ReadLine()仍然会返回null。是这样吗?相反的情况也可能发生吗?即.EndOfStream可能返回true,但调用.ReadLine()会返回实际数据吗? - Brondahl
显示剩余8条评论

49

可以在循环中使用.EndOfStream属性,而不是检查下一行是否为null。

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

using (StreamReader reader = new StreamReader("example.txt"))
{
    while(!reader.EndOfStream)
    {
        lines.Add(reader.ReadLine());
    }
}

请参见Jon Skeet的回答中的讨论。对于所有流,使用.EndOfStream并不能保证有效。我怀疑它对于“简单”的流(如文件和Web请求)将是可以的。 - Brondahl

8

简短回答

是的,你必须逐行循环。

详细说明

这里提供了最简单的方法,它取自 ReadAllLines, File.cs > InternalReadAllLines > ReadLine, StreamReader.cs

您可以看到参考版本正确处理所有的行结束符组合:\r、\n 和 \r\n。

ReadLine 方法在行终止符是 \r\n(DOS/Windows 通常会这样)时不会返回额外的空字符串。

ReadLine 也会丢弃最终分隔符之后的任何文本。

public static String[] ReadAllLines(this TextReader reader)
{
    String line;
    
    List<String> lines = new List<String>();

    while ((line = reader.ReadLine()) != null)
    {
        lines.Add(line);
    }

    return lines.ToArray();
}

虽然有不使用 ReadAllLines 的原因,但这是提问者所要求的内容。

ReadAllLines 接受一个TextReader,不仅限于 StreamReader,同时支持 StringReader

顺便说一下,StreamReader 这个名称很糟糕,因为它并不读取流,而是为文件实现了 TextReader。相比之下,Stream 是一个抽象类,提供字节序列的通用视图。换句话说,它可以是一个 FileStream——可能没有可适用的文本编码的二进制流。

为什么使用 ReadLine

文本文件是后缀分隔符,也就是说每行都以换行符结束。此外,在 Windows、Unix 和 Mac OS 上常用的换行符有 3 种组合方式。你的应用程序可能永远不会被移植到另一个操作系统,但可能需要从外部文件中读取来自外国操作系统的数据。

Split 不等同于 ReadLineSplit 最适合用于中缀分隔的字符串,例如逗号分隔的列表。对于后缀字符串来说,它不适用,因为分隔符可能是三种组合之一。 Split 将 \r 和 \n 视为两个分隔符并返回一个空字符串。它还会返回最后一个分隔符之后的任何文本。

某些其他答案中建议使用的 StringSplitOptions.RemoveEmptyEntries 选项会移除所有空行,包括原始输入中的空行。

因此,对于以下输入:

line1\r
\r
line3\r\n

ReadLine 返回 3 行,第二行为空。 Split 创建了 4 个字符串。(在最后一个 \n 后面还有一个额外的字符串。)然后它删除了第二个和第四个字符串。这不是 ReadAllLines 所做的。


我非常欣赏这个回答,并且同意它是正确的。但实际上它并不是一个答案,只是对别人答案的评论。 - John Henckel
@JohnHenckel 我确定我不记得8年前的事情了,但我猜测SO会阻止这样一个很长的评论。这不是我最后一次冗长且不负责任地回答OP的问题。我本应该成为一名律师,但我讨厌别人这样对待我 :) 在抱怨使用Split之前,我已经编辑了我的答案来实际回答问题,并提供了代码。 - Andrew Dennison

6
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Test_Resources.Resources.Accounts.txt"))
using (StreamReader reader = new StreamReader(stream))
{
    // Would prefer string[] result = reader.ReadAllLines();
    string[] result = reader.ReadToEnd().Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
}

6
使用以下扩展方法:
public static class Extensions
{
    public static IEnumerable<string> ReadAllLines(this StreamReader reader)
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

可以找到您想要的代码:

using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Test_Resources.Resources.Accounts.txt"))
using (StreamReader reader = new StreamReader(stream))
{
    string[] result = reader.ReadAllLines().ToArray();
}

2
如果你想使用StreamReader,那么是的,你必须使用ReadLine并循环遍历StreamReader,逐行读取。

像这样:

string line;

using (StreamReader reader = new StreamReader(stream))
{
    while ((line = reader.ReadLine()) != null)
    {   
        Console.WriteLine(line); 
    }
}

或者尝试

using (StreamReader reader = new StreamReader("file.txt"))
    {

       string[] content = reader.ReadToEnd().Replace("\n","").Split('\t');
    }

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