C# 在二进制流上实现ReadLine()最有效的方法是什么?

C# 在二进制流上实现ReadLine()最有效的方法是什么?,c#,.net,vb.net,C#,.net,Vb.net,如果我在任何时候错了,请随时纠正我 我正在尝试使用.NET文件I/O类读取逗号分隔值文件。现在问题是,,此CSV文件可能包含一些带有软回车的字段,即单独的\r\n或\r\n标记,而不是文本文件中用于在某些字段中结束一行的标准\r\n标记,并且标准文本模式i/O类StreamReader不遵守标准约定,将软回车视为硬回车,因此会造成损害CSV文件的完整性 现在,使用BinaryReader类似乎是剩下的唯一选项,但BinaryReader没有ReadLine函数,因此需要自己实现ReadLine

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

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

现在,使用BinaryReader类似乎是剩下的唯一选项,但BinaryReader没有ReadLine函数,因此需要自己实现ReadLine

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


但我想知道:这是实现ReadLine函数最有效的方法吗?请告诉我。

简单地对文件进行预处理怎么样

用独特的东西替换软回车


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

简单地预处理文件怎么样

用独特的东西替换软回车


对于记录,数据中包含换行符的CSV文件,这是糟糕的设计。

您可以一次读取更大的数据块,使用Encoder.GetString将其取消编码为字符串,然后使用string.split\r\n甚至使用string.Substring0将其拆分为行,string.IndexOf\r\n并将其余部分留给下一行处理。请记住将下一次读取操作添加到上一次读取的最后一行。

您可以一次读取更大的数据块,使用Encoder.GetString将其取消编码为字符串,然后使用string.split将其拆分为行。\r\n甚至使用string.Substring0提取字符串的开头,string.IndexOf\r\n并将其余部分留给下一行处理。记住将下一次读取操作添加到上一次读取的最后一行。

可能是这样。就顺序而言,它只遍历每个字符一次,因此它将位于其中n是流的长度,所以这不是一个问题。要读取单个字符,最好使用二进制读取器

我要做的就是上课

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();
    }
}
或者类似的


警告:代码未经测试,按原样提供,无任何明示或暗示的担保。如果该程序被证明有缺陷或破坏了地球,您将承担所有必要的维护、修理或修正费用。

很可能是这样。就顺序而言,它只遍历每个字符一次,因此它将位于其中n是流的长度,所以这不是一个问题。要读取单个字符,最好使用二进制读取器

我要做的就是上课

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();
    }
}
或者类似的


警告:代码未经测试,按原样提供,无任何明示或暗示的担保。如果该计划被证明有缺陷或破坏了地球,您将承担所有必要的维护、修理或修正费用。

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


不过,这可能无关紧要。

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


不过,这可能无关紧要。

您可能需要考虑使用ODBC/OleDB连接来实现这一点。如果将oledb连接的数据源指向包含csv文件的目录,则可以将每个csv作为表进行查询。
检查http://www.connectionstrings.com/?carrier=textfile>connectionstrings.com要获得正确的连接字符串,您可能需要查看如何使用ODBC/OleDB连接来执行此操作。如果将oledb连接的数据源指向包含csv文件的目录,则可以将每个csv作为表进行查询。
检查http://www.connectionstrings.com/?carrier=textfile>connectionstrings.com要获得正确的连接字符串,这里有一个支持编码的更快的替代方法。它扩展了BinaryReader,所以您可以使用它来执行这两项任务,读取二进制块,还可以直接在二进制流上执行类似StreamReader的ReadLine

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;
        }
    }

}
这是 这是一个支持编码的更快的替代方案。它扩展了BinaryReader,所以您可以使用它来执行这两项任务,读取二进制块,还可以直接在二进制流上执行类似StreamReader的ReadLine

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;
        }
    }

}

下面是BinaryReader类的扩展方法:

使用System.IO; 使用系统文本; 公共静态类BinaryReaderExtension { 公共静态字符串ReadLine此二进制读取器 { 如果reader.IsEndOfStream 返回null; StringBuilder结果=新的StringBuilder; 字符; 而!reader.IsEndOfStream&&character=reader.ReadChar!='\n' 如果字符!='\r'&&character!='\n' 结果:特征; 返回result.ToString; } 公共静态布尔值是Endofstream此二进制读取器 { 返回reader.BaseStream.Position==reader.BaseStream.Length; } }
我没有在所有条件下都进行测试,但这段代码对我来说很有效。

这里是BinaryReader类的扩展方法:

使用System.IO; 使用系统文本; 公共静态类BinaryReaderExtension { 公共静态字符串ReadLine此二进制读取器 { 如果reader.IsEndOfStream 返回null; StringBuilder结果=新的StringBuilder; 字符; 而!reader.IsEndOfStream&&character=reader.ReadChar!='\n' 如果字符!='\r'&&character!='\n' 结果:特征; 返回result.ToString; } 公共静态布尔值是Endofstream此二进制读取器 { 返回reader.BaseStream.Position==reader.BaseStream.Length; } }

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

