Scala之String


一、前言

  大數據領域的Spark、Kafka、Summingbird等都是由Scala語言編寫而成,相比Java而言,Scala更精煉。由於筆者從事大數據相關的工作,所以有必要好好學習Scala語言,之前也學習過,但是沒有記錄,所以就會忘記,感覺Scala確實比Java方便精煉很多,下面以Scala Cookbook英文版作為參考資料,從頭到尾梳理Scala相關知識點,也加深印象。PS:這是在研究Zookeeper源碼的間隙中交叉學習,不至於總是看源碼太枯燥。

二、String

  在Scala的REPL環境中測試String的類型,可以發現其就是Java中的String。

      

  所以可以使用Java中String的所有方法,如獲取字符串的長度、連接多個字符串。在Scala中,由於String可以被隱式轉化成StringOps類型,可將字符串看成一個字符序列,並且可以使用foreach方法遍歷字符串的每個字符。

      

  也可將字符串當成字符序列,使用for循環遍歷每個字符

      

  同理,也可將字符串當前字節序列,使用for循環遍歷

      

  由於字符串可以當成字符順序集合(字符序列),而在集合上可進行多種操作,如filter

  

  可以看到對字符串進行了過濾,去掉了l字符。filter方法是StringOps的方法,由於String會被隱式的轉化為StringOps,因此可以調用此方法。

  對於該隱式轉化,是在Perdef對象中定義。 

  @inline implicit def augmentString(x: String): StringOps = new StringOps(x)

  向封閉類中添加方法

  在Java中,String被定義成final的,即無法繼承String類或者添加任何方法,但在Scala中,我們可以通過隱式轉化添加方法,下面例子展示了Scala的String擁有String的特性和集合的特性。

      

  其中,drop方法和take方法都是Scala的序列(集合)的方法,而capitalize方法則是StringOps的方法,這種調用都是通過隱式轉化完成的。

  2.1 測試String的相等性

  1. 問題描述

  你需要比較兩個字符串以判斷它們是否相等,或者它們包含相同的字符序列。

  2. 解決方案

  定義了如下字符串s1、s2、s3

  

  使用"=="進行相等性判斷

  

  可以看到s1、s2、s3均相等,而對於一個好的"=="方法而言,即便是有參數為空,也不會拋出異常。

      

  若在比較過程中不區分大小寫,則可將字符串轉化成大寫或小寫進行比較。

  

  然而,在對空字符串調用toUpperCase方法會拋出異常。

  

  在Java中,想要比較不區分大小寫比較兩個字符串是否相等,則使用equalsIgnoreCase方法

  

  3. 討論

  在Scala中,我們使用==方法來判定對象的相等性,這與Java不同,Java使用equals方法判定兩個對象的相等性,== 是比較兩個對象是否是同一個對象(內存地址相同)。在Scala中,== 方法在AnyRef(所有引用類型的父類)中定義,其首先會檢查空值,然后再調用第一個對象的equals方法進行比較,因此,當比較兩個字符串是否相等時,我們不需要檢查空值。

  2.2 創建多行字符串

  1. 問題描述

  在Scala源碼中想要創建多行字符串。

  2. 解決方案

  在Scala中,你可以使用三個雙引號來創建多行字符串。     

    val s1 = """This is
        a multiline
        String
      """
    println(s1)

  3. 討論

  使用上述方法可以創建多行字符串,但是當打印時,其結果如下 

This is
        a multiline
        String

  第二行和第三行是以空格開頭,若需要使第二行、第三行的字符串都不以空格開頭,則可進行如下處理。    

復制代碼
    val s1 =
      """
        |This
        |is a multiline
        |String
      """.stripMargin
    
    println(s1)
復制代碼

  結果如下

This
is a multiline
String

  即通過|來處理,或者使用其他符號,如#,同時使用stripMargin方法。 

    val s1 = """This is
        #a multiline
        #String
      """.stripMargin('#')
    println(s1)

  結果如下

