Java String.replaceAll() 與後向引用(backreference)

来源:http://www.cnblogs.com/xiaomiganfan/archive/2016/03/30/5332555.html
-Advertisement-
Play Games

問題 昨天看到一篇博文,文中談到一道 Java 面試題: 給定一字元串,若該字元串中間包含 "*",則刪除該 "*";若該字元串首字元或尾字元為 "*",則保留該 "*"。 舉幾個例子(箭頭左邊為輸入,箭頭右邊為輸出): * --> * ** --> ** **** --> ** *ab**de** ...


問題

昨天看到一篇博文,文中談到一道 Java 面試題:

給定一字元串,若該字元串中間包含 "*",則刪除該 "*";若該字元串首字元或尾字元為 "*",則保留該 "*"。

舉幾個例子(箭頭左邊為輸入,箭頭右邊為輸出):

* --> *

** --> **

**** --> **

*ab**de** --> *abde*

我覺得應該用正則表達式來處理,但想不出正則表達式該怎麼寫。

第一種解答

該博文的回覆中有人給出下麵的答案

str.replaceAll("(^\\*)|(\\*$)|\\*", "$1$2");

上機驗證一下,答案是對的,但不懂為什麼正則表達式要這麼寫。到 stackoverflow 上發帖問了一下,才大概明白是怎麼回事兒。當時問的時候, 對這個問題想得不清楚,所以問的問題也是糊裡糊塗。

下麵是我的理解,不對之處請多拍磚:

replaceAll() 是 Java String 類的一個方法:

public String replaceAll(String regex, String replacement)
Replaces each substring of this string that matches the given regular expression with the given replacement.
(特別要註意的是,這個方法的第一個參數是一個正則表達式。我過去在第一個參數上栽過跟頭。不過,這回我栽在第二個參數上。)

"(^\\*)|(\\*$)|\\*" 解釋: 

(^\\*) :capturing group 1, 匹配字元串開始處的 *
(\\*$) :capturing group 2, 匹配字元串結尾處的 *
\\* : 匹配任意位置的 *
  • 因為 "*" 在正則表達式中是特殊字元,所以需要使用轉義字元 "\"。但在Java中 "\" 也是特殊字元,所以需要再一次使用 "\",這樣就造成 "*" 前面有兩個 "\"。
  • 圓括弧 "()" 把括弧內的內容作為一個 capturing group,為後面的 backreference 做準備。關於 capturing group 請看這裡
  • "|" 表明左右的表達式是 "或" 的關係。
  • "\\*" 單獨使用的話可以匹配字元串中任意位置的 "*"。但在上述的表達式中,開始和結尾處的 "*" 優先被 "(^\\*)" 或 "(\\*$)" 匹配了。
因此上面的表達式可以匹配字元串開始處的 "*",或者匹配字元串結尾處的 "*",或者匹配字元串任意位置的 "*"。也就是說,字元串中所有的 "*" 都匹配上了。 

"$1$2" 解釋:

$1 :backreference 第一個 capturing group
$2 :backreference 第二個 capturing group

這個參數中 "$1" 和 "$2" 的內容被用來替換前一個參數中匹配的字元串。

以字元串 "*ab**de**" 為例: 

  1. 第一個 "*" 匹配,使用 "$1$2" 來替換。這時 "$1" 的內容為 "*","$2" 的內容為空,所以第一個 "*"  被它自己替換。

  2. 接下來 "a" 和 "b" 都不匹配,略過,繼續往後走。

  3. 第二個 "*" 匹配,使用 "$1$2" 來替換。這時 "$1" 的內容為空,"$2" 的內容為空,所以這個 "*" 被替換為空。

  4. 第三個 "*" 跟第二個 "*" 一樣,也被替換為空。

  5. 接下來的 "d" 和 "e" 不匹配,繼續往後走。

  6. 第四個 "*" 匹配,跟第二個、第三個 "*" 一樣,被替換為空。

  7. 最後一個 "*" 匹配,使用 "$1$2" 來替換。這時 "$1" 的內容為空,"$2" 的內容為 "*",所以最後一個 "*" 被它自己替換。

  8. 最後的結果是:"*abde*" 

這裡有一點要註意:在正則表達式中,backreference 是用 "反斜杠 + 數字" 來表示的,比如:\1, \2 。但是,當 backreference 出現在替換字元串中時,Java 的 backreference 使用 "美元符號 + 數字" 來表示,比如:$1, $2 。據說這是跟 Perl 學的。不嫌累的話看看這個帖子吧。

第二種解答

