String.replaceAll方法详析(正则妙用)

前言

我通常是不太关心代码的具体实现的,因为我的开发语言很杂,倾向于一些最简单通用的方式去解决。今儿不小心在群里看到一位朋友发了下面的java代码,感觉自己还是很局限很无知的:

String str1 = "createTime";
String str2 = "createTimeAt";
String regex = "([A-Z])+";

System.out.println(str1.replaceAll(regex, "_$1").toLowerCase());
System.out.println(str2.replaceAll(regex, "_$1").toLowerCase());

//result
//create_time
//create_time_at

通过输出可以看到,这段代码的作用是把驼峰命名格式的字符串替换成下划线分割,这个功能比较简单,但是吸引我的却是他的代码。

"createTime".replaceAll("([A-Z]+)","_$1")

这行代码简单的很,就是调用了String类的replaceAll方法,方法的第一个参数是正则表达式,第二个参数是将要被替换成的新值。

让我惊奇的是他代码中,replaceAll的第二个参数,也就是JDK文档中名为replacement的参数,竟然是_$1。这是什么鬼?还支持类似占位符这样的东西?我一直都不知道。

问题探索

由于之前研究过一段正则表达式,通过观察replaceAll的第一个参数([A-Z]+),我猜想,这个应该是用到了正则表达式的分组,对应JDK中,就是java.util.regex.Matcher类的group()方法。

在Linux的Sed命令上,就使用&进行了一些替换,道理应该是相通的。

于是看了下String.replaceAll方法是如何实现的。JDK:

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

哦,原来它底层就是用了Matcher,只不过用的是Matcher自己的replaceAll方法。

去看它的文档,这个方法的参数果然有鬼,看下面实现代码。

public String replaceAll(String replacement) {
    reset();
    boolean result = find();
    if (result) {
      StringBuilder sb = new StringBuilder();
      do {
        appendReplacement(sb, replacement);
        result = find();
      } while (result);
      appendTail(sb);
      return sb.toString();
    }
    return text.toString();
  }

里面关键的部分就是文档中说的appendReplacement方法,然后可以看到详细的描述文档。

看到这里明白了,原来这个方法的replacement参数可以通过$字符来指代Matcher通过正则匹配得到的分组,支持name和number 两种方式,这里对应的就是Matcher类的group(name)和group(int)两个方法。

结论

1、String的replaceAll方法实际上是通过java.util.regex.Matcher类的replaceAll()方法实现的。

2、java.util.regex.Matcher类的replaceAll方法又是通过调用appendReplacement方法实现替换逻辑

3、Matcher类的appendReplacement方法的replacement参数支持通过$符号来指代Matcher匹配的分组

下面这串代码,就是使用Matcher类分组的一个最佳实践。

String data = "哈哈哈,xjjdog的手机号码是:12345678901,你会打给我吗";
//通过Matcher的分组功能,可以提取出上面字符串中的手机号
Matcher matcher = Pattern.compile(".*(xjjdog的手机号码是:([0-9]{11}))").matcher(data);
while (matcher.find()) {
  System.out.println("G0:" + matcher.group(0));
  System.out.println("G1:" + matcher.group(1));
  System.out.println("G2:" + matcher.group(2));
}
//result
//G0:哈哈哈,xjjdog的手机号码是:12345678901
//G1:xjjdog的手机号码是:12345678901
//G2:12345678901

group(0)表示整个字符串

group(1)表示第一个匹配的,上面的例子中就是(我的手机号码是:([0-9]{11}))部分

group(2)表示第二个匹配的,上面的例子中就是([0-9]{11})部分

使用分组可以用来提取字符串中的目标字符串值,很好用!

几个例子

下面是几个例子,大家可以触类旁通。

驼峰转下划线命名

public static String camelToUnderline(String camelName) {
return camelName.replaceAll("([A-Z]+)", "_$1").toLowerCase();
}

下划线转驼峰

