背景
JDK在String類中給我們提供的API,replace是個使用頻率很高的的方法。因為他可以對字符串內容進行替換,只需要輸入替換字符串和被替換字符串,就可以輕松得到你想要的字符串,功能非常強大。從JDK里的說明就能看出它有多方便了:
源碼:
public String replace(CharSequence target, CharSequence replacement) { return Pattern.compile(target.toString(), Pattern.LITERAL).matcher( this).replaceAll(Matcher.quoteReplacement(replacement.toString())); }
事故回放
好了,接下來描述的場景純屬虛構,如有雷同,那一定是你也踩過類似的坑! 今天產品MM找你實現一個功能,要求對你們網站上的一些不友好的評論使用“嗶~”屏蔽掉,但是判斷不友好的功能比較復雜,用到了NLP,所以用到了算法同事給你提供的接口判斷,那么接下來事情就簡單了: 我們只需要將NLP處理之后返回來的字符串作為String.replace(target, replacement)的入參target,"嗶~"作為replacement就好了。相信熟練的你很快能噼里啪啦敲出以下代碼:
// 調用NLP接口判斷不友好的內容 List<String> waitForReplace = callRpcNLP(text); if (waitForReplace == null || waitForReplace.size() == 0) { return; } // 逐一進行替(和)換(諧) for (String target : waitForReplace) { if (target == null) { continue; } text.replace(target, "嗶~"); }
看起來很不錯,各種校驗也都有了,我的代碼果然寫得優美又健壯,你已經忍不住陶醉在自己的傑作中了,那么這樣有沒問題呢?
事實上,到了真正運行的時候,內存爆了!!!
案情分析
原因之一
那么到底發生了什么,debug之下,我們定位到replace方法,發現正是因為遠程RPC接口返回了空字符串"",而空字符串作為replace的入參target時,會對任意字符串匹配多次匹配成功。我們用個簡單點的字符串來做下實驗: String text = "hello"; System.out.println(text.replace("","*"));
打印:
嗶!h嗶!e嗶!l嗶!l嗶!o嗶!
大家能看到,替換后的字符串出現了6個“嗶~”。也就是說,一個簡簡單單的"hello"字符串,在repalce運行之后,被""匹配出了6處需要替換的地方,那么如果不是一個"hello",而是一大段文本,一篇幾千字的論文呢?到了這里,我們距離真相已經很近了,""會導致replace方法對字符串進行多次匹配。這是內存爆了的其中一個原因。
原因之二
至於另一個原因,就得說下replace的實現了,上面說到的字符串匹配,大家很容易想到正則表達式,實際上,replace內部也確實是通過調用正則表達式相關的API,來實現字符串匹配的。xxxxxxxxxx 原因之二至於另一個原因,就得說下replace的實現了,上面說到的字符串匹配,大家很容易想到正則表達式,實際上,replace內部也確實是通過調用正則表達式相關的API,來實現字符串匹配的。 而正則表達式實現字符串匹配,實際上是個很復雜的操作,replace(target, replacement)中的target,在正則表達式中稱為"模式"(pattern),而匹配的過程,需要每次從字符串拿出一個字符和模式中的字符匹配。如果匹配成功,那么字符串拿出下一個字符,模式也拿出下一個字符,繼續下一輪的匹配,但是我們知道,正則表達式支持使用點“.”匹配任意字符,星號"*"匹配任意數量的字符,所以整個匹配的過程需要遞歸進行,沒法通過兩個字符串簡單地一對一移動指針來完成匹配。 總結
-
入參target不但需要做null校驗,還要做""空字符串校驗
-