在C#中读取mbox文件

我们的一名工作人员丢失了他的邮箱,但幸运的是他以mbox格式转发了他的电子邮件。 我需要以某种方式获取mbox文件中的所有消息并将它们喷入我们的技术支持数据库(因为它是一个自定义工具,没有可用的导入工具)。

我发现SharpMimeTools分解了一条消息,但是不允许你在mbox文件中迭代一堆消息。

有没有人知道一个不错的解析器打开而不必学习RFC写出一个?

我不知道任何解析器,但mbox实际上是一种非常简单的格式。 新邮件从以“发件人”(From + Space)开头的行开头,每行邮件末尾附有一个空行。 如果在电子邮件本身的一行开头出现“From”,则会引出(通过添加“>”)。

另请参阅维基百科关于该主题的条目 。

我正在使用C#中的MIME和mbox解析器调用MimeKit 。

它基于我编写的早期MIME和mbox解析器(例如GMime ),它们非常快(可以在大约1秒内解析1.2GB mbox文件中的每条消息)。

我还没有测试MimeKit的性能,但是我在C中使用了很多与C语言相同的技术。我怀疑它会比我的C实现慢,但是因为瓶颈是I / O而MimeKit是写得做最佳(4k)读取就像GMime一样,它们应该非常接近。

您发现当前方法变慢的原因(StreamReader.ReadLine(),组合文本,然后将其传递给SharpMimeTools)是由于以下原因:

  1. StreamReader.ReadLine()不是从文件中读取数据的最佳方式。 虽然我确信StreamReader()执行内部缓冲,但它需要执行以下步骤:

    A)将从文件读取的字节块转换为unicode(这需要迭代从磁盘读取的byte []中的字节,以将从流中读取的字节转换为unicode char [])。

    B)然后它需要遍历其内部char [],将每个char复制到StringBuilder,直到找到’\ n’。

    所以就在那里,只有阅读线,你的mbox输入流至少有2次传递。 更不用说正在进行的所有内存分配……

  2. 然后,将所有已阅读的行合并为一个单字符串。 这需要对您的输入进行另一次传递(将从ReadLine()读取的每个字符串中的每个字符复制到StringBuilder中,大概是?)。

    我们现在对输入文本进行了3次迭代,甚至还没有解析。

  3. 现在你把你的超级字符串交给使用SharpMimeMessageStream的SharpMimeTools ……(/ facepalm)是一个基于ReadLine()的解析器,它位于另一个执行字符集转换的StreamReader之上。 这甚至可以在任何事情之前进行5次迭代。 SharpMimeMessageStream还有一种方法可以“撤消”ReadLine(),如果它发现它读得太远了。 所以可以合理地假设他正在扫描其中一些线至少两次。 更不用说所有正在进行的字符串分配……呃。

  4. 对于每个标题,一旦SharpMimeTools具有其行缓冲区,它就会分割为字段和值。 那是另一个传球。 到目前为止,我们最多可以通过6次。

  5. SharpMimeTools然后使用string.Split()(这是一个非常好的指示,这个mime解析器不符合标准)通过拆分’,’和参数化标题(如Content-Type和Content-Disposition)通过拆分来标记地址标题上 ‘;’。 那是另一个传球。 (我们现在最多7次通过。)

  6. 一旦它拆分,它就会在string.Split()返回的每个字符串上运行正则表达式匹配,然后在最终对编码字字符集和有效负载组件进行另一次传递之前,每个rfc2047编码字令牌传递更多正则表达式。 到目前为止,我们正在谈论大部分输入至少9或10次传球。

我放弃了我的考试,因为它已经超过GMime和MimeKit所需的2倍,我知道我的解析器可以优化至少比他们少一次。

另外,作为旁注,任何解析字符串而不是byte [](或sbyte [])的MIME解析器永远不会很好。 电子邮件的问题在于,野外的许多邮件客户端/脚本/等将在标头和邮件正文中发送未声明的8位文本。 unicode字符串解析器如何处理? 提示:它不能。

2013-09-18更新:我已经让MimeKit达到了现在可用于解析mbox文件并成功设法解决问题的程度,但它并不像我的C库那么快。 这是在iMac上测试的,因此I / O性能不如我在旧的Linux机器上那么好(这是GMime能够在~1s内解析类似大小的mbox文件):

[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解析器的性能有一些想法。 事实certificate,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++; 

优化更新:我终于可以通过切换使用Enum.HasFlag()并使用直接位屏蔽来最终使MimeKit像GMime一样快。

MimeKit现在可以在3.78s内解析相同的mbox流。

相比之下,SharpMimeTools需要超过20 分钟 (为了测试这一点,我不得不将电子邮件拆分成单独的文件,因为SharpMimeTools无法解析mbox文件)。

另一个更新:我通过代码中的各种其他调整将它降低到3.00s。

如果你可以使用Python,那么标准库中就有一个 。 我遗憾地找不到.NET。