2

所以我有一个算法,可以从(非常大,~155+ MB)二进制文件中读取,根据规范解析它并写出必要的信息(到 CSV,平面文本)。对于前 1550 万行的输出,它可以完美运行,生成约 0.99-1.03 GB 的 CSV 文件。这通过几乎不超过 20% 的二进制文件。在此之后它会中断,因为突然打印的数据根本不是二进制文件中显示的内容。我检查了二进制文件,同样的模式还在继续(数据分成“数据包”——见下面的代码)。由于它的处理方式,内存使用量从未真正增加(稳定〜15K)。功能代码如下。是我的算法吗(如果是这样,为什么它会在 1550 万行之后中断?!)......由于文件大小很大,我没有考虑其他影响吗?有任何想法吗?

(仅供参考:每个“数据包”的长度为 77 字节,以 3 字节的“起始码”开始,以 5 字节的“结束码”结束 - 您将看到下面的模式)

编辑代码已根据以下建议更新...谢谢!

private void readBin(string theFile)
{
    List<int> il = new List<int>();
    bool readyForProcessing = false;

    byte[] packet = new byte[77];

    try
    {
        FileStream fs_bin = new FileStream(theFile, FileMode.Open);
        BinaryReader br = new BinaryReader(fs_bin);

        while (br.BaseStream.Position < br.BaseStream.Length && working)
        {
            // Find the first startcode
            while (!readyForProcessing)
            {
                // If last byte of endcode adjacent to first byte of startcod...
                // This never occurs outside of ending/starting so it's safe
                if (br.ReadByte() == 0x0a && br.PeekChar() == (char)0x16)
                    readyForProcessing = true;
            }

            // Read a full packet of 77 bytes
            br.Read(packet, 0, packet.Length);

            // Unnecessary I guess now, but ensures packet begins
            // with startcode and ends with endcode
            if (packet.Take(3).SequenceEqual(STARTCODE) &&
                packet.Skip(packet.Length - ENDCODE.Length).SequenceEqual(ENDCODE))
            {
                il.Add(BitConverter.ToUInt16(packet, 3)); //il.ElementAt(0) == 2byte id
                il.Add(BitConverter.ToUInt16(packet, 5)); //il.ElementAt(1) == 2byte semistable
                il.Add(packet[7]); //il.ElementAt(2) == 1byte constant

                for(int i = 8; i < 72; i += 2) //start at 8th byte, get 64 bytes
                    il.Add(BitConverter.ToUInt16(packet, i));

                for (int i = 3; i < 35; i++)
                {
                    sw.WriteLine(il.ElementAt(0) + "," + il.ElementAt(1) +
                        "," + il.ElementAt(2) + "," + il.ElementAt(i));
                }

                il.Clear();
            }
            else
            {
                // Handle "bad" packets
            }
        } // while

        fs_bin.Flush();
        br.Close();                
        fs_bin.Close();
    }
    catch (Exception e)
    {
        MessageBox.Show(e.ToString());
    }
}
4

2 回答 2

17

您的代码会默默地捕获 while 循环中发生的任何异常并将其吞下。

这是一种不好的做法,因为它掩盖了您遇到的问题。

最有可能的是,您在循环中调用的方法之一(int.Parse()例如)抛出异常,因为它在数据格式(或您对该格式的假设)方面遇到了一些问题。

一旦发生异常,读取数据的循环就会中断,因为它不再位于记录边界处

您应该做几件事来使这段代码更有弹性:

  1. 不要在运行循环中默默吞下异常。对付他们。
  2. 不要在循环中逐字节或逐字段读取数据。由于您的记录是固定大小(77 字节) - 将整个记录读入 byte[] 然后从那里处理它。这将有助于确保您始终在记录边界处阅读。
于 2009-10-22T17:58:08.867 回答
3
  • 不要catch在这里放置一个空的通用块,只是默默地捕捉并继续。您应该检查并查看您是否在那里遇到实际异常并从那里开始。
  • 不需要该byteToHexString功能。只需0x在十六进制数字前使用前缀,它将进行二进制比较。

IE

if(al[0] == 0x16 && al[1] == 0x3C && al[2] == 0x02)
{
    ...
}
  • 我不知道你的doConvert函数是做什么的(你没有提供那个源),但是这个BinaryReader类提供了许多不同的函数,其中之一是ReadInt16. 除非您short的 s 以编码格式存储,否则这应该比进行相当模糊和令人困惑的转换更容易使用。即使它们被编码,读取byte和操作它们仍然要简单得多,而不是通过转换为字符串进行多次往返。

编辑

您似乎非常自由地使用 LINQ 扩展方法(尤其是ElementAt)。每次调用该函数时,它都会枚举您的列表,直到达到该数字。如果您只使用列表中的内置索引器,您将获得性能更好的代码(以及更少的冗长)。

al[3]而不是al.ElementAt(3)

此外,您不需要调用Flushinput StreamFlush用于告诉流将其写入缓冲区中的任何内容写入底层 OS 文件句柄。对于输入流,它不会做任何事情。

我建议用sw.WriteLine这个替换你当前的电话:

sw.WriteLine(BitConverter.ToString(packet));看看你在它开始混乱的那一行所期望的数据是否真的是你得到的。

我实际上会这样做:

if (packet.Take(3).SequenceEqual(STARTCODE) &&
    packet.Skip(packet.Length - ENDCODE.Length).SequenceEqual(ENDCODE))
{
    ushort id = BitConverter.ToUInt16(packet, 3);
    ushort semistable = BitConverter.ToUInt16(packet, 5);
    byte contant = packet[7];

    for(int i = 8; i < 72; i += 2)
    {
        il.Add(BitConverter.ToUInt16(packet, i));
    }

    foreach(ushort element in il)
    {
        sw.WriteLine(string.Format("{0},{1},{2},{3}", id, semistable, constant, element);
    }

    il.Clear();
}
else
{
    //handle "bad" packets
}
于 2009-10-22T18:02:17.437 回答