thinking in java 学习笔记 13 字符串
第十三章 字符串
?
String我平时进行得最多的东西,可以证明,字符串操作是计算机程序设计中最常见的行为
?
尤其在web中,这个也是可以想象的,所以要学好
?
?
.不可变String
String对象不可变,每个看起来会修改String值的方法,实际上都是创建了一个全新的String 对象,以包含修改后的字符串的内容,而最初的String则丝毫未动
?
?
.重载“+”与StringBuilder
String对象可以加任意多的别名,String对象具有只读特性,任何引用都无法改变它的值,这就代来了一个问题,当使用+的时候,无疑是生成许多个String对象
?
?
注:当你使用String对象时,其实编译器已经帮你优化,也就是内部是使用StringBuilder的,但是它仍然会产生多个StringBuilder,使用效率来说,还是自己用sb好
?
Stringbuilder是java5引入的,提供了相当全面的方法,最常用的是append和toString,在j5以前,使用的是StringBuffer,后者是线程安全,所以开销会大些
?
.无意识的递归
若要打印,最负责的方法不是使用this.tostring,因为会无意识的递归,最好使用super.tostring
?
?
.String上的操作
这个应该是我们最常用的
具体看文档或者书
?
.格式化输出
system.out.format()与c语言中的printf类似
formatter
在java中,新的格式功能都是由java.util.formatter处理的,还可以进行类型转换
String.format用起来很方便
?
.正则表达式
这个我在许多语言中都要遇见,以前学c#的时候才发现他的强大,,或者说正则表达式能够解决各种字符串关于处理,匹配,选择,编辑,及验证的问题
?
使用正则表达式最简单的途径就是使用String.matches
还有一个就是String.spilt,其功能是将字符串从正则表达式匹配的地方切开
还有最后一个就是String.replaceFirst或者replaceall
?
?
这上面的是String内建的功能
?
.强大的正则表达式
java.util.regex.pattern,应该经常去阅读
关于正则表达式的,我觉得很难完全记住的,更重要的时学会查阅
总之可以说的时,一旦关于java字符串复杂操作,想到的应该是正则表达式
?
注意:正则表达式还能与io结合哦
这简直就是恐怖
?
.StringTokenizer本来是用来分割字符串的唯一方法,不过j5出而来正则表达式j4,还有Scanner,所以它基本废弃了
?
这一章需要注意的是Stringbuilder与正则表达式
?
?
?
注:正则表达式详解
?
?
正则表达式用这些改进后的来替换的话,上面的代码就成了一个非常有用的电话号码数字匹配器:Pattern pattern =
Pattern.compile("(//(//d{3}//)|//d{3})//s?//d{3}(-|)?//d{4}");
可以确定的是,你可以自己试着进一步改进上面的代码。
现在看看第二个例子,它是从Friedl的中改编过来的。其功能是用来检查文本文件中是否有重复的单词,这在印刷排版中会经常遇到,同样也是个语法检查器的问题。
匹配单词,像其他的一样,也可以通过好几种的正则表达式来完成。可能最直接的是/b/w+/b,其优点在于只需用少量的regex元字符。其中/w元字符用来匹配从字母a到u的任何字符。+元字符表示匹配匹配一次或多次字符,/b元字符是用来说明匹配单词的边界,它可以是空格或任何一种不同的标点符号(包括逗号,句号等)。
现在,我们怎样来检查一个给定的单词是否被重复了三次?为完成这个任务,需充分利用正则表达式中的所熟知的向后扫描。如前面提到的,圆括号在正则表达式中有几种不同的用法,一个就是能提供组合类型,组合类型用来保存所匹配的结果或部分匹配的结果(以便后面能用到),即使遇到有相同的模式。在同样的正则表达中,可能(也通常期望)不止有一个组合类型。在第n个组合类型中匹配结果可以通过向后扫描来获取到。向后扫描使得搜索重复的单词非常简单:/b(/w+)/s+/1/b。
圆括号形成了一个组合类型,在这个正则表示中它是第一组合类型(也是仅有的一个)。向后扫描/1,指的是任何被/w+所匹配的单词。我们的正则表达式因此能匹配这样的单词,它有一个或多个空格符,后面还跟有一个与此相同的单词。注意的是,尾部的定位类型(/b)必不可少,它可以防止发生错误。如果我们想匹配"Paris in the the spring",而不是匹配"Java's regex package is the theme of this article"。根据java现在的格式,则上面的正则表达式就是:Pattern pattern =Pattern.compile("//b(//w+)//s+//1//b");
最后进一步的修改是让我们的匹配器对大小写敏感。比如,下面的情况:"The the theme of this article is theJava's regex package.",这一点在regex中能非常简单地实现,即通过使用在Pattern类中预定义的静态标志CASE_INSENSITIVE :
Pattern pattern =Pattern.compile("//b(//w+)//s+//1//b",
Pattern.CASE_INSENSITIVE);
有关正则表达式的话题是非常丰富,而且复杂的,用Java来实现也非常广泛,则需要对regex包进行的彻底研究,我们在这里所讲的只是冰山一角。即使你对正则表达式比较陌生,使用regex包后会很快发现它强大功能和可伸缩性。如果你是个来自Perl或其他语言王国的老练的正则表达式的黑客,使用过regex包后,你将会安心地投入到java的世界,而放弃其他的工具,并把java的regex包看成是手边必备的利器。?
?
?
import java.util.regex.*;publicclass TestRegularExpression {publicstaticvoid main(String[] args) {if(args.length < 2) { System.out.println("Usage:/n" +"java TestRegularExpression " +"characterSequence regularExpression+"); System.exit(0); } System.out.println("Input: /"" + args[0] + "/"");for(int i = 1; i < args.length; i++) { System.out.println("Regular expression: /"" + args[i] + "/""); Pattern p = Pattern.compile(args[i]); Matcher m = p.matcher(args[0]);while(m.find()) { System.out.println("Match /"" + m.group() +"/" at positions " + m.start() + "-" + (m.end() - 1)); } } }} ///:~); i++; } monitor.expect(new String[] {"Evening","is","full","of","the","linnet","s","wings","Evening vening ening ning ing ng g is is s full " +"full ull ll l of of f the the he e linnet linnet " +"innet nnet net et t s s wings wings ings ngs gs s " }); }} ///:~ " + m1.end());while(m2.find()) System.out.println("m2.find() '" + m2.group() +"' start = "+ m2.start() + " end = " + m2.end());if(m1.lookingAt()) // No reset() necessary System.out.println("m1.lookingAt() start = " + m1.start() + " end = " + m1.end());if(m2.lookingAt()) System.out.println("m2.lookingAt() start = " + m2.start() + " end = " + m2.end());if(m1.matches()) // No reset() necessary System.out.println("m1.matches() start = " + m1.start() + " end = " + m1.end());if(m2.matches()) System.out.println("m2.matches() start = " + m2.start() + " end = " + m2.end()); } monitor.expect(new String[] {"input 0: Java has regular expressions in 1.4","m1.find() 'regular' start = 9 end = 16","m1.find() 'ressions' start = 20 end = 28","m2.find() 'Java has regular expressions in 1.4'" +" start = 0 end = 35","m2.lookingAt() start = 0 end = 35","m2.matches() start = 0 end = 35","input 1: regular expressions now " +"expressing in Java","m1.find() 'regular' start = 0 end = 7","m1.find() 'ressions' start = 11 end = 19","m1.find() 'ressing' start = 27 end = 34","m2.find() 'Java' start = 38 end = 42","m1.lookingAt() start = 0 end = 7","input 2: Java represses oracular expressions","m1.find() 'represses' start = 5 end = 14","m1.find() 'ressions' start = 27 end = 35","m2.find() 'Java represses oracular expressions' " +"start = 0 end = 35","m2.lookingAt() start = 0 end = 35","m2.matches() start = 0 end = 35" }); }} ///:~ 注意,只要字符串里有这个模式,))); monitor.expect(new String[] {"[This, unusual use, of exclamation, points]","[This, unusual use, of exclamation!!points]","[Aha!, String, has, a, split(), built, in!]" }); }} ///:~ 第二个, " ");// Replace one or more spaces at the beginning of each// line with no spaces. Must enable MULTILINE mode: s = s.replaceAll("(?m)^ +", ""); System.out.println(s); s = s.replaceFirst("[aeiou]", "(VOWEL1)"); StringBuffer sbuf = new StringBuffer(); Pattern p = Pattern.compile("[aeiou]"); Matcher m = p.matcher(s);// Process the find information as you// perform the replacements:while(m.find()) m.appendReplacement(sbuf, m.group().toUpperCase());// Put in the remainder of the text: m.appendTail(sbuf); System.out.println(sbuf); monitor.expect(new String[]{"Here's a block of text to use as input to","the regular expression matcher. Note that we'll","first extract the block of text by looking for","the special delimiters, then process the","extracted block. ","H(VOWEL1)rE's A blOck Of tExt tO UsE As InpUt tO","thE rEgUlAr ExprEssIOn mAtchEr. NOtE thAt wE'll","fIrst ExtrAct thE blOck Of tExt by lOOkIng fOr","thE spEcIAl dElImItErs, thEn prOcEss thE","ExtrActEd blOck. " }); }} ///:~ 用
有一点需要清楚的是,如果你把构造器Perl5Substitution(java.lang.String substitution,int numInterpolations)
中的numInterpolations参数设为INTERPOLATE_ALL,那么当每次找到一个匹配字串时,替换变量($1,$2等)所指向的内容都根据目前匹配字串来更新,但是如果numInterpolations参数设为一个正整数N时,那么在替换时就只会在前N次匹配发生时替换变量会跟随匹配对象来调整所代表的内容,但N次之后就以一致以第N次替换变量所代表内容来做为以后替换结果。
举个例子会更好理解:
假如沿用以上例子中的正则表达式模式以及替换内容来进行替换工作,设目标字符串为"Tank b123: 85 Tank b256: 32 Tank b78: 22",并且设numInterpolations参数为INTERPOLATE_ALL,而Util.substitute()方法中的numSub变量设为SUBSTITUTE_ALL(请参考上文Util.substitute()方法内容),那么你获得的替换结果将会是:Tank a123- 85 Tank a256- 32 Tank a78- 22
但是如果你把numInterpolations设为2,并且numSubs依然设为SUBSTITUTE_ALL,那么这时你获得的结果则会是:Tank a123- 85 Tank a256- 32 Tank a256- 22
你要注意到最后一个替换所用变量$1所代表的内容与第二个$1一样为"256",而不是预期的"78",因为在替换进行中,替换变量$1只根据匹配内容进行了两次更新,最后一次就使第二次匹配时所更新的结果,那么我们可以由此知道,如果numInterpolations设为1,那么结果将是:Tank a123- 85 Tank a123- 32 Tank a123- 22
并且在表达式里每个符号中间不能有空格,否则就会同样出现编译错误。
实例化PatternCompiler对象,创建Pattern对象PatternCompiler compiler=new Perl5Compiler();
Pattern pattern=compiler.compile(restring);
创建PatternMatcher对象,调用PatternMatcher接口的contain()方法检查匹配情况: PatternMatcher matcher=new Perl5Matcher(); if (matcher.contains(content,pattern)) { //处理代码片段 }这里matcher.contains(content,pattern)中的参数 content是从数据库里取来的字符串变量。该方法只会查到第一个匹配的对象字符串,但是由于音标项均在CONETNET内容字符串中的起始位置,所以用这个方法就已经可以保证把每条记录里的音标项找出来了,但更为直接与合理的办法是使用boolean matchesPrefix(PatternMatcherInput input, Pattern pattern)方法,该方法验证目标字符串是否以正则表达式所匹配的字串为起始。
具体实现的完整的程序代码如下:
package RegularExpressions;//import……import org.apache.oro.text.regex.*;//使用Jakarta-ORO正则表达式库前需要把它加到CLASSPATH里面,如果用IDE是//JBUILDER,那么也可以在JBUILDER里直接自建新库。public class yisuo{ public static void main(String[] args){ try{ //使用JDBC DRIVER进行DBMS连接,这里我使用的是一个第三方JDBC //DRIVER,Microsoft本身也有一个面向SQLSERVER7/2000的免费JDBC //DRIVER,但其性能真的是奇差,不用也罢。 Class.forName("com.jnetdirect.jsql.JSQLDriver"); Connection con=DriverManager.getConnection ("jdbc:JSQLConnect://kevin:1433","kevin chen","re"); Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);//为使用Jakarta-ORO库而创建相应的对象String rsstring=" //[[^]]+//]"; PatternCompiler orocom=new Perl5Compiler(); Pattern pattern=orocom.compile(rsstring); PatternMatcher matcher=new Perl5Matcher(); ResultSet uprs = stmt.executeQuery("SELECT * FROM aesop"); while (uprs.next()) {Stirng word=uprs.getString("word"); Stirng content=uprs.getString("content"); if(matcher.contains(content,pattern)){ //或if(matcher.matchesPrefix(content,pattern)){ MatchResult result=matcher.getMatch(); Stirng pure=result.toString(); System.out.println(word+"的音标为:"+pure); } } } catch(Exception e) { System.out.println(e); } }}输出结果为:kevin的音标为['kevin]
在这个处理中我是用toString()方法来取得结果,但是如果正则表达式里是用了分组符号(圆括号),那么就可以用group(int gid)的方法来取得相应各组匹配的结果,如正则表达式改为" (/[[^]]+/])",那么就可以用以下方法来取得结果:pure=result.group(0);
用程序验证,输出结果同样为:kevin的音标为['kevin]
而如果正则表达式为(/[[^]]+/])(/[[^]]+/]),则会查找到两个连续的方括号所包含的内容,也就找到[音标] [词性]两项,但是两项的结果分别在两个组里面,分别由下面语句获得结果:
result.group(0)->返回[音标] [词性]两项内容,也就是与整个正则表达式相匹配的结果字符串,在这里也就为['kevin] [名词]
result.group(1) ->返回[音标]项内容,结果应是['kevin]
result.group(2) ->返回[词性]项内容,结果应是[名词]
继续用程序验证,发现输出并不正确,主要是当内容有中文时就不能成功匹配,考虑到可能是Jakarta-ORO正则表达式库版本不支持中文的问题,回看一下原来我一直用的还是2.0.1的老版本,马上到Jakarta.org上下载最新的2.0.4版本装上再用程序验证,得出的结果就和预期一样正确。
★查找多个匹配:
经过第一步的尝试使用Jakarta-ORO后,我们已经知道了如何正确使用该API包来查找目标字符串里一个匹配的子串,下面我们接着来看一看当目标字符串里包含不止一个匹配的子串时我们如何把它们一个接一个找出来进行相应的处理。
首先我们先试个简单的应用,假设我们想把CONTNET字段内容里所有用方括号包起来的字串都找出来,很清楚地,CONTNET字段的内容里面就只有两项匹配的内容:[音标]和 [词性],刚才我们其实已经把它们分别找出来了,但是我们所用的方法是分组方法,把"[音标] [词性]"作为一整个正则表达式匹配的内容先找到,再根据分组把[音标]和 [词性]分别挑出来。但是现在我们需要做的是把[音标]和[词性]分别做为与同一个正则表达式匹配的内容,先找到一个接着再找下一个,也就是刚才我们的表达式为(/[[^]]+/])(/[[^]]+/]),而现在应为" /[[^]]+/] "。
我们已经知道在匹配操作的三个方法里只要用PatternMatcherInput对象作为参数替代String对象就可以从字符串中最后一次匹配的位置开始继续进行匹配,实现的程序片段如下:
?
PatternMatcherInput input=new PatternMatcherInput(content); while (matcher.contains(input,pattern)) { result=matcher.getMatch(); System.out.println(result.group(0)) }输出结果为:['kevin]?
接着我们来做复杂一点的处理,就是我们要先把下面内容:
['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin is living in ZhuHai now. /凯文现住在珠海/名词: 凯文)}中的整个例句部分(也就是由大括号所包含的部分)找出来,再分别把例句一和例句二找出,而各例句中的各项内容(英文句、中文句、词性、解释)也要分项列出。
第一步当然是要定出相应的正则表达式,需要有两个,一是和整个例句部分(也就是由大括号包起来的部分)匹配的正则表达式:"/{.+/}",
另一个则要和每个例句部分匹配(也就是小括号中的内容),:/(([^)]+/)
?
而且由于要把例句的各项分离出来,所以要再把里面的各部分用分组的方法匹配出来:" ([^(]+)/(.+)/(.+):([^)]+) "。
为了简便起见,我们不再和从数据库里读出,而是构造一个包含同样内容的字符串变量,程序片段如下:
?
try{ String content="['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词:凯文) (Kevin is living in ZhuHai now./凯文现住在珠海/名词: 凯文)}"; String ps1="//{.+//}"; String ps2="//([^)]+//)"; String ps3="([^(]+)/(.+)/(.+):([^)]+)"; String sentence; PatternCompiler orocom=new Perl5Compiler(); Pattern pattern1=orocom.compile(ps1); Pattern pattern2=orocom.compile(ps2); Pattern pattern3=orocom.compile(ps3); PatternMatcher matcher=new Perl5Matcher();//先找出整个例句部分 if (matcher.contains(content,pattern1)) { MatchResult result=matcher.getMatch(); String example=result.toString(); PatternMatcherInput input=new PatternMatcherInput(example); //分别找出例句一和例句二 while (matcher.contains(input,pattern2)){ result=matcher.getMatch(); sentence=result.toString(); //把每个例句里的各项用分组的办法分隔出来 if (matcher.contains(sentence,pattern3)){ result=matcher.getMatch(); System.out.println("英文句: "+result.group(1)); System.out.println("句子中文翻译: "+result.group(2)); System.out.println("词性: "+result.group(3)); System.out.println("意思: "+result.group(4)); } } } } catch(Exception e) { System.out.println(e); }?
输出结果为:
英文句: Kevin loves comic.
句子中文翻译: 凯文爱漫画
词性: 名词
意思: 凯文
英文句: Kevin is living in ZhuHai now.
句子中文翻译: 凯文现住在珠海
词性: 名词
意思: 凯文
★查找替换:
以上的两个应用都是单纯在查找字符串匹配方面的,我们再来看一下查找后如何对目标字符串进行替换。
例如我现在想把第二个例句进行改动,换为:Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。
也就是把
['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin is living in ZhuHai now. /凯文现住在珠海/名词: 凯文)}
改为:
['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}
之前,我们已经了解Util.substitute()方法与Substiution接口,以及Substiution的两个实现类StringSubstitution和Perl5Substitution,我们就来看看怎么用Util.substitute()方法配合Perl5Substitution来完成我们上面提出的替换要求,确定正则表达式:
我们要先找到其中的整个例句部分,也就是由大括号包起来的字串,并且把两个例句分别分组,所以正则表达式为:"/{(/([^)]+/))(/([^)]+/))/}",如果用替换变量来代替分组,那么上面的表达式可以看为"/{$1$2/}",这样就可以更容易看出替换变量与分组间的关系。
根据上面的正则表达式Perl5Substitution类可以这样构造:
Perl5Substitution("{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}")
再根据这个Perl5Substitution对象来使用Util.substitute()方法便可以完成替换了,实现的代码片段如下:
?
try{ String content="['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)(Kevin lives in ZhuHai now./凯文现住在珠海/名词: 凯文)}"; String ps1="//{(//([^)]+//))(//([^)]+//))//}"; String sentence; String pure; PatternCompiler orocom=new Perl5Compiler(); Pattern pattern1=orocom.compile(ps1); PatternMatcher matcher=new Perl5Matcher(); String result=Util.substitute(matcher, pattern1,new Perl5Substitution( "{$1( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}",1), content,Util.SUBSTITUTE_ALL); System.out.println(result); } catch(Exception e) { System.out.println(e); }?
输出结果是正确的,为:
['kevin] [名词](人名凯文){(Kevin loves comic./凯文爱漫画/名词: 凯文)( Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。)}
至于有关使用numInterpolations参数的构造器用法,读者只要根据上面的介绍自己动手试一下就会清楚了,在此就不再例述。
总结:
本文首先介绍了Jakarta-ORO正则表达式库的对象与方法,并且接着举例让读者对实际应用有进一步的了解,虽然例子都比较简单,但希望读者们在看了该文后对Jakarta-ORO正则表达式库有一定的认知,在实际工作中有所帮助与启发。
其实在Jakarta org里除了Jakarta-ORO外还有一个百分百的纯JAVA正则表达式库,就是由Jonathan Locke赠与Jakarta ORG的Regexp,在该包里面包含了完整的文档以及一个用于调试的Applet例子,对其有兴趣的读者可以到此下载。
参考资料:
?
?