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

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

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

对于性能测量,表达式x为(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的特殊类,其中包含变量的名称和数值。 解析表达式时,将在范围(基本上只是一个映射)中查找一次该变量,然后从现在开始使用。 由于每个查找返回相同的实例,因此在评估表达式时对变量的访问与对字段的读取或写入一样便宜,因为我们仅访问Variablevalue字段。

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

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

参考:在Andy的软件工程专栏博客中, 如何从我们的JCG合作伙伴 Andreas Haufler编写Java中最快的表达式评估器之一 。

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

如何用Java编写最快的表达式评估器之一相关推荐

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

    java编写正则表达式 当然,标题有点吸引人,但确实如此(您当然不相信自己没有伪造自己的基准,但这是另一回事了). 因此,上周我正在寻找一个小型且可用的库来评估数学表达式. 我几乎直接偶然发现了这个s ...

  2. java入门教程:如何用java编写一款王者荣耀游戏?

    Java是一种编程语言,被特意设计用于互联网的分布式环境.Java具有类似于C++语言的"形式和感觉",但它要比C++语言更易于使用,而且在编程时彻底采用了一种"以对象为 ...

  3. 如何用java编写一个简单的多人PK游戏

    如何用java编写一个简单的多人PK游戏 类与对象---PK游戏的编写 素材取用----王者荣耀和BILIBILI 创建两个对战的角色(PK游戏最少需要两名角色) 创建孙尚香和Van 设置孙尚香和Va ...

  4. java类似sizeof_如何用Java编写类似C的Sizeof函数

    java类似sizeof 如果您刚开始学习Java并且是C语言背景,那么您可能已经注意到Java和C编程语言之间存在一些差异,例如String是Java中的对象,而不是NULL终止的字符数组. 同样, ...

  5. java 代码 内存泄露_如何用Java编写一段代码引发内存泄露

    Q:刚才我参加了面试,面试官问我如何写出会发生内存泄露的Java代码.这个问题我一点思路都没有,好囧. A1:通过以下步骤可以很容易产生内存泄露(程序代码不能访问到某些对象,但是它们仍然保存在内存中) ...

  6. java编写一个函数_请教如何用java编写一个函数图像生成的应用程序?谢谢!

    展开全部 package math; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridLayo ...

  7. 如何用Java编写类似C的Sizeof函数

    如果您刚开始学习Java并且是C语言背景,那么您可能已经注意到Java和C编程语言之间存在一些差异,例如String是Java中的对象,而不是NULL终止的字符数组. 同样,Java中没有sizeof ...

  8. 如何用java编写小游戏

    应一起学习java的众兄弟姐妹们的要求,对塔防游戏小程序做一个简单的说明,本人学习java两年时间,现在刚刚入职,虽然上班基本不用java swing相关的知识,但由于大学期间运用awt.swing编 ...

  9. 如何用Java编写一个简单的服务器和客户机

    今天我要向大家介绍的是自己编写的一个比较简单的服务器和客户机程序,注意一下哦,比较简单.好了,闲话休提,砸门直入主题. 小编先从客户机和服务器的模型开始讲解.简单来说,我们实现的这种模型呢,我们每一个 ...

最新文章

  1. java session缓存_Java服务端采用Session的缓存oauth2.0授权用户信息
  2. 重磅!居全国前列!合肥获批建设3个国家战略性新兴产业集群!
  3. tensorflow使用tf.placeholder会报错
  4. boost::range_const_reverse_iterator相关的测试程序
  5. 解决AJAX表单用POST方式提交Checkbox复选框的问题
  6. SVN trunk branches tags 的用法 - 摘自网络
  7. vue给组件传html,如何将 html 模板作为道具传递给 Vue 组件
  8. Ubuntu Touch OTA-12 发布,Ubuntu 移动版本
  9. rip协议中周期性广播路由信息的报文_关于RIP的一点小笔记--华为
  10. 衔着树枝飞跃太平洋的傻鸟!(童话版)
  11. pycharm添加python注释头_pycharm使用教程——py文件自动添加文件头注释
  12. 基于bert的情感分类
  13. PIPIOJ1099PIPI的油田
  14. (七)设定目标:原理与方法
  15. 同步,异步,直流电机原理
  16. 高通modem侧新增AT命令
  17. 金融 python 招聘,滴滴、度小满金融python工程师社招面经
  18. 如何进行shell脚本正确性测试
  19. 四川省某市智慧海绵城市监测系统案例分享
  20. python中求某一行的和

热门文章

  1. pagehelper 不分页的解决方法
  2. 以计算机网络为中介的人际传播,以计算机为中介的人际传播理论范式
  3. 本地方法(JNI)——调用 java 方法
  4. c#自定义控件资源释放问题_定义资源
  5. pivotal_Spring Data Pivotal Gemfire教程
  6. java 编写代码_如果您在2016年编写过Java代码-这是您不容错过的趋势
  7. java minor gc_Java Minor发布计划再次进行了调整
  8. java多线程做一件事_关于Java的十件事
  9. java字节码_好的,每个接触Java字节码的人
  10. 定制基元和DTO的(反)序列化和验证