Чтение файла mbox в C # - PullRequest
4 голосов
/ 24 мая 2009

Один из наших сотрудников потерял свой почтовый ящик, но, к счастью, получил дамп своей электронной почты в формате mbox. Мне нужно каким-то образом получить все сообщения из файла mbox и добавить их в нашу базу данных технической поддержки (поскольку это пользовательский инструмент, инструменты для импорта недоступны).

Я нашел SharpMimeTools , который разбивает сообщение, но не позволяет перебирать кучу сообщений в файле mbox.

Кто-нибудь знает о приличном парсере, который открывается без необходимости изучать RFC для его записи?

Ответы [ 3 ]

18 голосов
/ 13 сентября 2013

Я работаю над анализатором MIME & mbox в C #, который называется MimeKit .

Он основан на более ранних парсерах MIME и mbox, которые я написал (например, GMime ), которые были безумно быстрыми (мог анализировать каждое сообщение в файле mbox объемом 1,2 ГБ примерно за 1 секунду).

Я еще не тестировал MimeKit на производительность, но я использую многие из тех же техник в C #, которые я использовал в C. Я подозреваю, что это будет медленнее, чем моя реализация C, но поскольку узким местом является I / O и MimeKit написан для оптимального чтения (4k), как у GMime, они должны быть довольно близки.

Причины того, что ваш текущий подход медленен (StreamReader.ReadLine (), объединяет текст, а затем передает его в SharpMimeTools), вызваны следующими причинами:

  1. StreamReader.ReadLine () - не очень оптимальный способ чтения данных из файла. Хотя я уверен, что StreamReader () выполняет внутреннюю буферизацию, необходимо выполнить следующие шаги:

    A) Преобразовать блок байтов, считанных из файла, в юникод (для этого требуется перебрать байты в считанном с диска байте [], чтобы преобразовать байты, прочитанные из потока, в символ юникода []).

    B) Затем ему нужно перебрать свой внутренний символ [], копируя каждый символ в StringBuilder, пока не найдет '\ n'.

    Итак, просто читая строки, у вас есть как минимум 2 прохода через ваш входной поток mbox. Не говоря уже о том, что происходит выделение памяти ...

  2. Затем вы объединяете все прочитанные строки в одну мега-строку. Это требует еще одной передачи вашего ввода (копирование каждого символа из каждой строки, считанной из ReadLine () в StringBuilder, предположительно?).

    Теперь у нас есть до 3 итераций для входного текста, и синтаксический анализ даже не проводился.

  3. Теперь вы передаете свою мега-строку SharpMimeTools, которая использует SharpMimeMessageStream, который ... (/ facepalm) является синтаксическим анализатором на основе ReadLine (), который находится поверх другого StreamReader, который выполняет преобразование кодировки. Это делает 5 итераций, прежде чем что-либо вообще анализируется. SharpMimeMessageStream также имеет способ «отменить» ReadLine (), если он обнаруживает, что прочитал слишком далеко. Поэтому разумно предположить, что он сканирует некоторые этих строк, по крайней мере, дважды. Не говоря уже о том, что происходит распределение строк ... тьфу.

  4. Для каждого заголовка, когда SharpMimeTools имеет свой линейный буфер, он разделяется на поле и значение. Это еще один проход. Пока у нас до 6 пасов.

  5. Затем SharpMimeTools использует string.Split () (что является довольно хорошим показателем того, что этот анализатор mime не соответствует стандартам) для токенизации заголовков адресов путем разделения на ',' и параметризованных заголовков (таких как Content-Type и Content-Disposition) путем разбиения на ';'. Это еще один проход. (Сейчас у нас до 7 проходов.)

  6. Как только он разделяет их, он запускает совпадение с регулярным выражением для каждой строки, возвращаемой из строки. Split () и затем большее количество проходов регулярного выражения для токена кодированного слова rfc2047, прежде чем, наконец, сделать еще один проход по кодировке кодированного слова и полезной нагрузке компоненты. Мы говорим, по крайней мере, 9 или 10 проходов по большей части ввода к этой точке.

Я перестаю идти дальше с экзаменом, потому что это уже в 2 раза больше проходов, чем нужно GMime и MimeKit, и я знаю мои парсеры могут быть оптимизированы так, чтобы проход по крайней мере на 1 проход меньше, чем они .

Кроме того, как примечание, любой синтаксический анализатор MIME, который анализирует строки вместо байта [] (или sbyte []), никогда не будет очень хорошим. Проблема с электронной почтой состоит в том, что так много почтовых клиентов / скриптов / и т. Д. В дикой природе будут отправлять необъявленный 8-битный текст в заголовках и телах сообщений. Как может обработчик строки Unicode возможно обрабатывать это? Подсказка: не может.

2013-09-18 Обновление: Я довел MimeKit до такой степени, что теперь его можно использовать для анализа файлов mbox, и он успешно справился с обработкой изломов, но это не так быстро, как моя библиотека C Это было протестировано на iMac, поэтому производительность ввода-вывода не так хороша, как на моей старой машине с Linux (где GMime может анализировать файлы mbox одинакового размера за ~ 1 с):

[fejj@localhost MimeKit]$ mono ./mbox-parser.exe larger.mbox 
Parsed 14896 messages in 6.16 seconds.
[fejj@localhost MimeKit]$ ./gmime-mbox-parser larger.mbox 
Parsed 14896 messages in 3.78 seconds.
[fejj@localhost MimeKit]$ ls -l larger.mbox 
-rw-r--r--  1 fejj  staff  1032555628 Sep 18 12:43 larger.mbox

Как вы можете видеть, GMime все еще немного быстрее, но у меня есть некоторые идеи о том, как улучшить производительность анализатора MimeKit. Оказывается, операторы C # fixed довольно дороги, поэтому мне нужно переделать их использование. Например, простая оптимизация Я вчера побрился на 2-3 с от общего времени (если я правильно помню).

Обновление оптимизации: Просто увеличил производительность еще на 20%, заменив:

while (*inptr != (byte) '\n')
    inptr++;

с:

do {
    mask = *dword++ ^ 0x0A0A0A0A;
    mask = ((mask - 0x01010101) & (~mask & 0x80808080));
} while (mask == 0);

inptr = (byte*) (dword - 1);
while (*inptr != (byte) '\n')
    inptr++;

Обновление оптимизации: Мне удалось, наконец, сделать MimeKit так же быстро, как GMime, отказавшись от использования Enum.HasFlag () и применив вместо этого прямую битовую маскировку.

MimeKit теперь может анализировать тот же поток mbox за 3,78 с.

Для сравнения, SharpMimeTools занимает более 20 минут (чтобы проверить это, мне пришлось разделить электронные письма на отдельные файлы, поскольку SharpMimeTools не может анализировать файлы mbox).

Другое обновление: Я довел его до 3,00 с помощью различных других настроек кода.

2 голосов
/ 24 мая 2009

Я не знаю парсера, но mbox действительно очень простой формат. Новое электронное письмо начинается со строк, начинающихся с «From» (From + Space), и в конце каждого письма прикрепляется пустая строка. Если в начале строки в самом письме встречается «От», это указывается в кавычках (добавляя «>»).

Также см. Запись Википедии на тему .

0 голосов
/ 24 мая 2009

Если вы можете использовать Python, в стандартной библиотеке есть one . Я не могу найти что-либо для .NET, к сожалению.

...