컴퓨터 프로그램의 사고논리(89) - 정규표현식(중)
上节介绍了正则表达式的语法,本节介绍相关的Java API。
正则表达式相关的类位于包java.util.regex下,有两个主要的类,一个是Pattern,另一个是Matcher。Pattern表示正则表达式对象,它与要处理的具体字符串无关。Matcher表示一个匹配,它将正则表达式应用于一个具体字符串,通过它对字符串进行处理。
字符串类String也是一个重要的类,我们在29节专门介绍过String,其中提到,它有一些方法,接受的参数不是普通的字符串,而是正则表达式。此外,正则表达式在Java中是需要先以字符串形式表示的。
下面,我们先来介绍如何表示正则表达式,然后探讨如何利用它实现一些常见的文本处理任务,包括切分、验证、查找、和替换。
表示正则表达式
转义符 '\'
正则表达式由元字符和普通字符组成,字符'\'是一个元字符,要在正则表达式中表示'\'本身,需要使用它转义,即'\\'。
在Java中,没有什么特殊的语法能直接表示正则表达式,需要用字符串表示,而在字符串中,'\'也是一个元字符,为了在字符串中表示正则表达式的'\',就需要使用两个'\',即'\\',而要匹配'\'本身,就需要四个'\',即'\\\\',比如说,如下表达式:
<(\w+)>(.*)</\1>
对应的字符串表示就是:
"<(\\w+)>(.*)</\\1>"
一个简单规则是,正则表达式中的任何一个'\',在字符串中,需要替换为两个'\'。
Pattern对象
字符串表示的正则表达式可以被编译为一个Pattern对象,比如:
String regex = "<(\\w+)>(.*)</\\1>"; Pattern pattern = Pattern.compile(regex);
Pattern是正则表达式的面向对象表示,所谓编译,简单理解就是将字符串表示为了一个内部结构,这个结构是一个有穷自动机,关于有穷自动机的理论比较深入,我们就不探讨了。
编译有一定的成本,而且Pattern对象只与正则表达式有关,与要处理的具体文本无关,它可以安全地被多线程共享,所以,在使用同一个正则表达式处理多个文本时,应该尽量重用同一个Pattern对象,避免重复编译。
匹配模式
Pattern的compile方法接受一个额外参数,可以指定匹配模式:
public static Pattern compile(String regex, int flags)
上节,我们介绍过三种匹配模式:单行模式(点号模式)、多行模式和大小写无关模式,它们对应的常量分别为:Pattern.DOTALL,Pattern.MULTILINE和Pattern.CASE_INSENSITIVE,多个模式可以一起使用,通过'|'连起来即可,如下所示:
Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL)
还有一个模式Pattern.LITERAL,在此模式下,正则表达式字符串中的元字符将失去特殊含义,被看做普通字符。Pattern有一个静态方法:
public static String quote(String s)
quote()的目的是类似的,它将s中的字符都看作普通字符。我们在上节介绍过\Q和\E,\Q和\E之间的字符会被视为普通字符。quote()基本上就是在字符串s的前后加了\Q和\E,比如,如果s为"\\d{6}",则quote()的返回值就是"\\Q\\d{6}\\E"。
切分
简单情况
文本处理的一个常见需求是根据分隔符切分字符串,比如在处理CSV文件时,按逗号分隔每个字段,这个需求听上去很容易满足,因为String类有如下方法:
public String[] split(String regex)
比如:
String str = "abc,def,hello"; String[] fields = str.split(","); System.out.println("field num: "+fields.length); System.out.println(Arrays.toString(fields));
输出为:
field num: 3[abc, def, hello]
不过,有一些重要的细节,我们需要注意。
转义元字符
split将参数regex看做正则表达式,而不是普通的字符,如果分隔符是元字符,比如. $ | ( ) [ { ^ ? * + \,就需要转义,比如按点号'.'分隔,就需要写为:
String[] fields = str.split("\\.");
如果分隔符是用户指定的,程序事先不知道,可以通过Pattern.quote()将其看做普通字符串。
将多个字符用作分隔符
既然是正则表达式,分隔符就不一定是一个字符,比如,可以将一个或多个空白字符或点号作为分隔符,如下所示:
String str = "abc def hello.\n world"; String[] fields = str.split("[\\s.]+");
fields内容为:
[abc, def, hello, world]
空白字符串
需要说明的是,尾部的空白字符串不会包含在返回的结果数组中,但头部和中间的空白字符串会被包含在内,比如:
String str = ",abc,,def,,"; String[] fields = str.split(","); System.out.println("field num: "+fields.length); System.out.println(Arrays.toString(fields));
输出为:
field num: 4[, abc, , def]
找不到分隔符
如果字符串中找不到匹配regex的分隔符,返回数组长度为1,元素为原字符串。
切分数目限制
split方法接受一个额外的参数limit,用于限定切分的数目:
public String[] split(String regex, int limit)
不带limit参数的split,其limit相当于0。关于limit的含义,我们通过一个例子说明下,比如字符串是"a:b:c:",分隔符是":",在limit为不同值的情况下,其返回数组如下表所示:
Pattern的split方法
Pattern也有两个split方法,与String方法的定义类似:
public String[] split(CharSequence input)public String[] split(CharSequence input, int limit)
与String方法的区别是:
Pattern接受的参数是CharSequence,更为通用,我们知道String, StringBuilder, StringBuffer, CharBuffer等都实现了该接口;
如果regex长度大于1或包含元字符,String的split方法会先将regex编译为Pattern对象,再调用Pattern的split方法,这时,为避免重复编译,应该优先采用Pattern的方法;
如果regex就是一个字符且不是元字符,String的split方法会采用更为简单高效的实现,所以,这时,应该优先采用String的split方法。
验证
验证就是检验输入文本是否完整匹配预定义的正则表达式,经常用于检验用户的输入是否合法。
String有如下方法:
public boolean matches(String regex)
比如:
String regex = "\\d{8}"; String str = "12345678"; System.out.println(str.matches(regex));
检查输入是否是8位数字,输出为true。
String的matches实际调用的是Pattern的如下方法:
public static boolean matches(String regex, CharSequence input)
这是一个静态方法,它的代码为:
public static boolean matches(String regex, CharSequence input) { Pattern p = Pattern.compile(regex); Matcher m = p.matcher(input);return m.matches(); }
就是先调用compile编译regex为Pattern对象,再调用Pattern的matcher方法生成一个匹配对象Matcher,Matcher的matches()返回是否完整匹配。
查找
查找就是在文本中寻找匹配正则表达式的子字符串,看个例子:
public static void find(){ String regex = "\\d{4}-\\d{2}-\\d{2}"; Pattern pattern = Pattern.compile(regex); String str = "today is 2017-06-02, yesterday is 2017-06-01"; Matcher matcher = pattern.matcher(str);while(matcher.find()){ System.out.println("find "+matcher.group()+" position: "+matcher.start()+"-"+matcher.end()); } }
代码寻找所有类似"2017-06-02"这种格式的日期,输出为:
find 2017-06-02 position: 9-19find 2017-06-01 position: 34-44
Matcher的内部记录有一个位置,起始为0,find()方法从这个位置查找匹配正则表达式的子字符串,找到后,返回true,并更新这个内部位置,匹配到的子字符串信息可以通过如下方法获取:
//匹配到的完整子字符串public String group()//子字符串在整个字符串中的起始位置public int start()//子字符串在整个字符串中的结束位置加1public int end()
group()其实调用的是group(0),表示获取匹配的第0个分组的内容。我们在上节介绍过捕获分组的概念,分组0是一个特殊分组,表示匹配的整个子字符串。除了分组0,Matcher还有如下方法,获取分组的更多信息:
//分组个数public int groupCount()//分组编号为group的内容public String group(int group)//分组命名为name的内容public String group(String name)//分组编号为group的起始位置public int start(int group)//分组编号为group的结束位置加1public int end(int group)
比如:
public static void findGroup() { String regex = "(\\d{4})-(\\d{2})-(\\d{2})"; Pattern pattern = Pattern.compile(regex); String str = "today is 2017-06-02, yesterday is 2017-06-01"; Matcher matcher = pattern.matcher(str);while (matcher.find()) { System.out.println("year:" + matcher.group(1)+ ",month:" + matcher.group(2)+ ",day:" + matcher.group(3)); } }
输出为:
year:2017,month:06,day:02year:2017,month:06,day:01
替换
replaceAll和replaceFirst
查找到子字符串后,一个常见的后续操作是替换。String有多个替换方法:
public String replace(char oldChar, char newChar)public String replace(CharSequence target, CharSequence replacement)public String replaceAll(String regex, String replacement)public String replaceFirst(String regex, String replacement)
第一个replace方法操作的是单个字符,第二个是CharSequence,它们都是将参数看做普通字符。而replaceAll和replaceFirst则将参数regex看做正则表达式,它们的区别是,replaceAll替换所有找到的子字符串,而replaceFirst则只替换第一个找到的,看个简单的例子,将字符串中的多个连续空白字符替换为一个:
String regex = "\\s+"; String str = "hello world good"; System.out.println(str.replaceAll(regex, " "));
输出为:
hello world good
在replaceAll和replaceFirst中,参数replacement也不是被看做普通的字符串,可以使用美元符号加数字的形式,比如$1,引用捕获分组,我们看个例子:
String regex = "(\\d{4})-(\\d{2})-(\\d{2})"; String str = "today is 2017-06-02."; System.out.println(str.replaceFirst(regex, "$1/$2/$3"));
输出为:
today is 2017/06/02.
这个例子将找到的日期字符串的格式进行了转换。所以,字符'$'在replacement中是元字符,如果需要替换为字符'$'本身,需要使用转义,看个例子:
String regex = "#"; String str = "#this is a test"; System.out.println(str.replaceAll(regex, "\\$"));
如果替换字符串是用户提供的,为避免元字符的的干扰,可以使用Matcher的如下静态方法将其视为普通字符串:
public static String quoteReplacement(String s)
String的replaceAll和replaceFirst调用的其实是Pattern和Matcher中的方法,比如,replaceAll的代码为:
public String replaceAll(String regex, String replacement) {return Pattern.compile(regex).matcher(this).replaceAll(replacement); }
边查找边替换
replaceAll和replaceFirst都定义在Matcher中,除了一次性的替换操作外,Matcher还定义了边查找、边替换的方法:
public Matcher appendReplacement(StringBuffer sb, String replacement)public StringBuffer appendTail(StringBuffer sb)
这两个方法用于和find()一起使用,我们先看个例子:
public static void replaceCat() { Pattern p = Pattern.compile("cat"); Matcher m = p.matcher("one cat, two cat, three cat"); StringBuffer sb = new StringBuffer();int foundNum = 0;while (m.find()) { m.appendReplacement(sb, "dog"); foundNum++;if (foundNum == 2) {break; } } m.appendTail(sb); System.out.println(sb.toString()); }
在这个例子中,我们将前两个"cat"替换为了"dog",其他"cat"不变,输出为:
one dog, two dog, three cat
StringBuffer类型的变量sb存放最终的替换结果,Matcher内部除了有一个查找位置,还有一个append位置,初始为0,当找到一个匹配的子字符串后,appendReplacement()做了三件事情:
将append位置到当前匹配之前的子字符串append到sb中,在第一次操作中,为"one ",第二次为", two ";
将替换字符串append到sb中;
更新append位置为当前匹配之后的位置。
appendTail将append位置之后所有的字符append到sb中。
模板引擎
利用Matcher的这几个方法,我们可以实现一个简单的模板引擎,模板是一个字符串,中间有一些变量,以{name}表示,如下例所示:
String template = "Hi {name}, your code is {code}.";
这里,模板字符串中有两个变量,一个是name,另一个是code。变量的实际值通过Map提供,变量名称对应Map中的键,模板引擎的任务就是接受模板和Map作为参数,返回替换变量后的字符串,示例实现为:
private static Pattern templatePattern = Pattern.compile("\\{(\\w+)\\}");public static String templateEngine(String template, Map<String, Object> params) { StringBuffer sb = new StringBuffer(); Matcher matcher = templatePattern.matcher(template);while (matcher.find()) { String key = matcher.group(1); Object value = params.get(key); matcher.appendReplacement(sb, value != null ?Matcher.quoteReplacement(value.toString()) : ""); } matcher.appendTail(sb);return sb.toString(); }
代码寻找所有的模板变量,正则表达式为:
\{(\w+)\}
'{'是元字符,所以要转义,\w+表示变量名,为便于引用,加了括号,可以通过分组1引用变量名。
使用该模板引擎的示例代码为:
public static void templateDemo() { String template = "Hi {name}, your code is {code}."; Mapparams = new HashMap (); params.put("name", "老马"); params.put("code", 6789); System.out.println(templateEngine(template, params)); }
输出为:
Hi 老马, your code is 6789.
小结
本节介绍了正则表达式相关的主要Java API,讨论了如何在Java中表示正则表达式,如何利用它实现文本的切分、验证、查找和替换,对于替换,我们演示了一个简单的模板引擎。
下一节,我们继续探讨正则表达式,讨论和分析一些常见的正则表达式。
위 내용은 컴퓨터 프로그램의 사고논리(89) - 정규표현식(중)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제









iPhone의 기본 지도는 Apple의 독점 위치 정보 제공업체인 지도입니다. 지도가 점점 좋아지고 있지만 미국 이외의 지역에서는 잘 작동하지 않습니다. Google 지도와 비교하면 아무것도 제공할 수 없습니다. 이 기사에서는 Google 지도를 사용하여 iPhone의 기본 지도로 만드는 실행 가능한 단계에 대해 설명합니다. iPhone에서 Google 지도를 기본 지도로 설정하는 방법 Google 지도를 휴대전화의 기본 지도 앱으로 설정하는 것은 생각보다 쉽습니다. 아래 단계를 따르십시오. – 전제 조건 단계 – 휴대폰에 Gmail이 설치되어 있어야 합니다. 1단계 – AppStore를 엽니다. 2단계 – “Gmail”을 검색하세요. 3단계 - Gmail 앱 옆을 클릭하세요.

2024CSRankings 전국 컴퓨터 공학 전공 순위가 방금 발표되었습니다! 올해 미국 최고의 CS 대학 순위에서는 카네기멜론대학교(CMU)가 CS 분야 전국 최고에 올랐고, 일리노이대학교 어바나-샴페인 캠퍼스(UIUC)가 1위를 차지했다. 6년 연속 2위를 기록했다. 조지아공대(Georgia Tech)가 3위를 차지했습니다. 이어 스탠퍼드대, 캘리포니아대 샌디에이고대, 미시간대, 워싱턴대가 공동 4위를 차지했다. MIT의 순위가 하락해 상위 5위권 밖으로 떨어졌다는 점은 주목할 만하다. CSRankings는 매사추세츠 대학교 Amherst 컴퓨터 및 정보 과학부의 Emery Berger 교수가 시작한 컴퓨터 과학 분야의 글로벌 대학 순위 프로젝트입니다. 순위는 객관적인 기준에 따라 결정됩니다.

Windows 원격 데스크톱 서비스를 사용하면 사용자가 원격으로 컴퓨터에 액세스할 수 있으므로 원격으로 작업해야 하는 사람들에게 매우 편리합니다. 그러나 사용자가 원격 컴퓨터에 연결할 수 없거나 원격 데스크톱이 컴퓨터의 ID를 인증할 수 없는 경우 문제가 발생할 수 있습니다. 이는 네트워크 연결 문제 또는 인증서 확인 실패로 인해 발생할 수 있습니다. 이 경우 사용자는 네트워크 연결을 확인하고 원격 컴퓨터가 온라인인지 확인한 후 다시 연결을 시도해야 할 수 있습니다. 또한 원격 컴퓨터의 인증 옵션이 올바르게 구성되었는지 확인하는 것이 문제 해결의 핵심입니다. Windows 원격 데스크톱 서비스와 관련된 이러한 문제는 일반적으로 설정을 주의 깊게 확인하고 조정하여 해결할 수 있습니다. 원격 데스크톱은 시간 또는 날짜 차이로 인해 원격 컴퓨터의 ID를 확인할 수 없습니다. 계산을 확실히 해주세요

컴퓨터를 사용할 때 운영체제가 오작동하는 경우가 가끔 있습니다. 오늘 제가 겪은 문제는 gpedit.msc에 액세스할 때 올바른 권한이 부족할 수 있기 때문에 그룹 정책 개체를 열 수 없다는 메시지가 시스템에 표시된다는 것입니다. 이 컴퓨터의 그룹 정책 개체를 열 수 없습니다. 해결 방법: 1. gpedit.msc에 액세스할 때 시스템은 권한 부족으로 인해 이 컴퓨터의 그룹 정책 개체를 열 수 없다는 메시지를 표시합니다. 세부 정보: 시스템이 지정된 경로를 찾을 수 없습니다. 2. 사용자가 닫기 버튼을 클릭하면 다음과 같은 오류 창이 나타납니다. 3. 로그 기록을 즉시 확인하고 기록된 정보를 결합하여 C:\Windows\System32\GroupPolicy\Machine\registry.pol 파일에 문제가 있는지 확인합니다.

휴대폰에 시계 앱이 없나요? 날짜와 시간은 iPhone의 상태 표시줄에 계속 표시됩니다. 그러나 시계 앱이 없으면 세계 시계, 스톱워치, 알람 시계 및 기타 여러 기능을 사용할 수 없습니다. 따라서 누락된 시계 앱을 수정하는 것이 해야 할 일 목록의 맨 위에 있어야 합니다. 이러한 솔루션은 이 문제를 해결하는 데 도움이 될 수 있습니다. 수정 1 - 시계 앱 배치 실수로 홈 화면에서 시계 앱을 제거한 경우 시계 앱을 다시 제자리에 배치할 수 있습니다. 1단계 – iPhone을 잠금 해제하고 앱 라이브러리 페이지에 도달할 때까지 왼쪽으로 스와이프합니다. 2단계 – 다음으로 검색창에 “시계”를 검색하세요. 3단계 – 검색 결과 아래에 “시계”가 표시되면 길게 누르고

C++는 카운트다운 프로그램을 작성하는 데 매우 편리하고 실용적인 프로그래밍 언어로 널리 사용됩니다. 카운트다운 프로그램은 매우 정확한 시간 계산 및 카운트다운 기능을 제공할 수 있는 일반적인 애플리케이션입니다. 이 기사에서는 C++를 사용하여 간단한 카운트다운 프로그램을 작성하는 방법을 소개합니다. 카운트다운 프로그램 구현의 핵심은 타이머를 사용하여 시간의 경과를 계산하는 것입니다. C++에서는 time.h 헤더 파일의 함수를 사용하여 타이머 함수를 구현할 수 있습니다. 다음은 간단한 카운트다운 프로그램의 코드입니다.

매일 같은 시간에 같은 웹사이트를 자주 방문하시나요? 이로 인해 여러 브라우저 탭을 열어두고 일상적인 작업을 수행하는 동안 브라우저가 복잡해지는 데 많은 시간을 소비하게 될 수 있습니다. 그렇다면 브라우저를 수동으로 실행할 필요 없이 열어보는 것은 어떨까요? 매우 간단하며 아래와 같이 타사 앱을 다운로드할 필요가 없습니다. 웹사이트를 열려면 작업 스케줄러를 어떻게 설정하나요? 키를 누르고 검색 상자에 작업 스케줄러를 입력한 다음 열기를 클릭합니다. Windows 오른쪽 사이드바에서 기본 작업 생성 옵션을 클릭합니다. 이름 필드에 열려는 웹사이트의 이름을 입력하고 다음을 클릭하세요. 그런 다음 트리거에서 시간 빈도를 클릭하고 다음을 클릭합니다. 이벤트를 반복할 기간을 선택하고 다음을 클릭하세요. 활성화 선택

iOS 17에서 Apple은 몇 가지 새로운 메시징 기능을 추가했을 뿐만 아니라 메시지 앱의 디자인을 조정하여 더욱 깔끔한 모습을 제공했습니다. 카메라 및 사진 옵션과 같은 모든 iMessage 앱과 도구는 이제 키보드 위와 텍스트 입력 필드 왼쪽에 있는 "+" 버튼을 탭하여 접근할 수 있습니다. "+" 버튼을 클릭하면 기본 옵션 순서가 포함된 메뉴 열이 나타납니다. 맨 위에서부터 카메라, 사진, 스티커, 현금(가능한 경우), 오디오, 위치가 있습니다. 맨 아래에는 "더 보기" 버튼이 있는데, 이 버튼을 누르면 설치된 다른 메시징 앱이 표시됩니다(위로 스와이프하여 숨겨진 목록을 표시할 수도 있습니다). iMessage 앱을 재구성하는 방법 다음과 같이 할 수 있습니다.
