Java學(xué)習(xí):字符串和正則表達(dá)式
簡介
可以證明,字符串操作是計算機(jī)程序設(shè)計中最常見的行為
不可變String
String對象是不可變的,通過JDK文檔可以發(fā)現(xiàn),每一個看似會修改String值的操作,實際上都是創(chuàng)建了一個新的String對象,以包含修改后的字符串內(nèi)容,而最初的String對象則絲毫未動。
重載"+" 與StringBuilder
String對象是不可變的額,你可以給String對象加任意多的別名。因為String具有只讀特性,所以指向它的任何引用都不可能改變它的值,因此,也就不會對其他的引用有什么影響。
不可變性會帶來一定的效率問題。為String對象重載的”+“操作符就是一個例子,用于String的”+“與”+=“被重載為用來連接兩個字符串
看下面代碼

上面代碼一開始聲明了一個Hello字符串,后面與World字符串進(jìn)行了連接賦值給了str2.所以最后會打印Hello World的字段
現(xiàn)在看看代碼到底是如何工作的
public static void main(java.lang.String[]);
????code:
????????0: ldc? ? ? ? ? ? ? ? ? #2 ? ? ? ? ? ? ?// String Hello
????????2: astore_1 ? ? ??
????????3: new? ? ? ? ? ? ? ? #3 ? ? ? ? ? ?// class java/lang/StringBuilder ? ? ??
????????6: dup ? ? ??
????????7: invokespecial #4? ? ???// Method java/lang/StringBuilder."<init>":()V ? ? ?
????????10: aload_1 ? ? ?
????????11: invokevirtual #5? ? ? ?// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; ? ? ?
????????14: ldc? ? ? ? ? ? ? ? #6? ? ? ? ? ??// String World ? ? ?
????????16: invokevirtual #5? ? ? ?// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; ? ?
????????19: invokevirtual #7? ? ?? // Method java/lang/StringBuilder.toString:()Ljava/lang/String;? ? ? ?
????????22: astore_2 ? ? ?
????????23: getstatic? ? ? ?#8? ? ? ???// Field java/lang/System.out:Ljava/io/PrintStream; ? ? ?
????????26: aload_2 ? ? ?
????????27: invokevirtual?#9? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V ? ? ?
????????30: return
上面是對源代碼進(jìn)行反編譯后的匯編語句。我們暫時不必讀懂,可以參考后面的注釋。我們可以發(fā)現(xiàn)所謂的String字符串拼接實際上就是創(chuàng)建一個空的StringBuilder對象,并將對所有通過”+“進(jìn)行拼接的字符串進(jìn)行append操作,最后通過toString方法將拼接好的字符串賦值給str2引用。
注意:雖然編譯器會將字符串的拼接優(yōu)化為StringBuilder的操作,但是這不意味著我們可以濫用”+“,這會我們帶來不少的資源浪費(fèi)。現(xiàn)在看下面的代碼:

上面代碼中,前者通過”+=“來循環(huán)拼接字符串,這意味著每一次循環(huán),都會創(chuàng)建一個新的SringBuilder對象,這會極大浪費(fèi)系統(tǒng)的性能,雖然垃圾回收機(jī)制可以回收存儲資源,但是創(chuàng)建對象是非常浪費(fèi)資源的是比較慢的,所以相比之下,后者的使用效率會更高。
String上的操作

格式化輸出
Formatter
在java中所有新的格式化功能都由java.util.Formatter類處理??梢詫⑦@個類當(dāng)作一個翻譯器,它將你的格式化字符串與數(shù)據(jù)翻譯成需要的結(jié)果。當(dāng)你創(chuàng)建一個Formatter對象的時候,需要向其構(gòu)造器傳遞一些信息,告訴它最終的結(jié)果將向哪里輸出:

格式化說明符
在插入數(shù)據(jù)時,如果想要控制空格與之對其,需要更加精細(xì)復(fù)雜的格式修飾符,以下是抽象的語法:
%[argument_index$][flags][width][.precision]conversion
%[][對齊方向][最小寬度][精度(小數(shù)點精確到幾位或者字符串最多有幾位)]類型轉(zhuǎn)換符
%?-10.2s
類型轉(zhuǎn)換符

String.format()
String中也提供了格式化的功能,這個方法是靜態(tài)的,與Formatter的format具有相同的作用

正則表達(dá)式
正則表達(dá)式是一種靈活而強(qiáng)大的文本處理工具。通過正則表達(dá)式我們就能以編程的方式構(gòu)造復(fù)雜的文本模式,并對輸入的字符串進(jìn)行搜索。
一般來說,正則表達(dá)式就是以某種方式來描述字符串,因此,你可以說”如果一個字符串包含這樣那樣的東西,那么它就是我要找的“
創(chuàng)建正則表達(dá)式


量詞

量詞中的貪婪型、勉強(qiáng)型和占有型
量詞描述了一個模式吸收輸入文本的方式
貪婪型
貪婪型就是我們前面提到的量詞表中的形式,貪婪型會根據(jù)表達(dá)式去盡最大范圍去匹配

通過前面正則符號的學(xué)習(xí)我們可以知道”.“代表任意字符,而”+“則代表一個或多個字符,組合起來就表示匹配一個或多個任意字符。我們看到例子中的正則表達(dá)式”<.+>“表示會匹配一對尖括號<>,尖括號中間會有一個或多個任意字符。所以,通常來說我們會認(rèn)為例子中的符號會被匹配到量詞,因為有兩個尖括號是符合條件的。但是事實上我們看到輸出結(jié)果只匹配到了一個。所以我們現(xiàn)在可以理解貪婪型中的盡最大范圍匹配是什么含義了
勉強(qiáng)型
勉強(qiáng)型就是在貪婪型的基礎(chǔ)上增加了”?“符號,通過前面的學(xué)習(xí)我們知道”?”代表0個或1個符號,但是如果前面已經(jīng)出現(xiàn)了量詞,那它將不再作為原來的含義使用。
勉強(qiáng)型的含義剛好以貪婪型相反,它會近最小的匹配返回去匹配

在上面例子中我們發(fā)現(xiàn)例子中的test字符串被匹配到了兩次,這不就是我們希望的結(jié)果嗎,事實上,勉強(qiáng)型就是會在遇到匹配的結(jié)果就不再擴(kuò)大范圍,直接完成對象匹配,然后完成對應(yīng)操作后再往后繼續(xù)重新開始匹配
獨占型
獨占型比較相比前兩者難理解,其實獨占型與貪婪型基本上是一樣的,都是盡最大范圍去匹配,只是獨占型沒有回退功能,所以如果一旦由于把范圍擴(kuò)的太大,會造成原先匹配到的對象都丟失的情況。
這樣說比較抽象。我們對正則匹配的方式進(jìn)行深度的說明
下面圖中綠色代表匹配,黃色代表不匹配

通過上面的圖我們可以發(fā)現(xiàn)到第四步,我們的“.++”把后面所有的字符都給匹配掉了,所以在第五步時,我們的“>”沒有可以匹配的字符了,這時候就會匹配失敗,如果時我們的獨占型,就會到此結(jié)束。但是如果是貪婪型,將會把之前匹配完成的結(jié)果進(jìn)行回退,一直退到符合匹配條件的或者全部都不匹配為止。
CharSequeue
CharSequeue是一個從CharBuffer、String、StringBuffer,StringBuilder類之中抽象出來的字符序列的一般化定義。這些類都實現(xiàn)了該接口。大多數(shù)正則表達(dá)式操作都會接收這個接口作為參數(shù)。由于這個接口在1.8之后有大量的方法體,所以有需要可以自己去看看源碼
Pattern和Matcher
一般來說,比起功能有限的String類,我們更愿意構(gòu)造功能強(qiáng)大的正則表達(dá)式對象。只需要導(dǎo)入java.util.regex包,然后用static Pattern.compile()方法來編譯正則表達(dá)式即可。它會根據(jù)你的String類型的正則擺動式生成一個Pattern對象。接下來,把你想要檢索的字符串傳入Pattern的matcher()方法。該方法會生成一個Macther對象,它會有許多的功能可用。
我們看下面的代碼