另外一種使用正則表達式的方法是:如果 "*" 不在頭,也不在尾,則替換為空。這種想法很自然,但實現起來卻不容易。
String repl = str.replaceAll("(?<!^)\\*+(?!$)", "");

正則表達式解釋:

(?<!^)   # 如果前一個位置不是行首
\\*+     # 匹配一個或多個 *
(?!$)    # 如果下一個位置不是行尾

"?<!" 表示 Negative Lookahead,"?!" 表示 Negative Lookbehind 。詳細說明請參考這裡這裡

第三種解答

String repl = str.replaceAll("(^\\*)|(\\*$)|\\*+", "$1$2");

這個跟上面的第二種解答都是由同一個人回覆的,但這個解答有點問題:如果結尾處有兩個或兩個以上的 "*" 時,這些 "*" 都被替換為空。

例如,若輸入為 "*ab**de**",則輸出為"*abde",最後的那個 "*" 不見了。

這是因為預設情況下,正則匹配處於 Greediness(貪婪) 匹配模式,會匹配儘量多的字元。"\*+" 可以匹配一個或多個 "*" 。在倒數第二個 "*" 的時候,匹配一個 "*" 或兩個 "*" 都可以。但它比較貪婪,所以把最後兩個 "*" 都匹配上了,然後被 "$1$2" 替換為空。

把正則匹配改為 Laziness(偷懶)匹配可以解決這個問題。在表達式後面加一個 "?" 就變成 Laziness 匹配了:"\*+?" 。

String repl = str.replaceAll("(^\\*)|(\\*$)|\\*+?", "$1$2");

關於 Greediness 和 Laziness 請看這裡

正則表達式效率

該網站可以測試正則表達式,並給出詳細的解釋。它還給出匹配所需的步數,你可以用這個步數來比較表達式的效率。從這個網站上看,第二種方法效率最高。

參考鏈接


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 設計思路 為了設計一套具有較強可擴展性的用戶認證管理,需要建立用戶、角色和許可權等資料庫表,並且建立之間的關係,具體實現如下。 1.1 用戶 用戶僅僅是純粹的用戶,用來記錄用戶相關信息,如用戶名、密碼等,許可權是被分離出去了的。用戶(User)要擁有對某種資源的許可權,必須通過角色(Role)去關聯。 ...
  • 0x 00 前言 ZoomEye 的 API 在前幾天正式對外部開發,這對網路滲透人員來說是一件開心的事 可以說“媽媽再也不用擔心批量測(x)試(zhan)沒有資源了。” 官方的 API 幫助文檔在下麵: https://www.zoomeye.org/api/ 看了下,使用方法是先提交賬戶,密碼獲 ...
  • 1.決策樹的簡介 http://www.cnblogs.com/lufangtao/archive/2013/05/30/3103588.html 2.決策是實現的偽代碼 3.python數據結構設計 1.數據集:用於存儲二維的訓練數據training_data 二維的list數組,對於二維的lis ...
  • HashMap實現了Map介面,HashTable是Dictionary的子類; 主要區別有以下三點: 1.HashMap允許空的鍵值,也就是說 key 可以為 null(只能有一個key為null),而HashTable不可以; 2.HashMap不同步的,在多線程訪問時,需要為它的方法實現同步S ...
  • 實現了任意大數與 2^64-1以下的數相乘, 兩個任意大數可以將其中一個拆分成多個因數, 兩個大數質數暫未考慮 ...
  • 最近在學習PKI,順便接觸了一些加密演算法。對RSA著重研究了一下,自己也寫了一個簡單的實現RSA演算法的Demo,包括公、私鑰生成,加解密的實現。雖然比較簡單,但是也大概囊括了RSA加解密的核心思想與流程。這裡寫下來與大家分享一下。 RSA概述: RSA是目前最有影響力的公鑰加密演算法,它能夠抵抗到目前 ...
  • 文/JC_Huang(簡書作者)原文鏈接:http://www.jianshu.com/p/f4d7827821f1著作權歸作者所有,轉載請聯繫作者獲得授權,並標註“簡書作者”。 產品分析 首先我們來看一下市場上關於消息的實現是怎麼樣的。 簡書 簡書的消息系統主要分了兩種 簡信 提醒 簡信簡信的性質 ...
  • 使用Nginx代理多台伺服器實行負載的時候,如何查看某一個請求被轉發到哪台伺服器上呢? 加上如下紅色配置: 重啟Nginx,重新訪問,在瀏覽器中F12查看request的Headers信息,可以看到當前伺服器的IP ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...