作者网址:

https://my.oschina.net/shipley/blog/98973

背景:  
    前几天有人发了一个关于下面问题的贴,对这个有点好奇,故花时间做了点研究。
    
    对单个反斜杠字符串替换成双斜杠的Java实现如下:
    String s = "\\";
    方法一:String sr1 = s.replaceAll("\\\\", "\\\\\\\\");
    方法二:String sr1 = s.replaceAll("\\\\", "$0$0");
    
    我第一眼看到比较困惑,下面慢慢来分析。

分析:
    对String类的replaceAll(String reg, String replacement)方法分析

一、两点疑惑
    A.    为啥第一个参数reg必须是”\\\\”?
    B.    为啥第二个参数replacement 必须是”\\\\\\\\”?
    
     二、解答
    A.因为reg这个参数表示一个正则表达式,首先字符串“\\\\”被转义后代表的实际是字符串\\,这就是正则表达式,那么在正则表达式里也有转义,那么这个正则匹配的就是\
    B.首先字符串“\\\\\\\\”被转义后实际代表的其实是字符串\\\\;
    接下来才是重点:
        查看源码replaceAll方法的实现如下

    public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }

其中Pattern.compile(regex).matcher(this), 返回的是一个Matcher对象。
        先简单介绍java.util.regex.Matcher类,是通过解释 Pattern 对 字符序列执行匹配操作的引擎,其中持有对当前Pattern对象和当前String对象的引用。
        执行一次调用其find方法,即对字符串执行一次从左向右的以Pattern为正则的匹配,并记录下匹配结果字符串的开始和结束位置索引,以及更新一个记录当前匹配结果的分组groups。
        
         顺藤摸瓜,进入Matcher类的replaceAll方法,继续查看源码,

        public String replaceAll(String replacement) {
            
            // 对当前Matcher类进行重置,即对其中记录匹配结果的开始和结束位置索引,以及分组信息重置
            reset();
            
            // 执行第一次搜索
            boolean result = find();
            
            if (result) {
                // 第一次搜索匹配成功
                
                // 用于记录最终的替换结果字符串
                StringBuffer sb = new StringBuffer();
                
                // 循环搜索
                do {
                    // *重点在此方法内:用于将从上一次匹配子字符串的下一个索引位置开始,到当前匹配的子字符串的结束索引位置的所有字符 append到字符串sb中
                    // 有点绕,可以暂时跳过,下面会对该方法进一步分析
                    appendReplacement(sb, replacement);
                    result = find();
                } while (result);
                
                // 将从最后一次匹配子字符串的下一个索引位置,到字符串的结尾的所有字符append到字符串sb中
                appendTail(sb);
                return sb.toString();
            }
            return text.toString();
        }

继续南下,进入Matcher类的appendReplacement方法,

        public Matcher appendReplacement(StringBuffer sb, String replacement) {
            
            ...省略部分代码
            
            // 用于跟踪replacement字符串的索引
            int cursor = 0;
                                    
            String s = replacement;// Java api源码也有垃圾代码啊,呵呵 (s局部变量并未在后续代码中被使用)
            
            // 对当前匹配到子字符串替换后的结果字符串
            StringBuffer result = new StringBuffer();
            
            // 遍历replacement字符串
            while (cursor < replacement.length()) {
                
                char nextChar = replacement.charAt(cursor);
                
                if (nextChar == '\\') {
                
                    // 重点1:当字符为\时,跳过,并获取其后面的字符,追加到result
                    cursor++;
                    nextChar = replacement.charAt(cursor);
                    result.append(nextChar);
                    cursor++;
                } else if (nextChar == '$') {
                    
                    // 重点2:当字符为$时,跳过,并获取其后面的数值,并以此如果$后面第一个不为数字则抛异常,
                    // Skip past $
                    cursor++;
                    
                    // The first number is always a group
                    int refNum = (int)replacement.charAt(cursor) - '0';
                    
                    // 此处代码用于计算$符号后的数值,数值结果赋予refNum
                    ...省略部分代码
                    
                    // group(refNum) 用于获取正则表达式第refNum个分组表示的字符串,不详说了
                    if (group(refNum) != null)
                        result.append(group(refNum));    // 追加到result
                } else {
                    
                    // 当前字符不为\ 或 $ 则直接追加到result
                    result.append(nextChar);
                    cursor++;
                }
            }
            
            // 将从上一次匹配的子字符串的结尾索引,到当前匹配的第一个字符串索引的字符串追加到sb
            // lastAppendPosition参数为上一次执行appendReplacement方法最后追加的字符在原始字符串中的索引位置。
            // first 参数为当前待替换的子字符串的首个字符在原始字符串中的索引位置
            sb.append(getSubSequence(lastAppendPosition, first));
            
            // 将当前配置子字符串替换后的结果字符串追加到sb
            sb.append(result.toString());
            
            // 更新lastAppendPosition,供下一个匹配执行appendReplacement方法使用
            lastAppendPosition = last;
            
            
            /*
            
                到此, sb中追加了当前匹配的子字符串与前一次匹配子字符串中间的字符,以及当前匹配子字符串被替换后的字符串
            
            */
            
            return this;
        }

