【问题标题】:Doing Java String replacement efficiently有效地进行 Java 字符串替换
【发布时间】:2011-08-15 08:42:35
【问题描述】:

我们有以下代码:

String templateQuery = "select * from my_table where col1=$1 or col2 like '%$2.$1'";
String tmp = templateQuery;

for(int i=1;i<=maxCols;i++) {
    tmp = tmp.replaceAll("\\$"+i, data[i-1]);
}

这段代码运行良好,因为maxCols 永远不会超过 10。但我的同事不同意我的说法,即这段代码消耗了太多内存。你能帮助我们吗?

编辑: 我已经用一个非常现实的模板查询更改了初始模板查询。其次,templateQuery 可能是一个 big 字符串。

编辑 2: 感谢那些指出SQLInjection问题的人。

【问题讨论】:

  • 定义“工作正常”。并定义“内存过多”。
  • 就“内存太大”而言,消耗的临时内存应该远远小于10KB,价值不到0.1美分的内存;)
  • @Peter @Oli templateQuery 可能比示例中的要大得多。
  • 所以如果它是 100K,那就是 1 美分的临时内存。 ;)

标签: java string performance replace


【解决方案1】:

不要这样做。

不是出于性能原因(与数据库查询的成本相比会微不足道),而是为了避免 SQL 注入攻击。如果data[0] 实际上是字符串会发生什么

' OR 'x' = 'x

?

然后你会得到一个 SQL 语句:

SELECT * FROM my_table WHERE col1='' OR 'x' = 'x'

我认为我们可以同意的不是您想要的。

改用参数化 SQL 语句 (PreparedStatement) 并让数据库驱动程序分别发送参数值。

编辑:在其他 cmets 中,OP 已指定模板字符串可以很长,并且某些参数实际上可能涉及组合在一起的多个初始值。我仍然说更换成本在宏伟的计划中可能微不足道,我仍然说PreparedStatement是要走的路。在将输入设置为PreparedStatement 的值之前,您应该对输入执行所需的任何组合操作 - 因此模板可能需要带有 SQL 占位符的 SQL,然后“子模板”来确定如何从您的输入中获取PreparedStatement 的参数。无论您做什么,将值直接放入 SQL 都是错误的方法。

【讨论】:

  • +1:为了代码的可维护性和使用标准实践。 BTW:单个数据库查询的资源成本远远超过几个字符串的成本。
  • @Peter:我确定我在某处的编辑中得到了那个(关于性能差异很小的一点)......它似乎已经消失了:(
  • 在我们的应用程序中,在运行查询时,我们总是将查询结果限制在前 50 行。
  • @Stephan:你为什么认为这与这个问题有关?
  • @Jon 我的评论是对您回复中的回复的回复。即使攻击者执行SELECT * FROM my_table WHERE col1='' OR 'x' = 'x',他也只会得到前50行。
【解决方案2】:

您为什么不直接使用带有替换参数的PreparedStatement?

String templateQuery = "SELECT * FROM my_table WHERE col1 = ?";
PreparedStatement ps = con.prepareStatement(templateQuery);
for (int i = 0; i < data.length; i++) {
    ps.setString(i + 1, data[i]);
}
ResultSet rs = ps.executeQuery();

如果你像以前一样使用字符串替换,否则你很容易受到SQL injection 的攻击。

【讨论】:

  • 我们可以有这种类型的模板:select * from my_table where col1=$1 or col2 like '%$2.$1' 所以 PreparedStatement 不太适合
  • @Stephan:这只是意味着您需要在使用 PreparedStatement之前对您的值(和模板)进行一些预处理。您仍然应该绝对不将值直接放入 SQL 中。
  • @Jon 我清楚地了解 SQLInjection 的大问题。在我们的例子中,这不是一个问题,因为该应用程序是一个仅供内部使用的小工具(到目前为止有 2 个用户......)。主要关注的是如何有效地转换模板查询。
  • @Stephan:我仍然认为这是解决问题的错误方法。它目前可能只在内部使用......但是现在做出这样的错误决定往往会在以后咬你。如果它只是一个小型内部工具,那么性能实际上有多重要——你测量过吗?没有测量,讨论性能是毫无意义的。
  • @Jon 性能很重要,因为我们希望 templateQuery 的处理时间少于 2 秒。该处理大致可分为 3 个步骤:将 templateQuery 转换为数据库服务器可处理的结果查询,执行结果查询并显示结果。问题在于尽快执行第一步。
【解决方案3】:

你的同事是对的,每次字符串替换都会创建一个新的字符串副本。 (但是,如果参数少于 10 个,这些成本可能可以忽略不计。)此外,对于此查询的每次执行,SQL 引擎都需要重新解析它,这每次都会消耗更多的额外资源。

但潜在的更大问题是代码容易受到 SQL 注入。如果输入数据来自外部来源,黑客可以传入"col1; drop table my_table;" 等参数,从而有效地删除您的整个表。

所有这些都可以通过使用PreparedStatement 来解决。

【讨论】:

    【解决方案4】:

    这是否会消耗太多内存还有待商榷(什么是“太多”?)

    不过,对于这类东西,您应该使用PreparedStatement。它允许您以一种更简洁的方式完成几乎所有您想要实现的目标。

    【讨论】:

    • templateQuery 可能比示例大得多。
    【解决方案5】:

    他是正确的,因为你创建了 maxCols tmp Strings。 我意识到它是针对 Sql 命令的,如果是,为什么不使用 PreparedStatement (http://download.oracle.com/javase/1.4.2/docs/api/java/sql /PreparedStatement.html) 来完成这项任务?

    另外,对于格式化字符串,而不是使用替代,使用 Formatter,它更优雅:http://download.oracle.com/javase/1.5.0/docs/api/java/util/Formatter.html

    【讨论】:

    • 不,他不是正确的 - 它会消耗一些内存,但你真的认为 10 个额外的字符串与数据库查询的成本相比会很重要吗?这是一个典型的微优化示例,当有一个非常大的 big 问题时,专注于一个 tiny 问题,而最好的解决方法是避免微优化问题.
    • 这是一个非常简单易行的优化,而且,他没有使用好的做法,为什么他不应该做得更好?
    • @Pih:因为它是错误的修复程序。 之后代码仍然被破坏 - 性能差异几乎肯定会非常小。 OP 的同事正在解决错误的问题,应该被告知。
    • 是的,性能问题是那里最小的问题,我引用了 PreparedStatement 来解决其他问题,比如 SQL 注入以及它看起来如何连续的 SQL 命令,这对 DB 会更好。
    • 嗯。我认为 Pih 在这一点上是正确的。 Jon Skeet 在他的帖子中完全正确(显然),但实际的问题是这种方式的字符串替换是否会消耗太多内存。这确实是微优化。不过,这是个问题,应该回答。
    猜你喜欢
    • 2020-03-23
    • 2017-08-13
    • 2011-09-30
    • 2010-09-22
    • 2012-01-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多