我需要一个快速密钥替换算法的Java

时间:2009-01-20 22:52:40

标签: java algorithm optimization string

给定一个包含替换键的字符串,如何使用 Java 最有效地用运行时值替换这些键?我需要经常,快速地,以及相当长的字符串(例如,平均1-2kb)。键的形式是我的选择,因为我也在这里提供模板。

这是一个例子(请不要挂断它是XML;我想这样做,如果可能的话,比使用XSL或DOM操作便宜)。我想用bean属性,true @[^@]*?@属性和一些其他来源中的属性值替换此中的所有Property模式。这里的关键是快速。有什么想法吗?

<?xml version="1.0" encoding="utf-8"?>

<envelope version="2.3">

  <delivery_instructions>

    <delivery_channel>
      <channel_type>@CHANNEL_TYPE@</channel_type>
    </delivery_channel>

    <delivery_envelope>
      <chan_delivery_envelope>
    <queue_name>@ADDRESS@</queue_name>
      </chan_delivery_envelope>
    </delivery_envelope>

  </delivery_instructions>

  <composition_instructions>
    <mime_part content_type="application/xml">
      <content><external_uri>@URI@</external_uri></content>
    </mime_part>
  </composition_instructions>

</envelope>

天真的实现是使用String.replaceAll(),但我不禁认为这不太理想。如果我可以避免添加新的第三方依赖项,那就更好了。

13 个答案:

答案 0 :(得分:6)

Matcher中的appendReplacement方法看起来很有用,虽然我不能保证它的速度。

以下是Javadoc的示例代码:

Pattern p = Pattern.compile("cat");
Matcher m = p.matcher("one cat two cats in the yard");
StringBuffer sb = new StringBuffer();
while (m.find()) {
    m.appendReplacement(sb, "dog");
}
m.appendTail(sb);
System.out.println(sb.toString());

编辑:如果这很复杂,你可能很容易实现自己的状态机。你几乎正在做appendReplacement已经在做的事情,虽然专门的实现可能会更快。

答案 1 :(得分:4)

现在跳过编写自己的东西还为时过早。我会从天真的替换解决方案开始,并实际基准测试。然后我会尝试第三方模板解决方案。然后我会尝试自定义流版本。

在你获得一些难以理解的数字之前,你怎么能确定优化它的价值呢?

答案 2 :(得分:3)

Java是否有一种regexp replace()形式,其中函数被调用?

我被Javascript String.replace()方法破坏了。 (就此而言,您可以运行Rhino并使用Javascript,但不知何故我认为即使Javascript编译器/解释器有效,它也不会像纯Java调用一样快)

编辑:没关系,@ mmyers可能有最好的答案。

无偿的点 - 卑躬屈膝:(因为我想看看自己是否可以这样做:)

Pattern p = Pattern.compile("@([^@]*?)@");
Matcher m = p.matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) 
{
    m.appendReplacement(sb,substitutionTable.lookupKey(m.group(1)));
}
m.appendTail(sb);
// replace "substitutionTable.lookupKey" with your routine

答案 3 :(得分:1)

你真的想写一些自定义的内容,这样你就可以避免多次处理字符串。我不能强调这一点 - 因为我看到的大多数其他解决方案看起来都忽略了这个问题。

可选择将文本转换为流。通过char读取char,将每个char转发到输出字符串/流,直到看到@然后读取到下一个@ slurping out键,将键替换为输出:重复直到流结束。

我知道这很粗野 - 但它可能是最好的。

我假设您对'@'有一些合理的假设,而不仅仅是'显示'输入中与您的令牌键无关。 :)

答案 4 :(得分:1)

  

请不要挂断它是XML;我希望这样做,如果可能的话,比使用XSL或DOM操作便宜

如果您还没有处理插入的字符串以进行字符转义,那么您的进程的下游将会挂起。这并不是说如果你有充分的理由你不能自己做,但是这意味着你要么必须确保你的模式都在文本节点中,你也正确地逃避替换文本。