分析结束。
        
        
总结
    1、replaceAll中第二个参数replacement中,\有转义的作用,$用于获取分组匹配的当前子字符串
        现在想想为什么要引入这个\转义的功能? 我的猜测是 ----- 因为引入了$符的分组功能,所以为了解决能输出$字符,故引入\转义功能
    
    2、有助于理解Java的正则表达式;
    
    3、世界上没有十全十美的代码,Java源码里也有垃圾代码,呵呵。
    
    
     提供几个问题大家可以实践下:
        1、对两个反斜杠字符串每个字符串都替换成双斜杠,如何实现?
            即String s = "\\\\";  替换成  String sr = "\\\\\\";
            
        2、将单反斜杠替换成美元符,如何实现?
            即String s = "\\";  替换成  String sr = "$";
            
        3、String s = "Jack is Rose's boyfriends."
           使用$分组替换功能 替换成
           String sr = "Rose is Jack's girlfriends."

String类replaceAll方法正则替换深入分析的更多相关文章

  1. 《java入门第一季》之类(String类常见方法小叙)

    String类下面的构造方法和一些常见的方法: /* * 字符串:就是由多个字符组成的一串数据.也可以看成是一个字符数组. * 通过查看API,可以知道 * A:字符串字面值"abc&quo ...

  2. C++中string类的方法

    C++ string类的方法 具体每个方法怎么使用,可以参考相应的链接. 总的链接为http://www.cplusplus.com/reference/string/string/(C++参考文档) ...

  3. string类find_first_not_of ()方法

    string类find_first_not_of ()方法 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://xfqxj.blog. ...

  4. 深入分析Java的String类的方法与特点

    字符串是任何编程语言都必须支持的变量类型,有些编程语言是直接提供了原生的变量类型,有些编程语言则使用语法特性以 SDK 的形式提供支持.在Java编程平台中,对字符串的支持使用了后者的形式,就是通过在 ...

  5. Java中String类的方法及说明

    String : 字符串类型 一.      String sc_sub = new String(c,3,2);    //      String sb_copy = new String(sb) ...

  6. String的replaceAll方法中的正则表达式用法

    项目里面 需要对已手机号码进行 如下的显示 比如15088688388 要显示为150****8388的效果 实现这个简单的效果 方法有很多 我想试试用正则表达式去实现 查了点资料最终试出来以下方法可 ...

  7. 【转载】Java中String类的方法及说明

    转载自:http://www.cnblogs.com/YSO1983/archive/2009/12/07/1618564.html String : 字符串类型 一.      String sc_ ...

  8. 『Java』String类使用方法

    Java中的字符串 java.lang.String类表示字符串类,Java程序中所有字符串文字都可以看作实现该类的实例. 特点: 字符串不可变:字符串的值在创建后不能在发生改变 public cla ...

  9. JDK6与JDK7中String类subString()方法的区别

    1.subString()方法的作用 subString(int beginIndex, int endIndex)方法的返回的是以beginIndex开始到 endIndex-1结束的某个调用字符串 ...

随机推荐

  1. appium+java+junit demo运行

    对java熟悉一些,所以想用java把appium给做起来.今天用myeclipse给环境和Demo弄了一下,网上没有一篇全套资料的. 各块环境的搭建: 1.安装appium客户端,省略下载和安装步骤 ...

  2. web中异步和同步的理解

    普通的B/S模式就是同步,而AJAX技术就是异步,当然XMLHttpReques有同步的选项. 同步:提交请求->等待服务器处理->处理完毕返回.这个期间客户端浏览器不能干任何事. 异步: ...

  3. Windows系统自带工具的 cmd 命令

    目标 与计算机高手无关,只是为了减少鼠标点击的次数,提高效率. 适用范围 Windows XP,Windows 7,Window 8 (在Windows 7 下验证通过.) 使用方法 在 “运行“ 对 ...

  4. 自然语言12_Tokenizing Words and Sentences with NLTK

    https://www.pythonprogramming.net/tokenizing-words-sentences-nltk-tutorial/ # -*- coding: utf-8 -*- ...

  5. .Net中使用OracleDataAdapter

    本来只想简单记录一下OracleDataAdapter的批量增加和修改用法的,在园子里看到一篇比较详细的就在这分享了(Oracle Data Provider for .NET),虽然用的是 Upda ...

  6. Embedding Scripts

    Mono http://www.mono-project.com/docs/advanced/embedding/ http://www.mono-project.com/docs/advanced/ ...

  7. FPS

    游戏的FPS跟什么有关 http://zhidao.baidu.com/link?url=jZJoN_-MTC9fWCBSBaL5vezBp13MRSv8PdCcQMbwX5tiFGzxNEHe7UB ...

  8. 固定IP 正常访问谷歌

    如题 地址栏直接输入 http://173.194.1.150/ 正常使用 ~标记一下~

  9. js中,还真不了解 console

    参考链接: https://segmentfault.com/a/1190000000481884

  10. 固定导航(Sticky nav)

    方法1: <div class="footer"></div> .footer{ position:fixed; bottom:0; left:0; wid ...