作者网址:

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. Unable to execute dex: java.nio.BufferOverflowException.解决办法

    异常提示: [2014-01-16 09:27:35 - Dex Loader] Unable to execute dex: java.nio.BufferOverflowException. Ch ...

  2. ThreadPoolExecutor机制

    一.概述 1.ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等等服务: 2.Execu ...

  3. 常用机器视觉工具----图像分析工具(blob分析)

    http://blog.sina.com.cn/s/blog_67cc4eb70100ivnt.html Blob分析:Blob分析目的在于对图像中的2-D形状进行检测和分析,得到诸如目标位置.形状. ...

  4. BZOJ4415: [Shoi2013]发牌

    显然可以线段树或树状数组上二分. 然而直接写splay在bzoj上并不会T. 然而发这题的目的只是因为我又忘了return了啊啊啊啊(TдT) 内心十分崩溃.关键是在本地还能过. #include&l ...

  5. 手写控件,frame,center和bounds属性

    一.手写控件 1.手写控件的步骤 (1)使用相应的控件类创建控件对象 (2)设置该控件的各种属性 (3)添加控件到视图中 (4)如果是button等控件,还需考虑控件的单击事件等 (5)注意:View ...

  6. Linux防火墙:iptables禁IP与解封IP常用命令

    在Linux服务器被攻击的时候,有的时候会有几个主力IP.如果能拒绝掉这几个IP的攻击的话,会大大减轻服务器的压力,说不定服务器就能恢复正常了. 在Linux下封停IP,有封杀网段和封杀单个IP两种形 ...

  7. C#的两个大方向

    http://zhidao.baidu.com/link?url=wG9G_EaT3tRd5_7ndU3vpFqmuc6S8N7F5TpBYsDPEui3HQ-wcU7nqw-_aRpIwtXQbC3 ...

  8. Laravel教程 七:表单验证 Validation

    Laravel教程 七:表单验证 Validation 此文章为原创文章,未经同意,禁止转载. Laravel Form 终于要更新这个Laravel系列教程的第七篇了,期间去写了一点其他的东西. 就 ...

  9. 自然语言16_Chunking with NLTK

    Chunking with NLTK 对chunk分类数据结构可以图形化输出,用于分析英语句子主干结构 # -*- coding: utf-8 -*-"""Created ...

  10. JavaWeb学习笔记——XML解析

    DOM解析操作 只在跟节点<addresslist>下面建立一个子节点<name> <?xml version="1.0" encoding=&quo ...