【问题标题】:What's the most efficient way of implementing ReadLine() on a binary stream?在二进制流上实现 ReadLine() 的最有效方法是什么?
【发布时间】:2008-11-21 12:29:06
【问题描述】:

如果我在任何时候错了,请随时纠正我......

我正在尝试使用 .NET 文件 I/O 类读取 CSV(逗号分隔值)文件。现在的问题是,这个 CSV 文件可能在某些字段和标准文本模式中包含一些带有软回车的字段(即单独的 \r 或 \n 标记,而不是文本文件中用于结束一行的标准 \r\n) I/O 类 StreamReader 不遵守标准约定,将软回车视为硬回车,从而损害 CSV 文件的完整性。

现在使用 BinaryReader 类似乎是唯一的选择,但 BinaryReader 没有 ReadLine() 函数,因此需要我自己实现 ReadLine()。

我当前的方法一次从流中读取一个字符并填充 StringBuilder 直到获得 \r\n(忽略所有其他字符,包括单独的 \r 或 \n),然后返回 StringBuilder 的字符串表示(使用 ToString())。

但我想知道:这是实现 ReadLine() 函数的最有效方式吗?请赐教。

【问题讨论】:

  • 当您说“标准约定”时,您应该意识到它并不是特别标准。在 Unix 上,"\n" 是正常的行终止符,它自己。
  • 您真的有性能问题还是这是过早优化的典型案例? ;)。我没看到你提到性能问题
  • @Jon - 是的,我知道,谢谢。我的意思是窗口/dos 的标准。
  • @Pop Catalin - 我没有做过任何真正的性能测试,但是当我看到这个问题时我有点沮丧。我希望 StreamReader 能够工作。我担心自定义实现是否足够好。

标签: c# .net vb.net


【解决方案1】:

大概是这样。就顺序而言,它只通过每个 char 一次,所以它是 O(n) (其中 n 是流的长度),所以这不是问题。要读取单个字符,最好使用 BinaryReader。

我要做的就是上课

public class LineReader : IDisposable
{
    private Stream stream;
    private BinaryReader reader;

    public LineReader(Stream stream) { reader = new BinaryReader(stream); }

    public string ReadLine()
    {
        StringBuilder result = new StringBuilder();
        char lastChar = reader.ReadChar();
        // an EndOfStreamException here would propogate to the caller

        try
        {
            char newChar = reader.ReadChar();
            if (lastChar == '\r' && newChar == '\n')
                return result.ToString();

            result.Append(lastChar);
            lastChar = newChar;
        }
        catch (EndOfStreamException)
        {
            result.Append(lastChar);
            return result.ToString();
        }
    }

    public void Dispose()
    {
        reader.Close();
    }
}

或者类似的东西。

(警告:代码未经测试,按原样提供,不提供任何形式的明示或暗示保证。如果该程序证明有缺陷或破坏地球,您将承担所有必要的服务、修理或更正的费用。)子>

【讨论】:

  • 哇!那是相当快的。感谢您的回答我想投票,但我还没有赢得足够的声誉:)
  • 这不应该循环发生吗?我看不出你会如何阅读超过 1 或 2 个字符。
  • 我认为你是对的。我无法通过移动设备对其进行真正的编辑,并且暂时无法访问桌面。您能提出改进建议吗?
【解决方案2】:

您可能希望查看使用 ODBC/OleDB 连接来执行此操作。如果将 oledb 连接的数据源指向包含 csv 文件的目录,则可以像每个 CSV 是一个表一样查询它。
检查 http://www.connectionstrings.com/?carrier=textfile>connectionstrings.com 以获取正确的连接字符串

【讨论】:

  • 嗯,一个有趣的解决方案!
【解决方案3】:

这里是 BinaryReader 类的扩展方法:

using System.IO;
using System.Text;

public static class BinaryReaderExtension
{
    public static string ReadLine(this BinaryReader reader)
    {
        if (reader.IsEndOfStream())
            return null;

        StringBuilder result = new StringBuilder();
        char character;
        while(!reader.IsEndOfStream() && (character = reader.ReadChar()) != '\n')
            if (character != '\r' && character != '\n')
                result.Append(character);

        return result.ToString();
    }

    public static bool IsEndOfStream(this BinaryReader reader)
    {
        return reader.BaseStream.Position == reader.BaseStream.Length; 
    }
}

我没有在所有条件下都进行测试,但这段代码对我有用。

