Обработка больших файлов - Чтение разрывов алгоритма - C # - PullRequest
2 голосов
/ 22 октября 2009

Итак, у меня есть алгоритм, который читает из (очень большого, ~ 155 + МБ) двоичного файла, анализирует его в соответствии со спецификацией и записывает необходимую информацию (в CSV, плоский текст). Он работает безупречно для первых 15,5 миллионов строк вывода, что дает файл CSV ~ 0,99-1,03 ГБ. Это проходит чуть более 20% двоичного файла. После этого он ломается, так как внезапно напечатанные данные совсем не соответствуют тому, что показано в двоичном файле. Я проверил двоичный файл, тот же шаблон продолжается (данные разбиваются на «пакеты» - см. Код ниже). Благодаря тому, как он обрабатывается, использование памяти никогда не увеличивается (стабильно ~ 15K). Функциональный код указан ниже. Это мой алгоритм (если так, почему он ломался бы после 15,5 миллионов строк?!) ... есть ли другие последствия, которые я не рассматриваю из-за больших размеров файлов? Есть идеи?

(к сведению: каждый «пакет» имеет длину 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());
    }
}

Ответы [ 2 ]

17 голосов
/ 22 октября 2009

Ваш код молча перехватывает любое исключение, которое происходит в цикле while, и проглатывает его.

Это плохая практика, потому что она маскирует проблемы, подобные той, с которой вы столкнулись.

Скорее всего, один из методов, которые вы вызываете внутри цикла (например, int.Parse()), вызывает исключение, потому что он сталкивается с некоторой проблемой в формате данных (или ваших предположений об этом формате).

Как только возникает исключение, цикл, считывающий данные, сбрасывается с толку, потому что он больше не располагается на границе записи .

Вы должны сделать несколько вещей, чтобы сделать этот код более устойчивым:

  1. Не молча глотать исключение в цикле выполнения. Разобраться с ними.
  2. Не считывать данные побайтно или поле за полем в цикле. Поскольку ваши записи имеют фиксированный размер (77 байт) - прочитайте всю запись в байт [], а затем обработайте ее оттуда. Это поможет вам всегда читать на границе записи.
3 голосов
/ 22 октября 2009
  • Не кладите здесь пустой универсальный блок catch, просто молча поймайте и продолжайте. Вы должны проверить и убедиться, что вы получаете реальное исключение и идти оттуда.
  • Нет необходимости в функции byteToHexString. Просто используйте префикс 0x перед шестнадцатеричным числом, и он сделает двоичное сравнение.

1010 * т.е. *

if(al[0] == 0x16 && al[1] == 0x3C && al[2] == 0x02)
{
    ...
}
  • Я не знаю, что делает ваша функция doConvert (вы не указали этот источник), но класс BinaryReader предоставляет множество различных функций, одна из которых ReadInt16. Если ваши short не хранятся в закодированном формате, это должно быть проще в использовании, чем ваше довольно запутанное и запутанное преобразование. Даже если они закодированы, все равно будет гораздо проще читать byte s и манипулировать ими, чем выполнять несколько циклических переходов с преобразованием в строки.

Редактировать

Похоже, вы очень очень либерально используете методы расширения LINQ (особенно ElementAt). Каждый раз, когда вы вызываете эту функцию, она перечисляет ваш список, пока не достигнет этого числа. У вас будет намного более эффективный код (а также меньше подробностей), если вы просто используете встроенный индексатор в списке.

т.е. al[3], а не al.ElementAt(3).

Кроме того, вам не нужно вызывать Flush на входе Stream. Flush используется, чтобы указать потоку записывать все, что у него есть в буфере записи, в дескриптор файла ОС. Для входного потока это ничего не сделает.

Я бы предложил заменить ваш текущий 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
}
...