使用StreamReader

时间:2018-09-25 14:37:50

标签: c# .net

我正在使用Mozilla字符集检测器的this端口来确定文件的编码,然后使用该编码来构造StreamReader。到目前为止,一切都很好。

但是,我正在读取的文件格式是一种奇怪的格式,因此有时需要跳过一些字节。也就是说,以其他方式编码的文本文件将在其中嵌入一些原始字节。

我想以文本形式读取流,直到击中一些指示字节流跟随的文本为止,然后我想读取字节流,然后以文本形式继续读取。做到这一点的最佳方法是什么(简单性和性能之间的平衡)?

我不能依靠针对StreamReader的FileStream进行搜索(然后丢弃后者中的缓冲数据),因为我不知道在读取字符之前使用了多少字节。我可能会放弃使用StreamReader并切换到使用字节和字符的并行数组的定制类,使用解码器从前者填充字符和字符,并通过使用编码来每次读取字符时跟踪字节数组中的位置用于字符的字节数。 。

为进一步说明,文件具有以下格式:

[编码字符] [嵌入式字节指示器+ len] [len字节] [编码字符] ...

其中一个或多个嵌入式字节块为零且嵌入式char块的长度可以为任意长度。

例如,

ABC:123:DEF:456:$ 0099 [0x00,0x01,0x02,... x 99] GHI:789:JKL:...

没有行定界符。我可能有由某些字符(在这种情况下为冒号)分隔的任意多个字段(ABC,123,...)。这些字段可能在各种代码页中,包括UTF-8(不保证为单字节)。当我命中$时,我知道接下来的4个字节包含一个长度(称为n),接下来的n个字节将被原始读取,而字节n +1将是另一个文本字段(GHI)。

1 个答案:

答案 0 :(得分:1)

概念证明。此类适用于UTF-16字符串数据和每个OP中的':'分隔符。它期望二进制长度是一个4字节的低端二进制整数。应该很容易地将其调整为(奇数)文件格式的更具体的细节。例如,任何Decoder类都应放入ReadString()并“正常工作”。

要使用它,请使用Stream类构造它。对于每个单独的数据元素,调用ReportNextData(),它将告诉您下一个什么样的数据,然后调用适当的Read *()方法。对于二进制数据,请调用ReadBinaryLength(),然后调用ReadBinaryData()。

请注意,ReadBinaryData()遵循流协定;它不能保证返回所需的字节数,因此您可能需要多次调用它。但是,如果您请求太多字节,它将抛出EndOfStreamException。

我使用以下数据(十六进制格式)对其进行了测试: 410042004300240A0000000102030405060708090024050000000504030201580059005A003A310032003300

哪个是: ABC $ [10] [1234567890] $ [5] [54321] XYZ:123

像这样扫描数据:

OddFileReader.NextData nextData;

while ((nextData = reader.ReportNextData()) != OddFileReader.NextData.Eof)
{
    // Call appropriate Read*() here.
}

public class OddFileReader : IDisposable
{
    public enum NextData
    {
        Unknown,
        Eof,
        String,
        BinaryLength,
        BinaryData
    }

    private Stream source;
    private byte[] byteBuffer;
    private int bufferOffset;
    private int bufferEnd;
    private NextData nextData;
    private int binaryOffset;
    private int binaryEnd;
    private char[] characterBuffer;

    public OddFileReader(Stream source)
    {
        this.source = source;
    }

    public NextData ReportNextData()
    {
        if (nextData != NextData.Unknown)
        {
            return nextData;
        }

        if (!PopulateBufferIfNeeded(1))
        {
            return (nextData = NextData.Eof);
        }

        if (byteBuffer[bufferOffset] == '$')
        {
            return (nextData = NextData.BinaryLength);
        }
        else
        {
            return (nextData = NextData.String);
        }
    }