@ Foo @对标准&amp; Foo有什么确切的优势;已经内置到Java附带的XML库中的语法?

答案 5 :(得分:1)

如果你不改变你的范式,文本处理总会受到限制。我不知道你的域名是多么灵活,所以不确定这是否适用,但这里有:

尝试创建一个索引到文本替换的位置 - 如果模板不经常更改,这是特别好的,因为它成为模板“编译”的一部分,成为可以接受所需值的二进制对象替换,并将整个字符串作为字节数组进行blit。此对象可以缓存/保存,下次重新替换新值以再次使用。也就是说,每次都可以节省解析文档的费用。 (实施留给读者的练习= D)

但是,在开始编写自定义模板引擎之前,请使用分析器检查这是否是您所说的瓶颈。问题实际上可能是其他地方。

答案 6 :(得分:1)

正如其他人所说,appendReplacement()和appendTail()是你需要的工具,但是你需要注意的事项。如果替换字符串包含任何美元符号,则该方法将尝试将它们解释为捕获组引用。如果有任何反斜杠(用于逃避美元唱歌),它会吃掉它们或抛出异常。

如果您的替换字符串是动态生成的,您可能不会事先知道它是否包含任何美元符号或反斜杠。为了防止出现问题,您可以将替换直接附加到StringBuffer,如下所示:

Pattern p = Pattern.compile("@([^@]*?)@");
Matcher m = p.matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) 
{
    m.appendReplacement("");
    sb.append(substitutionTable.lookupKey(m.group(1)));
}
m.appendTail(sb);

您每次都需要调用appendReplacement(),因为这样可以让您与匹配位置保持同步。但是这个技巧避免了很多毫无意义的处理,这可能会给你带来明显的性能提升作为奖励。

答案 7 :(得分:1)

答案 8 :(得分:0)

我还有一个基于非正则表达式的替换库,可用here。我没有测试它的速度,它不直接支持你的例子中的语法。但是扩展以支持该语法很容易;例如,请参阅this class

答案 9 :(得分:0)

查看专门针对此的图书馆,例如Apache Velocity。如果没有别的,你可以打赌他们对这部分逻辑的实现很快。

答案 10 :(得分:0)

我不太确定接受的答案比String.replaceAll(String,String)更快。这里比较的是String.replaceAll的实现和在封面下使用的Matcher.replaceAll。看起来与OP正在寻找的非常相似,我猜它可能比这种简单的解决方案更加优化。

public String replaceAll(String s, String s1)
    {
        return Pattern.compile(s).matcher(this).replaceAll(s1);
    }

public String replaceAll(String s)
    {
        reset();
        boolean flag = find();
        if(flag)
        {
            StringBuffer stringbuffer = new StringBuffer();
            boolean flag1;
            do
            {
                appendReplacement(stringbuffer, s);
                flag1 = find();
            } while(flag1);
            appendTail(stringbuffer);
            return stringbuffer.toString();
        } else
        {
            return text.toString();
        }
    }

答案 11 :(得分:0)

...... Chii是对的。 如果这是一个必须运行很多次的模板,速度很重要,找到替换标记的索引,以便能够直接到达它们,而不必每次都从头开始。将“编译”抽象为具有良好属性的对象,它们只需要在更改模板后进行更新。

答案 12 :(得分:0)

Rythm现在发布了一个名为 String interpolation mode 的新功能的java模板引擎,它允许您执行以下操作:

String result = Rythm.render("Hello @who!", "world");

以上情况表明您可以按位置将参数传递给模板。 Rythm还允许您按名称传递参数:

Map<String, Object> args = new HashMap<String, Object>();
args.put("title", "Mr.");
args.put("name", "John");
String result = Rythm.render("Hello @title @name", args);

由于您的模板内容相对较长,您可以将它们放入文件中,然后使用相同的API调用Rythm.render

Map<String, Object> args = new HashMap<String, Object>();
// ... prepare the args
String result = Rythm.render("path/to/my/template.xml", args);

注意Rythm将你的模板编译成java字节码,速度相当快,比String.format快2倍

链接: