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

时间:2008-11-21 12:29:07

标签: c# .net vb.net

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

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

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

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

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

7 个答案:

答案 0 :(得分:7)

可能是。在顺序方面,它只通过每个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 :(得分:1)

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

如何简单地预处理文件?

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

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

答案 4 :(得分:0)

你可以一次读取一个更大的块,使用Encoder.GetString将其解码为字符串,然后使用string.Split(“\ r \ n”)拆分成行,甚至使用字符串挑选字符串的头部.Substring(0,string.IndexOf(“\ r \ n”))并留下其余部分来处理下一行。请记住将下一个读取操作添加到上一次读取的最后一行。

答案 5 :(得分:0)

你的方法听起来不错。提高方法效率的一种方法可能是在用常规字符串(即不是StringBuilder)构建它时存储每一行​​,然后将整行字符串附加到StringBuilder。有关详细说明,请参阅this article - StringBuilder不会自动成为最佳选择。

但是,它可能不会那么重要。

答案 6 :(得分:0)

这是一种具有编码支持的更快的替代方案。它扩展了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;
        }
    }

}