剑指offer--替换空格

==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍


题目:

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,
当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

这个题目其实很简单,没有绕的地方。
但是,做的过程中,发现使用java string的replaceAll方法居然比自己实现的要快很多,接下来就是要深挖一下这个坑
注:以下分析只针对java,其它语言,例如C/C++可能不适用

代码:

使用java string自带的方法replaceAll。

	public  String replace1(StringBuffer str){
        return str == null ? null : str.toString().replaceAll(" ", "%20");
    }

自己实现

原始代码:

	public String replace2(StringBuffer sb){
        StringBuffer res = new StringBuffer(sb.length());
        for (int i = 0; i < sb.length(); i++) {
            if (sb.charAt(i) == ' '){
                res.append("%20");
            } else {
                res.append(sb.charAt(i));
            }
        }
        return res.toString();
    }

优化代码1:
和原始代码的区别在于StringBuilder和StringBuffer的区别(具体什么区别,先想想,答案在最后的总结)

	public String replace3(StringBuffer sb){
        StringBuilder res = new StringBuilder(sb.length());
        for (int i = 0; i < sb.length(); i++) {
            if (sb.charAt(i) == ' '){
                res.append("%20");
            } else {
                res.append(sb.charAt(i));
            }
        }
        return res.toString();
    }

优化代码2:
和优化代码1的区别在于先将StringBuffer转String(为什么要转?)

	public String replace4(StringBuffer sb){
        String a = sb.toString();
        StringBuilder res = new StringBuilder(a.length());
        for (int i = 0; i < a.length(); i++) {
            if (a.charAt(i) == ' '){
                res.append("%20");
            } else {
                res.append(a.charAt(i));
            }
        }
        return res.toString();
    }

优化代码3:
和优化代码2的区别在于一块一块的复制(和代码2的一个个字符复制哪个更快?)

	public String replace5(StringBuffer sb){
        String a = sb.toString();
        StringBuilder res = new StringBuilder(a.length());
        int start = 0, end = 0;
        for (int i = 0; i < a.length(); i++) {
            if (a.charAt(i) == ' '){
                res.append(a, start, end);
                res.append("%20");
                start = i + 1;
            }
            end = i + 1;
        }
        if(start < end) {
            res.append(a, start, end);
        }
        return res.toString();
    }

速度测试

以下测试取10次平均值,并且空格占比为1/10(空格占增大,replaceAll效率会怎样变?自己实现的代码效率会怎样变?)

代码 / 字符串长度10241024 * 101024 * 1001024 * 10001024 * 100001024 * 100000
replace1(replaceAll)0.7 ms1.1 ms4.7 ms15.8 ms123.0 ms1029.1 ms
replace2(原始代码)0.3 ms1.8 ms9.9 ms76.9 ms744.5 ms7207.0 ms
replace3(优化代码1)0.2 ms1.2 ms8.3 ms71.3 ms696.5 ms7018.6 ms
replace4(优化代码2)0.1 ms0.8 ms2.0 ms9.1 ms69.5 ms644.8 ms
replace5(优化代码3)0.2 ms0.7 ms2.6 ms10.0 ms72.8 ms733.2 ms

总结

1.在这个题中replaceAll的思想和replace5一样,都是从头开始找,找到空格后,将两个空格之间的字符串复制过去(底层还是一个个字符的赋值)。

2.replace3和replace2的优化点在于,StringBuffer是线程安全的,每一个方法都加了锁,而StringBuilder是非线程安全的,方法没有加锁,所以从测试中可以看出replace3的速度比replace2的速度要快一点。

3.replace4和replace3的优化点在于先将StringBuffer转String,为什么要这样呢?原因是for循环中用到了str.length()和str.charAt(),这个两个方法是同步方法,加锁了的,每一次循环,去执行一次这个方法,会消耗比较多的时间。所以从测试中可以看出replace4比replace3速度快了10倍左右。

4.replace5和replace4的优化点在于一块一块的复制,但是从测试的结果看,其速度居然是慢了的,这是为什么呢?贴一下源码,大家就懂了。

	public AbstractStringBuilder append(CharSequence s, int start, int end) {
        if (s == null)
            s = "null";
        if ((start < 0) || (start > end) || (end > s.length()))
            throw new IndexOutOfBoundsException(
                "start " + start + ", end " + end + ", s.length() "
                + s.length());
        int len = end - start;
        ensureCapacityInternal(count + len);
        for (int i = start, j = count; i < end; i++, j++)
            value[j] = s.charAt(i);
        count += len;
        return this;
    }

它里面也是一个个字符赋值给value数组的,所以速度肯定是replace5>replace4的。
那么什么情况下可以像C/C++那样,直接用指针将一块块内存复制过去呢?

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    
  	public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

System.arraycopy是JVM自己实现的本地方法,可能底层用的就是C/C++内存块复制(我猜的,没研究过这个方法)。
5.空格占比大的时候,replaceAll方法的速度会渐渐的慢于自己实现的代码,replace2和replace3速度会慢慢变快,replace4和replace5速度会慢慢变慢,感兴趣的可以自己测试一下。
6.虽然这道题,不论怎么写,都可以AC,但是如果我是面试官,我会就这道题,从系统自带的实现,到自己实现,再到优化来考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值