底层流已经将读取缓冲到更大的块中,不是吗?我更担心的是将长度为1的字符串添加到StringBuffer中可能是指StringBuilder和频繁的堆alloc。对较大的字符串执行较少的操作要好得多。@Guge是的,我指的是StringBuilder,我的道歉:底层流已经将读取缓冲到较大的块中,不是吗?我更担心的是将长度为1的字符串添加到StringBuffer,他可能指的是StringBuilder,以及频繁的堆分配。在较大的字符串上执行较少的操作要好得多。@Guge是的,我是指StringBuilder,我的道歉:我认为CSV数据中的单一换行可能不是一个坏主意,只要您在windows/dos上。这种设计已经有相当一段时间了。例如,如果单元格中有换行符,则在excel中是这样做的。按Alt+Enter在单元格中引入换行符我认为CSV数据中的单个换行符可能不是一个坏主意,只要您在windows/dos上。这种设计已经有相当一段时间了。例如,如果单元格中有换行符,则在excel中是这样做的。按Alt+Enter在cellWow中引入换行符!那很快。谢谢你的回答,我想投赞成票,但我还没有赢得足够的声誉:这不是应该循环发生吗?我不知道你怎么能读超过一两个字符。我认为你是对的。虽然我不能从手机上编辑,但我暂时无法访问桌面。你能提出改进的建议吗?哇!那很快。谢谢你的回答,我想投赞成票,但我还没有赢得足够的声誉:这不是应该循环发生吗?我不知道你怎么能读超过一两个字符。我认为你是对的。虽然我不能从手机上编辑,但我暂时无法访问桌面。您能提出改进建议吗?这并不完全正确:在字符串[]上使用String.Join会更快。但是如何构建字符串[]?您可能需要一个列表或LinkedList,这反过来会比使用StringBuilder花费更多的时间来构建。这里的首要任务是一次梳理\r\n一个字符的传入流,并从被拒绝的字符生成一个字符串。在这种情况下,StringBuilder的性能优于String。@不过,感谢您提供本文的链接。您确实启发了我:这里的情况并非如此,但程序员往往在不需要StringBuilder的简单场景中过度使用StringBuilder。“你很好”是一个相当无聊的答案,所以我想我应该添加一些内容。@config:你不需要使用列表或链接列表。最简单的方法是字符串s=System.Text.AscienceODing.ASCII.GetStringb;其中b是字节数组。如果我今天觉得无聊,我将对这些不同的选项进行基准测试。这并不完全正确:在字符串[]上使用String.Join会更快。但是如何构建字符串[]?您可能需要一个列表或LinkedList,这反过来会比使用StringBuilder花费更多的时间来构建。这里的首要任务是一次梳理\r\n一个字符的传入流,并从被拒绝的字符生成一个字符串。StringBuilder优于Stri

在本例中为ng。@MusiGenesis感谢文章的链接。您确实启发了我:这里的情况并非如此,但程序员往往在不需要StringBuilder的简单场景中过度使用StringBuilder。“你很好”是一个相当无聊的答案,所以我想我应该添加一些内容。@config:你不需要使用列表或链接列表。最简单的方法是字符串s=System.Text.AscienceODing.ASCII.GetStringb;其中b是字节数组。如果我今天觉得无聊,我会对这些不同的选择进行基准测试。当你说标准惯例时,你应该意识到它不是特别标准。在Unix上,\n本身就是正常的行终止符。您是否真的有性能问题,或者这是一个典型的过早优化情况;。我没看见你提到表演problem@Jon-是的,我知道,谢谢。我指的是Windows/dos上的标准界面。@Pop Catalin-我还没有做任何真正的性能测试,但当我看到这个问题时,我有点不高兴。我希望StreamReader能工作。我担心定制实现是否足够好。当你说标准约定时,你应该意识到它不是特别标准。在Unix上,\n本身就是正常的行终止符。您是否真的有性能问题,或者这是一个典型的过早优化情况;。我没看见你提到表演problem@Jon-是的,我知道,谢谢。我指的是Windows/dos上的标准界面。@Pop Catalin-我还没有做任何真正的性能测试,但当我看到这个问题时,我有点不高兴。我希望StreamReader能工作。我担心定制实现是否足够好。嗯,一个有趣的解决方案!嗯,一个有趣的解决方案!请注意,ReadLine的这种实现对于长度超过2048的行(即bufferSize变量长度的两倍)失败。这是因为每次缓冲区已满时都会重新创建stringBuffer。请小心,对于长度超过2048的行(即bufferSize变量长度的两倍),ReadLine的此实现将失败。这是因为每次缓冲区满时都会重新创建stringBuffer。