This is
a multiline
String

  上述例子中,在第一行is和第二行multiline后面均隱藏了\n字符進行換行,當需要將多行字符串合並為一行時,可以在使用stripMargin方法之后使用replaceAll方法來將所有的\n替換為" "。

  

  另外,在三引號字符串中可以包含特殊字符,而並不需要轉義符進行轉義。

     

  2.3 分割字符串

  1. 問題描述

  你需要使用分割字符來分割字符串,如從逗號分隔(CSV)或管道分隔的文件中獲取字符串。

  2. 解決方案

  可以使用String的split方法進行分割

  

  split方法會返回String數組。

  3. 討論

  split函數的參數可以是正則表達式,所以對於CSV文件,你可以使用逗號進行分割字符串。

  

  可以看到,通過","進行分割時,結果中還包含了一些空格,如" milk", " butter", " Coco Puffs",此時,需要使用trim函數來去掉空格。

  

  我們也可以使用正則表達式來分割字符串

  

  split方法是重載的,一部分從Java的String而來,一部分從Scala的StringLike而來,例如,你可以使用字符而非字符串作為參數來調用split,此時,你使用的是StringLike的方法

  

  此時,使用字符和字符串作為參數兩者的結果是相同的。

  2.4 將變量替換成字符串

  1. 問題描述

  如同Perl、PHP、Ruby一樣,你需要將變量替換成字符串。

  2. 解決方案

  為在Scala中使用字符串插值,字符串前面需要使用字母s,同時需要將變量包含在字符串中,而每個變量名前面是一個$字符。

  

  當在字符串前面使用字母s時,表示正在創建一個已處理的字符串字面量,即可以在字符串中直接使用變量。

  字符串字面量中使用表達式

  除了在字符串中使用變量外,同樣也可以在字符串中使用表達式,此時表達式需要放在中括號內。

  

  

  也可以使用中括號打印對象的屬性。

  

  s是一個方法

  放在字符串字面量前面的s實際上是一個方法,而使用s方法可以讓你享受如下便利

    · Scala提供了其他現成的插值函數

    · 你可自定義字符串插值函數

  f字符串插值(printf格式)

  在討論中提到,weight被打印成65,但是如果需要在weight后面增加多位小數點應該怎么做,可以使用f字符串插值方法,其可以格式化字符串中的說明符。 

  

  為使用f字符串插值,首先需要在字符串前面添加f,然后在變量后面使用printf格式化說明符。

  粗插值

  除了使用s和f插值方法外,Scala還包括了粗插值方法,其會保留字符串中的特殊字符。

  

  可以看到使用raw修飾字符串時,其會保留字符串中的特殊字符。

  下表列出了最常用的說明符

  

  2.5 一次處理字符串的一個字符

  1. 問題描述

  你需要遍歷字符串的每個字符,並且對每個字符做相應的操作。

  2. 解決方案

  可以使用map方法、foreach方法、for循環等方法來遍歷字符串。

  

  或者使用下划線的方式

  

  對於字符串的字符序列,你可以使用鏈式調用來得到想要的結果,在下面的示例中,filter方法用於原始的字符串來生成新的字符串(去掉所有的字符l),然后再調用map方法將新生成的字符串轉化為大寫。

  

  使用for循環和yield也可以達到map方法的效果

  

  map方法、for和yield方法可以將舊的集合轉化為新的集合,而foreach方法則是對集合的每個元素進行操作,不會產生新的結果。

  3. 討論

  由於Scala將字符串當成是字符序列,而Scala也是面向對象和函數編程語言,在Java中,你可以使用如下方法來遍歷字符串中的每個字符  

String s = "hello"
for (int i = 0; i < s.length(); i++) {
    char c = s.charAt(i);
    System.out.println(c);    
}

  理解map方法的工作機制

  在map方法中,你可以傳入一大段代碼塊

  

  上述功能是將字符串的字符由大寫變換成小寫,由於是調用的String的map方法,因此每次只會處理字符串的一個字符,map會將String當成一個字符順序集合,map方法有一個隱式循環,在此循環中,每次只會傳入一個字符。除了在map方法中直接傳入代碼塊外,也可以先定義好函數,然后再傳入map,這樣可以保證代碼的簡潔性。

  

  並且也可以在for循環和yield中使用該方法

  

  除了使用方法方式外,還可以使用函數方式來完成上述的操作

  

  2.6 在字符串中查找模式

  1. 問題描述

  你需要確定字符串是否包含正則表達式模式。

  2. 解決方案

  通過String的.r方法來創建一個Regex對象,之后當查找第一個匹配時,使用findFirstIn方法,而查找所有匹配時,使用findAllIn方法。

  

  對於findAllIn方法而言,可以將結果轉化成Array、List、Seq等

  

  3. 討論

  使用字符串的.r方法是創建Regex對象最簡單的方式,另外一種方式是導入Regex類,創建一個Regex實例,之后使用實例的方法

  

  處理findFirstIn返回的結果

  findFirstIn查找第一個匹配並且返回一個Option[String]

  Option/Some/None類型將會在之后的章節進行討論,可以簡單的認為Option是一個容器,它要么持有0或者一個值,對於findFirstIn,成功時會返回Some("123"),不成功時會返回None

  

  返回類型為Option[String]的方法要么返回Some(String),要么返回None

  針對Option類型,想要獲取其值,可以使用如下方法

    · getOrElse

    · 使用foreach

    · 使用匹配表達式 

  使用getOrElse方法,你可以嘗試獲取值,或者失敗時定義缺省值

  

  使用foreach方法如下

  

  使用匹配表達式方法如下

  

  2.7 字符串的替換模式

  1. 問題描述

  你需要要在字符串中搜索正則表達式模式,並替換它們。

  2. 解決方案

  由於String是不可變的,所以你不能直接在字符串上進行查找並替換的操作,但是你可以創建包含替換內容的新字符串,可以使用replaceAll方法,要記得將結果賦值

  

  也可創建一個正則表達式,然后調用replaceAllIn方法,同樣要記得將結果賦值給新的字符串

  

  也可以調用replaceFirstIn來替換第一個匹配值,同樣要記得將結果賦值給新的字符串

  

  2.8 提取模式匹配的字符串部分

  1. 問題描述

  你需要提取字符串的一個或多個匹配正則達表達式的部分。

  2. 解決方案

  首先定義提取的正則表達式模式,然后將它們放置在括號里面形成正則表達式組

  

  上述示例從指定字符串中提取了數字部分和字母部分,並且分別賦值給count和fruit。

  3. 討論

  上述示例的語法可能有點古怪,似乎是將模式兩次定義成val字段,但是這種語法很便捷並且可讀性很高,可以試想你在編寫一個搜索引擎,你想讓人們用各種各樣的短語搜索電影,你可以讓他們輸入任何這些短語來獲得電影列表

  

  你可以定義一系列的正則表達式進行匹配,例如 