    public string ReadString()
    {
        ReportNextData();

        if (nextData == NextData.Eof)
        {
            throw new EndOfStreamException();
        }
        else if (nextData != NextData.String)
        {
            throw new InvalidOperationException("Attempt to read non-string data as string");
        }

        if (characterBuffer == null)
        {
            characterBuffer = new char[1];
        }

        StringBuilder stringBuilder = new StringBuilder();
        Decoder decoder = Encoding.Unicode.GetDecoder();

        while (nextData == NextData.String)
        {
            byte b = byteBuffer[bufferOffset];

            if (b == '$')
            {
                nextData = NextData.BinaryLength;

                break;
            }
            else if (b == ':')
            {
                nextData = NextData.Unknown;
                bufferOffset++;

                break;
            }
            else
            {
                if (decoder.GetChars(byteBuffer, bufferOffset++, 1, characterBuffer, 0) == 1)
                {
                    stringBuilder.Append(characterBuffer[0]);
                }

                if (bufferOffset == bufferEnd && !PopulateBufferIfNeeded(1))
                {
                    nextData = NextData.Eof;

                    break;
                }
            }
        }

        return stringBuilder.ToString();
    }

    public int ReadBinaryLength()
    {
        ReportNextData();

        if (nextData == NextData.Eof)
        {
            throw new EndOfStreamException();
        }
        else if (nextData != NextData.BinaryLength)
        {
            throw new InvalidOperationException("Attempt to read non-binary-length data as binary length");
        }

        bufferOffset++;

        if (!PopulateBufferIfNeeded(sizeof(Int32)))
        {
            nextData = NextData.Eof;

            throw new EndOfStreamException();
        }

        binaryEnd = BitConverter.ToInt32(byteBuffer, bufferOffset);
        binaryOffset = 0;
        bufferOffset += sizeof(Int32);
        nextData = NextData.BinaryData;

        return binaryEnd;
    }

    public int ReadBinaryData(byte[] buffer, int offset, int count)
    {
        ReportNextData();

        if (nextData == NextData.Eof)
        {
            throw new EndOfStreamException();
        }
        else if (nextData != NextData.BinaryData)
        {
            throw new InvalidOperationException("Attempt to read non-binary data as binary data");
        }

        if (count > binaryEnd - binaryOffset)
        {
            throw new EndOfStreamException();
        }

        int bytesRead;

        if (bufferOffset < bufferEnd)
        {
            bytesRead = Math.Min(count, bufferEnd - bufferOffset);

            Array.Copy(byteBuffer, bufferOffset, buffer, offset, bytesRead);
            bufferOffset += bytesRead;
        }
        else if (count < byteBuffer.Length)
        {
            if (!PopulateBufferIfNeeded(1))
            {
                throw new EndOfStreamException();
            }

            bytesRead = Math.Min(count, bufferEnd - bufferOffset);

            Array.Copy(byteBuffer, bufferOffset, buffer, offset, bytesRead);
            bufferOffset += bytesRead;
        }
        else
        {
            bytesRead = source.Read(buffer, offset, count);
        }

        binaryOffset += bytesRead;

        if (binaryOffset == binaryEnd)
        {
            nextData = NextData.Unknown;
        }

        return bytesRead;
    }

    private bool PopulateBufferIfNeeded(int minimumBytes)
    {
        if (byteBuffer == null)
        {
            byteBuffer = new byte[8192];
        }

        if (bufferEnd - bufferOffset < minimumBytes)
        {
            int shiftCount = bufferEnd - bufferOffset;

            if (shiftCount > 0)
            {
                Array.Copy(byteBuffer, bufferOffset, byteBuffer, 0, shiftCount);
            }

            bufferOffset = 0;
            bufferEnd = shiftCount;

            while (bufferEnd - bufferOffset < minimumBytes)
            {
                int bytesRead = source.Read(byteBuffer, bufferEnd, byteBuffer.Length - bufferEnd);

                if (bytesRead == 0)
                {
                    return false;
                }

                bufferEnd += bytesRead;
            }
        }

        return true;
    }

    public void Dispose()
    {
        Stream source = this.source;

        this.source = null;

        if (source != null)
        {
            source.Dispose();
        }
    }
}