【讨论】:

    【解决方案4】:

    简单地预处理文件怎么样?

    用独特的东西替换软回车。

    作为记录,数据中带有换行符的 CSV 文件,这是糟糕的设计。

    【讨论】:

    • 我认为 CSV 数据中的单个换行可能不是一个坏主意,只要您在 windows/dos 上。这种设计已经存在了一段时间。这就是它在 excel 中的完成方式,例如,如果您在单元格中有换行符。 (按 Alt+Enter 在单元格中引入换行符)
    【解决方案5】:

    您可以一次读取更大的块,使用 Encoder.GetString 将其解编码为字符串,然后使用 string.Split("\r\n") 分割成行,甚至使用 string 取出字符串的头部.Substring(0,string.IndexOf("\r\n")) 剩下的留给下一行的处理。请记住将下一个读取操作添加到上一次读取的最后一行。

    【讨论】:

    • 底层流已经将读取缓冲到更大的块中,不是吗?
    • 我更担心将长度为 1 的字符串添加到“StringBuffer”(他可能是指 StringBuilder),以及频繁的堆分配。对更大的字符串执行更少的操作会更好。
    • @Guge 是的,我的意思是 StringBuilder,我很抱歉 :)
    【解决方案6】:

    你的方法听起来不错。提高方法效率的一种方法可能是将每一行存储在常规字符串中(即不是 StringBuilder),然后将整行字符串附加到 StringBuilder。请参阅this article 以获得进一步的解释 - StringBuilder 并不是这里的最佳选择。

    不过,这可能无关紧要。

    【讨论】:

    • 这并不完全正确:在 string[] 上使用 String.Join 会更快。但是如何构建字符串 [] 呢?您将需要一个 List 或 LinkedList,这反过来会比使用 StringBuilder 花费更多的时间来构建。
    • 我刚刚阅读了这篇文章,我同意“配置器”。这里的首要任务是每次为 \r\n 一个字符梳理传入流,并从被拒绝的字符中构建一个字符串。在这种情况下,StringBuilder 的性能优于 String。
    • @MusiGenesis 感谢文章的链接。你确实启发了我:)
    • 这里不是这样,但程序员倾向于在不需要它的简单场景中过度使用 StringBuilder。 “你很好”是一个很无聊的答案,所以我想我会补充一点。
    • @config:你不需要使用 List 或 LinkedList。最简单的方法是“string s = System.Text.ASCIIEncoding.ASCII.GetString(b);”其中 b 是一个字节数组。如果我今天感到无聊,我将对这些不同的选项进行基准测试。
    【解决方案7】:

    这里有一个更快的编码支持替代方案。它扩展了 BinaryReader,因此您可以同时使用它来读取二进制块,也可以像 ReadLine 一样直接在二进制流上执行 StreamReader。

    public class LineReader : BinaryReader
    {
        private Encoding _encoding;
        private Decoder _decoder;
    
        const int bufferSize = 1024;
        private char[] _LineBuffer = new char[bufferSize];
    
        public LineReader(Stream stream, int bufferSize, Encoding encoding)
            : base(stream, encoding)
        {
            this._encoding = encoding;
            this._decoder = encoding.GetDecoder();
        }
    
        public string ReadLine()
        {
            int pos = 0;
    
            char[] buf = new char[2];
    
            StringBuilder stringBuffer = null;
            bool lineEndFound = false;
    
            while(base.Read(buf, 0, 2) > 0)
            {
                if (buf[1] == '\r')
                {
                    // grab buf[0]
                    this._LineBuffer[pos++] = buf[0];
                    // get the '\n'
                    char ch = base.ReadChar();
                    Debug.Assert(ch == '\n');
    
                    lineEndFound = true;
                }
                else if (buf[0] == '\r')
                {
                    lineEndFound = true;
                }                    
                else
                {
                    this._LineBuffer[pos] = buf[0];
                    this._LineBuffer[pos+1] = buf[1];
                    pos += 2;
    
                    if (pos >= bufferSize)
                    {
                        stringBuffer = new StringBuilder(bufferSize + 80);
                        stringBuffer.Append(this._LineBuffer, 0, bufferSize);
                        pos = 0;
                    }
                }
    
                if (lineEndFound)
                {
                    if (stringBuffer == null)
                    {
                        if (pos > 0)
                            return new string(this._LineBuffer, 0, pos);
                        else
                            return string.Empty;
                    }
                    else
                    {
                        if (pos > 0)
                            stringBuffer.Append(this._LineBuffer, 0, pos);
                        return stringBuffer.ToString();
                    }
                }
            }
    
            if (stringBuffer != null)
            {
                if (pos > 0)
                    stringBuffer.Append(this._LineBuffer, 0, pos);
                return stringBuffer.ToString();
            }
            else
            {
                if (pos > 0)
                    return new string(this._LineBuffer, 0, pos);
                else
                    return null;
            }
        }
    
    }
    

    【讨论】:

    • 小心,ReadLine() 的这种实现对于超过 2048 的行会失败,即两倍于 bufferSize 变量的长度。这是因为每次缓冲区满时都会重新创建 stringBuffer
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多