// match "movies 80301"
val MoviesZipRE = "movies (\\d{5})".r

// match "movies near boulder, co"
val MoviesNearCityStateRE = "movies near ([a-z]+), ([a-z]{2})".r

  之后可以根據用戶的輸入進行匹配,然后獲取搜索結果,偽代碼如下 

textUserTyped match {
case MoviesZipRE(zip) => getSearchResults(zip)
case MoviesNearCityStateRE(city, state) => getSearchResults(city, state)
case _ => println("did not match a regex")
}

  上述的正則表達式可以匹配如下字符串

  

  在匹配時,需要考慮所有情況,如case _ 表示不能匹配,如下字符串將無法匹配

  

  2.9 訪問字符串的字符

  1. 問題描述

  你想要獲取字符串中特定位置的字符。

  2. 解決方案

  可以使用Java的charAt方法

  

  另外,一種更好的方法是數組表示法

  

  3. 討論

  當map方法和foreach不適用時,可以將String看做Array類型,然后使用數組表示法來訪問字符,Scala中的數組表示法不同於Java的數組表示法,因為在Scala中,數組表示法是一個方法調用。

  

  在調用數組表示法時實際上調用的是apply方法,,但是由於Scala語法糖的存在,可以直接使用數組表示法獲取指定字符。

  2.10 向String類添加自定義方法

  1. 問題描述

   你想要向字符串類添加自定義方法,如"HAL".increment,而非使用工具類StringUtilities的increment方法。

  2. 解決方案

  可以定義一個隱式類,然后在類中定義你想要添加的方法

  

  在實際的編碼中會稍微復雜一點,因為隱式類需要必須要被定義在可以被定義方法的作用域中,這意味着隱式類必須定義在class、object、package object中。 

復制代碼
package com.leesf.utils

object StringUtils {
    implicit class StringImprovements(val s: String) {
        def increment = s.map(c => (c + 1).toChar)
    }
}    
復制代碼

  在使用時,需要導入com.leesf.utils.StringUtils

復制代碼
package foo.bar

import com.leesf.utils.StringUtils._

object Main extends App {
    println("HAL".increment)
}
復制代碼

  還可將隱式類放在包對象中 

復制代碼
package com.leesf

package object utils {
    implicit class StringImprovements(val s: String) {
        def increment = s.map(c => (c + 1).toChar)
    }
}    
復制代碼

  在使用時,需要導入com.leesf.utils  

復制代碼
package foo.bar

import com.leesf.utils._

object MainDriver extends App {
    println("HAL".increment)
}
復制代碼

  針對Scala2.10前的版本,與上述做法有稍許不同,首先需要在普通的類中定義increment方法  

class StringImprovements(val s: String) {
    def increment = s.map(c => (c + 1).toChar)
}

  然后定義隱式方法進行轉化  

implicit def stringToString(s: String) = new StringImprovements(s)

  3. 討論

  在Scala中,你可以通過隱式轉化向封閉的類添加新的方法,並且在使用時導入,而不需要繼承該類來添加方法(有些final類根本不能繼承)。

三、總結

  本篇博文講解了Scala中的String知識點,其也是在Scala編程中使用頻率非常高的知識點,對其進行了梳理,加深了印象,也謝謝各位園友的觀看~ 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM