为了改进这种情况,Matcher应该而且能够利用 Rope迭代器提供的更快的 O(1)访问时间。为了掌握这种方法,首先需要理解 Matcher到其 CharSequence的访问模式。
正则表达式匹配常见的场景是从字符序列的某个点上开始,向前移动,直到找到所有匹配,并到达序列末尾为止。在这个场景中,匹配器主要是向前移动,一次通常移动不止一个字符。但在少数情况下,匹配器被迫向后移动。
很容易能将 Rope迭代器改成一次向前移动不止一个字符。但是向后移动要复杂些,因为迭代器内部执行的是深度优先算法,预先排好了 Rope遍历的顺序,需要访问每个叶节点。遍历堆栈没有足够的信息能够前移到前一个叶节点,但是如果促使向后移动的信息量没有使迭代器离开当前叶节点,那么迭代器有可能服务请求。为了说明这个作法,图 5 显示一个虚构的迭代器的状态,它能够向后移动一、二、三、四个位置,但不能移动更多位置,因为这要求访问前面访问的叶节点。
为了支持这一新功能,可以修改 Rope的 charAt方法,这样在第一次调用的时候,在指定位置上构建一个迭代器。后续的调用会将迭代器前后移动指定的距离。如果迭代器不能后移指定的距离,那么执行默认的 charAt例程取得字符的值 —这种情况很少发生。
因为这种优化无法做到普遍适用,而且要求引入新的成员变量,所以好不要直接将它添加到 Rope类。相反,可以根据需要用这个优化修饰 rope。为此,Ropes for Java 在 Rope类中包含了一个方法,能够为指定的模式生成优化的匹配器。清单 5 演示了这种方法:
清单 5. 优化的正则表达式匹配
Pattern p = ...
Matcher m = rope.matcher(p);
清单 5 中第二行调用对 rope 进行修饰,优化正则表达式匹配。
表 7 提供了这种方法的测评结果,并包含了以前的结果(表 6)以便进行对照。
技术 | 时间(单位:纳秒) |
---|---|
String |
75,286,078 |
StringBuffer |
86,083,830 |
Rope |
12,507,367,218 |
重新均衡后的 Rope |
2,628,889,679 |
Rope.matcher |
246,633,828 |
优化的结果比起重新均衡后的 rope 明显提高了 10.6 倍,使 rope 的性能与 String性能的差距缩小到 3.2 倍之内。
应用程序
什么时候不应使用 rope企业级 Java 应用程序经常包含类似下面的代码:
String x = "<input type='text' name='name' value='"
+ escapePcData(bean.getName()) + "'>";
x 随后放在 HTML 内发送到客户机浏览器。用 rope 代替编译器默认生成的 StringBuilder来计算 x 的值是否有意义?
回答是否,原因有几个。首先,这里要连接的数据的数量比较小,所以使用 rope 不会提高性能(虽然能够提高健壮性和伸缩性)。(请设想一下 getName出人意料地返回 50 MB 字符串时这两种解决方案会如何反应。)
但是出于讨论的目的,假设有许多块数据进行连接。由于 Rope的附加性能通常比 StringBuffer好,这时使用 rope 是否有意义呢?答案还是否。不论何时将输入的数据组合在一起形成格式化输出时,漂亮有效的方法是使用模板引擎(例如 StringTemplate 或 FreeMarker)。这种方法不仅能干净地将表示标记与代码分开,而且模板只进行一次编译(通常编译为 JVM 字节码),以后可以重用,从而使它们拥有的性能特征。
使用模板的第二个好处暴露了对于类似以上代码中那些输出构建例程(包括用 rope 编写的例程)常见的基本缺陷。这个好处是:可以对模板进行逐步评估,而且输出一旦生成可以写入 Writer,不必先在内存中累积。在 Java EE 应用程序中,Writer实际是到客户机浏览器的缓冲的连接,这种输出方法呈现恒定的内存量 —— O(1),而其他解决方案的内存使用则是 O(n)。这对应用程序的可伸缩性和健壮性都是 巨大的改进,虽然对小的输出或较低的应用程序负载来说不是那么明显。(请参阅 参考资料中两篇关于流式架构的文章的链接,获得进一步解释和定量分析。)
.现在对于 rope 的性能已经有了很好的理解,可以考虑 rope 的一些传统用法,以及在 Java EE 应用程序中吸引人但很可能 并不恰当的用法。
虽然 rope 可以作为一种通用方法替代字符串的连续内存表示法,但是只有在大量修改大型字符串的应用程序中才能看到明显的性能提升。这可能并不让人惊讶,因为早的 rope 应用程序是用来在文本编辑器中表示文档。不仅在特大的文档中能够以几乎恒定的时间执行文本插入和删除,rope 的不可修改性还使得 “撤消堆栈(undo stack)” 的实现非常容易:只要在每次修改时保存对前一个 rope 的引用即可。
另一个更加神奇的 rope 应用是表示虚拟机的状态。例如,ICFP 2007 编程竞赛中有一个比赛是实现一个虚拟机,要求每个周期都修改它的状态,并针对某些输入运行数百万个周期(请参阅 参考资料)。在一个 Java 实现中,虚拟机的速度提高了三个数量级,从 ~50 周期 / 秒提高到超过 50,000/ 秒,这是通过使用 Rope代替专门的 StringBuffer来表示状态而做到的。
未来的研究方向
虽然 Ropes for Java 是一种新库,但底层概念并不新鲜,该库看起来实现了 rope 的性能许诺。但是,该项目希望通过以下方面在未来的发行版中对库的某些方面进行改进:
•提供其他常见字符串操作的高性能实现。
•编写适配器,将 rope 无缝地集成到 Scala和面向 Java 平台的其他高级语言。
•通过进一步的自动测试提高质量。Ropes for Java 既使用手工编写的 JUnit 自动测试进行了测试,也通过 JUnit 工厂自动生成的测试进行了测试。集成 ESC/Java 2检验过的 Java 建模语言(JML)标注可能会进一步提高质量。