失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > java编写正则表达式_如何用Java编写最快的表达式评估器之一

java编写正则表达式_如何用Java编写最快的表达式评估器之一

时间:2020-04-25 08:04:35

相关推荐

java编写正则表达式_如何用Java编写最快的表达式评估器之一

java编写正则表达式

当然,标题有点吸引人,但确实如此(您当然不相信自己没有伪造自己的基准,但这是另一回事了)。

因此,上周我正在寻找一个小型且可用的库来评估数学表达式。 我几乎直接偶然发现了这个stackoverflow帖子 。 推荐的库( Expr )确实非常快,几乎满足了我的所有需求。 但是,它没有提供限制变量范围的功能(所有内容都位于VM的一个全局命名空间中)。

因此,我做到了,通常这是我们不应该做的:我重新发明了轮子,并编写了自己的解析器/评估器。 无论如何,这是一个下雨的星期六,所以我认为一个小的递归降序解析器,一个可以简化并最终计算表达式以及一个用于管理变量的小助手的AST似乎并不重要。 事实并非如此。 我有一个初步的实现,并运行得非常快。 一旦进行了一些测试,使我相信它可以正确地计算所有内容,那么我想知道与原始文章中提到的其他库相比,评估器的速度如何。 由于没有手动优化每个内部循环和所有内容,因此我没有太大期望,毕竟有些库还是商业库。 因此,当我查看结果时,我感到非常惊讶。 下面的列表显示了一个微基准,该基准使用相应的库评估相同的表达。 我的库parsii的测量是使用最终版本完成的,该版本执行了一些简化操作,例如预先评估常量表达式。 但是,没有像字节码生成之类的“黑魔法”或类似的事情发生。

为了进行性能测量,对表达式“ 2 +(7 – 5)* 3.14159 * x ^(12-10)+ sin(-3.141)”进行了评估,其中x从0到1000000。对JIT进行10次加热。然后再次执行15次,平均执行时间为:

PARSII:28.3毫秒 曝光:37.2毫秒 MathEval:7748.5毫秒 JEP:647.0毫秒 MESP:220.8毫秒 JFEP:274.3毫秒

现在,我敢肯定,这些库中的每一个都有各自的优势,因此无法直接进行比较。 仍然令人惊讶的是,一个简单的实现可以很好地竞争。

对于那些不太了解编译器构造的人,下面简要介绍一下它的工作原理:

就像任何解析器或编译器一样,parsii使用经典的方法是使用分词器 ,该工具将字符流转换为令牌流。 因此,作为字符数组的“ 4”,“,”,“ +”,“”,“ 3”,“,”,“ *”,“ 8”的“ 4 + 3 * 8”将被转换为:

4(整数) +(符号) 3(整数) *(符号) 8(整数)

令牌生成器查看当前字符,然后确定正在查看的令牌类型,然后读取属于该令牌的所有字符。 每个标记都有其类型,文本内容,并且知道其起始位置(行和字符)。 网上有很多深入的教程,因此在这里我不再赘述。 您可以看一下源代码,但是正如我所说的,它只是一个简单的基本实现。

解析器将经典的递归降序解析器转换为AST(抽象语法树),然后可以对其进行评估。 这是构建解析器的最简单方法之一,因为它完全是手工编写的,而不是由工具生成的。 这样的解析器基本上包含每个语法规则的方法。

再次有很多此类解析器的教程。 但是,大多数示例遗漏的是正确的错误处理。除了正确和快速地解析表达式之外,良好的错误处理是良好的解析器的主要方面之一。 这并不难:正如您在源代码中看到的那样,解析器在解析表达式时从不抛出异常。 将收集所有错误,并且解析器将继续尽可能长的时间。 即使在出现第一个错误后,仍无法正确评估生成的AST,但仍应继续进行,并且一次运行应报告尽可能多的错误,这一点很重要。 令牌生成器使用相同的方法,因为将格式错误的令牌(例如带有两个小数分隔符的十进制数字)报告给同一错误列表。

评估AST是解析表达式的结果,这非常容易。 语法树的每个节点都有一个评估方法,该方法将由其父节点从根节点开始调用。 此处eval的结果是对表达式求值的结果。 可以在BinaryOperation中找到这种方法的基本示例,它表示+,-,*等操作。

为了稍微缩短评估时间,执行了三个优化:

首先,在解析AST之后,在根节点上调用一种称为simple的方法,该方法会传播到每个子节点。 然后,每个节点决定是否可以找到自己的子表达式的更简单表示形式。 例如:对于二进制运算 ,我们检查两个操作数是否都是常数(数字)。 在这种情况下,我们对表达式求值并返回一个包含操作结果的新常量。 对于所有参数都恒定的函数,也可以这样做。

当在表达式中使用变量时,完成第二次优化。 幼稚的方法是使用映射并在需要时读取或写入变量的值。 尽管这确实可行,但是在执行时需要进行很多查找。 因此,我们有一个名为Variable的特殊类,其中包含变量的名称和数值。 解析表达式时,将在范围(基本上只是一个映射)中查找一次该变量,然后从现在开始使用。 由于每个查找返回相同的实例,因此在评估表达式时对变量的访问与对字段的读取或写入一样便宜,因为我们仅访问Variable的value字段。

第三次也是最后一次优化可能不会经常发挥作用。 但由于易于实现,因此还是可以实现。 它基本上被称为“惰性求值”,并在调用函数时使用。 函数不会自动求值其所有参数,然后自动执行函数调用。 它宁愿看论点,也可以凭自己决定要评估哪个论点,而不要决定。 可以在if函数中找到使用它的示例。

parsii是根据MIT许可获得许可的。 可以在GitHub上找到所有源代码以及预编译的jar。

参考:如何在Andy的Software Engineering Corner博客上从我们的JCG合作伙伴 Andreas Haufler编写Java中最快的表达式评估器之一 。

翻译自: //01/how-to-write-one-of-the-fastest-expression-evaluators-in-java.html

java编写正则表达式

如果觉得《java编写正则表达式_如何用Java编写最快的表达式评估器之一》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。