C# StreamReader,“ReadLine”用于自定义分隔符

21

如何以自定义的(字符串)分隔符实现StreamReader.ReadLine()方法的功能最佳方法?

我想做类似于:

String text;
while((text = myStreamReader.ReadUntil("my_delim")) != null)
{
   Console.WriteLine(text);
}

我曾尝试使用Peek()StringBuilder来制作自己的方法,但效率太低。我正在寻找建议或可能的开源解决方案。

谢谢。

编辑

我应该更早地澄清这一点...我已经看过这个答案,不过,我不想将整个文件读入内存。


为什么不使用ReadLine(),然后在字符串中搜索分隔符呢? - Denis Palnitsky
通过使用 Peek()StringBuilder,你基本上是在复制 StreamReader 内部的 ReadLine() 操作... 所以我觉得它很慢很奇怪;你能发一下你尝试过的代码吗? - digEmAll
效率低下?有多低下?性能明显不足吗? - user1017882
如何按特定行分隔符读取文本文件 - KingCronus
4
@AdamKing - 不是重复问题 - 提问者明确想要一个字符串分隔符,而不是字符分隔符。 - Rob Levine
4个回答

4

我想发布自己的解决方案。它似乎运行得很好,代码相对简单。欢迎评论。

public static String ReadUntil(this StreamReader sr, String delim)
{
    StringBuilder sb = new StringBuilder();
    bool found = false;

    while (!found && !sr.EndOfStream)
    {
       for (int i = 0; i < delim.Length; i++)
       {
           Char c = (char)sr.Read();
           sb.Append(c);

           if (c != delim[i])
               break;

           if (i == delim.Length - 1)
           {
               sb.Remove(sb.Length - delim.Length, delim.Length);
               found = true;
           }
        }
     }

     return sb.ToString();
}

2
如果您在“found = true”之后也加上一个“break”,那么对我来说会稍微清晰一些。这需要更少的心理处理。 - Jon Coombs
4
此解决方案仅适用于某些情况。例如,如果分隔符是“xy”,那么此算法会在“axxyb”中错过分隔符,并且会读取直到流的结尾。 - Jirka Hanika

1

这段代码应该适用于任何字符串分隔符。

public static IEnumerable<string> ReadChunks(this TextReader reader, string chunkSep)
{
    var sb = new StringBuilder();

    var sepbuffer = new Queue<char>(chunkSep.Length);
    var sepArray = chunkSep.ToCharArray();

    while (reader.Peek() >= 0)
    {
        var nextChar = (char)reader.Read();
        if (nextChar == chunkSep[sepbuffer.Count])
        {
            sepbuffer.Enqueue(nextChar);
            if (sepbuffer.Count == chunkSep.Length)
            {
                yield return sb.ToString();
                sb.Length = 0;
                sepbuffer.Clear();
            }
        }
        else
        {
            sepbuffer.Enqueue(nextChar);
            while (sepbuffer.Count > 0)
            {
                sb.Append(sepbuffer.Dequeue());
                if (sepbuffer.SequenceEqual(chunkSep.Take(sepbuffer.Count)))
                    break;
            }
        }
    }
    yield return sb.ToString() + new string(sepbuffer.ToArray());
}

免责声明:

我对此进行了一些测试,实际上比ReadLine方法慢,但我怀疑这是由于在ReadLine方法中可以避免使用enqueue/dequeue/sequenceEqual调用(因为分隔符始终为\r\n)。

再次强调,我进行了少量测试,它应该可以工作,但不要认为它是完美的,欢迎您进行更正。 ;)


1
这是我常用的一个简单解析器(通常在流式传输不是关键时,只需读取并使用 .Split 即可完成任务),不太优化但应该可以正常工作:
(更像是 Split 方法 - 更多注释请见下文)
    public static IEnumerable<string> Split(this Stream stream, string delimiter, StringSplitOptions options)
    {
        var buffer = new char[_bufffer_len];
        StringBuilder output = new StringBuilder();
        int read;
        using (var reader = new StreamReader(stream))
        {
            do
            {
                read = reader.ReadBlock(buffer, 0, buffer.Length);
                output.Append(buffer, 0, read);

                var text = output.ToString();
                int id = 0, total = 0;
                while ((id = text.IndexOf(delimiter, id)) >= 0)
                {
                    var line = text.Substring(total, id - total);
                    id += delimiter.Length;
                    if (options != StringSplitOptions.RemoveEmptyEntries || line != string.Empty)
                        yield return line;
                    total = id;
                }
                output.Remove(0, total);
            }
            while (read == buffer.Length);
        }

        if (options != StringSplitOptions.RemoveEmptyEntries || output.Length > 0)
            yield return output.ToString();
    }

...而且如果需要,你只需简单地切换到字符分隔符,只要替换即可。

while ((id = text.IndexOf(delimiter, id)) >= 0)

...与

while ((id = text.IndexOfAny(delimiters, id)) >= 0)

(并使用id++而不是id+=以及签名this Stream stream,StringSplitOptions options,params char[] delimiters

...还可以删除空等等。
希望它能帮助到你。


1
    public static String ReadUntil(this StreamReader streamReader, String delimiter)
    {
        StringBuilder stringBuilder = new StringBuilder();

        while (!streamReader.EndOfStream)
        {
            stringBuilder.Append(value: (Char) streamReader.Read());

            if (stringBuilder.ToString().EndsWith(value: delimiter))
            {
                stringBuilder.Remove(stringBuilder.Length - delimiter.Length, delimiter.Length);
                break;
            }
        }

        return stringBuilder.ToString();
    }

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