作者网址:

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. Linux 吃掉我的内存

    在Windows下资源管理器查看内存使用的情况,如果使用率达到80%以上,再运行大程序就能感觉到系统不流畅了,因为在内存紧缺的情况下使用交换分区,频繁地从磁盘上换入换出页会极大地影响系统的性能.而当我 ...

  2. bootstrap学习总结-04 常用标签2

    1 表格 Bootstrap为表格设计了漂亮的样式. 1)表格基本实例 任意 <table> 标签添加 .table. <table class="table"& ...

  3. Oracle - 数据库的实例、表空间、用户、表之间关系

    完整的Oracle数据库通常由两部分组成:Oracle数据库和数据库实例. 1) 数据库是一系列物理文件的集合(数据文件,控制文件,联机日志,参数文件等): 2) Oracle数据库实例则是一组Ora ...

  4. Flask-WTF form doesn't have attribute 'validate_on_submit'问题

    今天在学习WTF表单的时候遇到了这个问题,在stackoverflow上搜索查到了解决方案 from flask.ext.wtf import Form from wtforms import Tex ...

  5. eclipse下遇到 无法解析类型 javax.servlet.http.HttpServletRequest

    参考:http://bbs.csdn.net/topics/370187655?page=1   java.lang.Error: 无法解析的编译问题: 无法解析类型 javax.servlet.ht ...

  6. Json数据可视化

    主要借助JSON.stringfy( value [, replacer] [, space] ). 一.参考文献 1.json数据可视化: http://www.cnblogs.com/lvdaba ...

  7. ConterReplaceBehavior.class.php模板内容替换,如__PUBLIC__

    ConterReplaceBehavior.class.php查找 __PUBLIC__ protected function templateContentReplace($content) { / ...

  8. Redis总结(三)Redis 的主从复制

    接着上一篇,前面两篇我总结了<Redis总结(一)Redis安装>和<Redis总结(二)C#中如何使用redis> 所以这一篇,会讲讲Redis 的主从复制以及C#中如何调用 ...

  9. C# 实现 Snowflake算法 ID生成

    http://blog.csdn.net/w200221626/article/details/52064976 C# 实现 Snowflake算法 /// <summary> /// 动 ...

  10. Python开发【第十七篇】:MySQL(一)

    一.概述 1.什么是数据库 ? 答:数据的仓库,如:在ATM的示例中我们创建了一个 db 目录,称其为数据库 2.什么是 MySQL.Oracle.SQLite.Access.MS SQL Serve ...