这个稍微麻烦点,是模仿者Matcher.replaceAll方法写的。

public static String underlineToCamel(String underlineName) {
    Matcher matcher = Pattern.compile("(_[a-z]{1})").matcher(underlineName);
    StringBuffer result = new StringBuffer();
    while (matcher.find()) {
      String replacement = matcher.group(1);
      matcher.appendReplacement(result, replacement.replace("_", "").toUpperCase());
    }
    matcher.appendTail(result);
    return result.toString();
}

另外,Mybatis Generator插件源码中的也提供了类似方法(JavaBeansUtil.getCamelCaseString),这里做了下简单修改

 public static String getCamelCaseString(String inputString) {
    StringBuilder sb = new StringBuilder();
    boolean nextUpperCase = false;
    for (int i = 0; i < inputString.length(); i++) {
      char c = inputString.charAt(i);
      switch (c) {
        case '_':
        case '-':
        case '@':
        case '$':
        case '#':
        case ' ':
        case '/':
        case '&':
          if (sb.length() > 0) {
            nextUpperCase = true;
          }
          break;
        default:
          if (nextUpperCase) {
            sb.append(Character.toUpperCase(c));
            nextUpperCase = false;
          } else {
            sb.append(Character.toLowerCase(c));
          }
          break;
      }
    }
    return sb.toString();
  }

没有复杂的正则参与,速度显而快了不少。

总结

