java string replace 重载_java – 替代连续的String.replace

虽然与

String.replace()相比,

StringBuilder.replace()是一个巨大的进步,但它仍然远非最佳。

StringBuilder.replace()的问题是,如果替换的长度不同于可替换部分的长度(适用于我们的情况),则可能必须分配更大的内部char数组,并且必须复制内容,然后替换将会发生(这也涉及到复制)。

想象一下:你有一个有10.000个字符的文本。如果要将在位置1(第二个字符)处找到的“XY”子字符串替换为“ABC”,则实现必须重新分配一个至少大1的字符缓冲区,必须将旧内容复制到新数组,并且必须将9.997个字符(从位置3开始)向右复制1以将“ABC”复制到“XY”的位置,最后将“ABC”的字符复制到起动器位置1.这必须为每个替换!这是缓慢的。

更快的解决方案:即时生成输出

我们可以即时构建输出:不包含可替换文本的部分可以简单地附加到输出,如果我们找到一个可替换的片段,我们追加替换,而不是它。理论上,它足以循环输入只有一次生成输出。听起来很简单,并不难实现它。

实施:

我们将使用一个预先加载了可替换替换字符串的映射的映射:

Map map = new HashMap<>();

map.put("

", "");

map.put("", "");

map.put("

", "");

map.put("", "");

map.put("

", "");

map.put("", "");

map.put("

", "");

map.put("", "");

map.put("

", "");

map.put("", "");

map.put("

", "");

map.put("", "");

并使用这个,这里是替换代码:(更多解释后的代码)

public static String replaceTags(String src, Map map) {

StringBuilder sb = new StringBuilder(src.length() + src.length() / 2);

for (int pos = 0;;) {

int ltIdx = src.indexOf('

if (ltIdx < 0) {

// No more '

sb.append(src, pos, src.length());

return sb.toString();

}

sb.append(src, pos, ltIdx); // Copy chars before '

// Check if our hit is replaceable:

boolean mismatch = true;

for (Entry e : map.entrySet()) {

String key = e.getKey();

if (src.regionMatches(ltIdx, key, 0, key.length())) {

// Match, append the replacement:

sb.append(e.getValue());

pos = ltIdx + key.length();

mismatch = false;

break;

}

}

if (mismatch) {

sb.append('

pos = ltIdx + 1;

}

}

}

测试:

String in = "Yo

TITLE

Hi!

Nice day.
Hi back!
End";

System.out.println(in);

System.out.println(replaceTags(in, map));

输出:(包装避免滚动条)

Yo

TITLE

Hi!

Nice day.
Hi back!
End

YoTITLEHi!Nice day.

Hi back!End

这个解决方案比使用正则表达式更快,因为这涉及到很多开销,比如编译模式,创建一个Matcher等,并且regexp也是更通用的。它还在引擎盖下创建许多临时对象,在更换后丢弃。这里我只使用一个StringBuilder(在它下面加上char数组),代码只对输入String进行一次迭代。另外,这个解决方案比使用StringBuilder.replace()快得多,详细在这个答案的顶部。

注释和说明

我在replaceTags()方法中初始化StringBuilder,如下所示:

StringBuilder sb = new StringBuilder(src.length() + src.length() / 2);

所以基本上我创建它的初始容量为原始字符串长度的150%。这是因为我们的替换比可替换文本更长,因此如果发生替换,输出将显然比输入长。给StringBuilder一个更大的初始容量将导致根本没有内部char []重新分配(当然,所需的初始容量取决于可替换替换对及其在输入中的频率/出现,但是这50%是一个很好的上估计)。

我还利用了这样一个事实,所有可替换的字符串以’

int ltIdx = src.indexOf('

它只是一个简单的循环和字符串中的字符比较,并且由于它总是开始从pos(而不是从输入的开始)搜索,整体代码迭代的输入字符串只有一次。

最后,告诉我们是否在可能的位置发生了可替换的字符串,我们使用String.regionMatches()方法来检查可替换的字符串,这也是快速的,因为它只是比较循环中的char值,并返回第一次不匹配字符。

和PLUS:

问题没有提到它,但我们的输入是一个HTML文档。 HTML标签不区分大小写,这意味着输入可能包含< H1>而不是< h1&gt ;.

对这个算法这不是一个问题。 String类中的regionMatches()有一个重载,即supports case-insensitive comparison:

boolean regionMatches(boolean ignoreCase, int toffset, String other,

int ooffset, int len);

所以如果我们想修改我们的算法,也找到和替换输入标签,它们是相同的,但使用不同的大小写写,所有我们要修改的是这一行:

if (src.regionMatches(true, ltIdx, key, 0, key.length())) {

使用此修改后的代码,可替换标记变得不区分大小写:

Yo

TITLE

Hi!

Nice day.
Hi back!
End

YoTITLEHi!Nice day.

Hi back!End

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值