組
組是用括號劃分的正則表達(dá)式,可以根據(jù)組的編號來引用某個組。組號為0表示整個表達(dá)式,組號為1表示被第一對括號括起的組,依此推類
看下面表達(dá)式

組0為ABCD,組1為BC,組2為C
Matcher對象提供一系列方法用來獲取與組相關(guān)的信息

下面介紹一下Matcher對象常用的一些方法
find():查找多個匹配,每次調(diào)用查找一次,每次只找一個匹配項,但下次查找會從前一次匹配的結(jié)束位置的后一位開始查找,假設(shè)我們要查找ava,目標(biāo)字段是avava那只能find到一次
lookingAt():只在正則表達(dá)式與輸入開始處就開始匹配時才會成功。舉個例子:表達(dá)式ava,遇到j(luò)ava會匹配失敗,但是遇上ava或者avaj都能夠匹配成功
matches():只在正則表達(dá)式與輸入開始處就開始匹配并且表達(dá)式和輸入完全匹配時才會成功。舉個例子:表達(dá)式ava,遇到j(luò)ava和avaj會匹配失敗,只有遇上ava才能夠匹配成功
注意,如果我們希望像上面代碼示例中一樣獲取組信息,需要先調(diào)用上面三個匹配方法,否則就會異常。
Pattern標(biāo)記
Pattern類的compile方法還有另一個版本,它接受一個標(biāo)記參數(shù)以調(diào)整匹配的行為。

其中的flag來自以下Pattern類中的常量:

掃描輸入
我們通常通過Scanner對象完成對各種輸入流的類型讀寫,否則什么都要自己通過分解String然后進(jìn)行各種類型的parse會是一件很大的工程。

這里只做一點簡單的額介紹,有需要可以自己去看一下源碼有哪些方法可用
Scanner定界符
默認(rèn)情況下,Scanner根據(jù)空白字符對輸入進(jìn)行分詞,但是我們也可以使用正則表達(dá)式指定我們需要的定界符。

通過上面例子我們能夠看出原字符串中是以“, ”作為分割符的,所以我們通過useDelimiter(",\s*")指定了新的分界符號。
正則表達(dá)式掃描
Scanner除了掃描基本類型以外,還能夠使用自定義的正則表達(dá)式進(jìn)行掃描。這在掃描復(fù)雜數(shù)據(jù)時非常有用,下面例子將掃描一個防火墻日志文件中記錄的威脅數(shù)據(jù)。

上面代碼中,我們希望從數(shù)據(jù)中篩選出我們需要的威脅記錄,并從中提取出ip和時間。注意,hasNext(pattern)和scanner.next(pattern)僅僅針對下一個輸入分詞進(jìn)行匹配,也就說,根據(jù)我們的正則表達(dá)式,在第三行數(shù)據(jù)會匹配失敗,此時不會往下繼續(xù)匹配,需要通過next()跳到下一個分詞
2021新的一年,不管你是零基礎(chǔ)的小白,還是想要提升技術(shù)的大牛,在這里有一套【Java實戰(zhàn)進(jìn)階的學(xué)習(xí)資料】,為正準(zhǔn)備學(xué)習(xí)的Java的小伙伴,精心準(zhǔn)備的禮物!看一下或許對于你有很大的提升哦!

想要快速的得到這套視頻,小伙伴們只需要 ?點贊+關(guān)注 ?在下方評論:【666】