看一些优秀的开源代码,确实能够了解到一些实用的技巧。这比起自己费劲心力重复制造一些轮子,要高效的多。时间要用在刀刃上,但不是用来切豆腐。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • 浅谈java中replace()和replaceAll()的区别

    replace和replaceAll是JAVA中常用的替换字符的方法,它们的区别是: 1)replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换(CharSequence即字符串序列的意思,说白了也就是字符串): 2)replaceAll的参数是regex,即基于规则表达式的替换,比如,可以通过replaceAll("\\d", "*")把一个字符串所有的数字字符都换成星号; 相同点:都是全部替换,即把源字符串中的某一字符

  • 浅谈Java中replace与replaceAll区别

    看门见山 1.java中replace API: replace(char oldChar, char newChar):寓意为:返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的. replace(CharSequence target, CharSequence replacement):寓意为:使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串. replaceAll(String regex, String replacem

  • Java中replace、replaceAll和replaceFirst函数的用法小结

    首先概述一下他们三个的用法: · replace(CharSequence target, CharSequence replacement) ,用replacement替换所有的target,两个参数都是字符串. · replaceAll(String regex, String replacement) ,用replacement替换所有的regex匹配项,regex很明显是个正则表达式,replacement是字符串. · replaceFirst(String regex, String

  • String.replaceAll方法详析(正则妙用)

    前言 我通常是不太关心代码的具体实现的,因为我的开发语言很杂,倾向于一些最简单通用的方式去解决.今儿不小心在群里看到一位朋友发了下面的java代码,感觉自己还是很局限很无知的: String str1 = "createTime"; String str2 = "createTimeAt"; String regex = "([A-Z])+"; System.out.println(str1.replaceAll(regex, "_$1

  • C++模拟实现string的方法详解

    目录 1.string 成员变量 2.构造函数 3.拷贝构造.赋值重载和析构函数 1.拷贝构造 2.赋值重载 3.析构函数 4.访问成员变量 5.遍历 1.下标+[] 2.迭代器(iterator) 3.范围for 6.空间的申请 1.reserve 2.resize 7.增删查改 8.重载cin 和 cout 1.cout 2.cin 1.string 成员变量 首先需要一个动态开辟的指针指向这个字符串,然后还需要容量和存储的个数,并且我们不能和标准库的string进行冲突所以我们需要写在我们

  • C++ string字符串的修改与替换方法详析

    字符串内容的变化包括修改和替换两种.本节将分别讲解字符串内容的修改和字符串内容的替换. 字符串内容的修改 可以通过使用多个函数修改字符串的值.例如 assign(),operator=,erase(),交换(swap),插入(insert)等.另外,还可通过 append() 函数添加字符. 下面逐一介绍各成员函数的使用方法. assign()函数 使用 assign() 函数可以直接给字符串赋值.该函数既可以将整个字符串赋值给新串,也可以将字符串的子串赋值给新串.其在 basic_string

  • Angular中响应式表单的三种更新值方法详析

    前言 众所周知Angular响应式表单相比较模板驱动表单更大操作性.更易测试性.因此,我更推荐这类表单创造方式. 当一个用于修改用户信息的表单,数据的来源总是来自远程:而对于一个 FormGroup 的创建总在 ngOnInit 中完成.因此,这里会有一个表单更新值的问题. 而通常我们会透过 FormGroup 下的三种方式 setValue.patchValue.reset 将值写入表单当中. 当然,或许我说的这三种方式时你压根就没有做过,那说明在表单上你依赖的是双向绑定 [(ngModel)

  • Spring中循环依赖的解决方法详析

    前言 说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚.本文就试着尽自己所能,对此做出一个较详细的解读.另,需注意一点,下文中会出现类的实例化跟类的初始化两个短语,为怕园友迷惑,事先声明一下,本文的实例化是指刚执行完构造器将一个对象new出来,但还未填充属性值的状态,而初始化是指完成了属性的依赖注入. 一.先说说Spring解决的循环依赖是什么 Java中的循环依赖分两种,一种是构造器的循环依赖,另一种是属性的循环依赖.

  • VScode中C++头文件问题的终极解决方法详析

    目录 引言 局部配置全局配置傻傻分不清楚 一些有帮助的信息 总结 引言 之前在配置VScode环境的时候,按照网上的文章配置,总是找不到头文件,搜索解决方案,都是千篇一律,没有说到重点.在此详细解释一下. 局部配置全局配置傻傻分不清楚 网上很多文章都在讲一个配置文件c_cpp_properties.json,但是有些人不知道什么原因是找不到这个配置文件的.在扩展面板中,点击C++的设置进入的页面其实是通用设置页面,大概长这样: 这里只能看到"在settings.json中编辑"的选项,

  • Django压缩静态文件的实现方法详析

    django静态文件配置原理 静态文件配置就是为了让用户请求时django服务器能找到静态文件返回. 首先要理解几个概念: 媒体文件:用户上传的文件 静态文件:css,js,image等 开发环境:使用django内置服务器处理静态文件 生产环境:使用apache2/nginx服务器处理静态文件映射 所以在配置时要分清楚开发环境还是生产环境,这个后面会详细介绍. 下面先介绍一下服务器查找静态文件的原理,这样我们才能更好的配置. 引言 在网站开发阶段,对于静态资源文件比如JS,CSS等文件都是未经

  • Java String对象使用方法详解

    Java String对象使用方法详解 先来看一个例子,代码如下: public class Test { public static void main(String[] args) { String str = "abc"; String str1 = "abc"; String str2 = new String("abc"); System.out.println(str == str1); System.out.println(str1

  • C# 为String类型增加方法详解

    namespace MyExtensionMethods { public static class MyExtensions { public static int MyGetLength(this System.String target) { return target.Length; } } } 使用时,需要引入这个名字空间,引用如下: string str = "dafasdf"; int len = str.MyGetLength(); 以上这篇C# 为String类型增加

  • 对C++ string append方法的常用用法详解

    C++ string append()添加文本 使用append()添加文本常用方法: 直接添加另一个完整的字符串: 如str1.append(str2); 添加另一个字符串的某一段子串: 如str1.append(str2, 11, 7); 添加几个相同的字符: 如str1.append(5, '.'); 注意,个数在前字符在后.上面的代码意思为在str1后面添加5个".". //======================================== #include<

随机推荐