在正式介紹String之前,我們先介紹下CharSequence
char + sequence 就是字符的序列的意思
Java中萬事萬物都是對象類型
而對於字符的序列,也就是多個char, 這么一種東西, 使用CharSequence這個接口來描述
既然是接口,自然規定了作為字符序列的基本協議
CharSequence簡介
char charAt(int index); | 返回指定索引的char |
int length() | 返回字符序列的長度 |
CharSequence subSequence(int start, int end) | 返回子序列 |
String toString() | 返回一個包含此序列中字符的字符串該字符串與此序列的順序相同 |
default IntStream chars() | 返回此序列的int stream,每個char零位擴展為int |
default IntStream codePoints() | 返回此序列的代碼點的stream |
我們都知道1.8的一個亮點就是stream和lambda
default方法也是1.8新增的,默認實現
既然CharSequence表示了 字符序列這么一個概念
顯然,String內部是char數組,就是一個char的序列
String簡介
String 類代表字符串
Java 程序中的所有字符串字面值(如 "abc" )都是String的實例
內部有一個char[]
注意到 上面的final, 字符串是常量;它們的值在創建之后不能更改
String str = "abc";
等效於:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
Java 語言提供對字符串串聯符號("+")以及將其他對象轉換為字符串的特殊支持
說白了就是+被重載過了,也提供了強大的將對象轉換為字符串的能力
char是UTF-16中的代碼單元,所以字符序列就是代碼單元的序列
仍舊是一個char可能是一個字符,也可能是半個字符
String 類提供處理 Unicode 代碼點(即字符)和 Unicode 代碼單元(即 char 值)的方法
屬性CASE_INSENSITIVE_ORDER
這就是一個比較器
邏輯也很簡單,兩個String 按照字典順序進行比較,忽略大小寫的
以兩者length小的那個作為循環次數,進行循環
如果第一個相等比較第二個,依次類推,直到不一樣
如果所有的都相等,那么就比較長度了 return n1 - n2
字符與字節數組
在繼續下去之前,再次簡單介紹下字符與字節數組的關系
字符到字節,是一個編碼的過程
字節到字符是一個解碼的過程
|
同樣的一個字符,在不同的字符集和編碼方式下,實際存儲的值,將是不同的
比如前面說的Unicode字符集,UTF8 和UTF16編碼后的數據是不同的
這個編碼后的數據,也就是字節 , 他們是不一樣的
|
同樣的一個編碼值,在不同的字符集中,可能代表着不同的字符 |
所以字符與字節之間,必然有編碼參與其中
這個編碼環節是必然存在的,否則,你就沒辦法把字節與字符聯系起來
|
一個字符可以根據 字符集編碼 進行多種方式的編碼 一個字節數組也可以根據 字符集編碼 進行多種方式的解碼 對於同一個字符,不管進行何種編碼,當他們按照當初編碼的方式進行解碼時,必然對應的還是同樣的那個字符 |
操作系統的文件都是以字節序列的形式存儲的,所以任何一個文件都是有編碼的 比如你在txt文件中輸入了一個字符 這個字符 底層就會使用指定的編碼存儲到字節中 軟件本身又把這個編碼以字符的形式呈現出來 所以你才看得到是一個字符 比如這個文件中11111.txt中,存儲了一個漢字春天的 " 春" 編碼方式是UTF8 二進制軟件查看是E6 98 A5 與我們進行UTF8 編碼計算的結果是對應的 |
ANSI編碼 不同的國家和地區制定了不同的標准 由此產生了 GB2312、GBK、Big5、Shift_JIS 等各自的編碼標准 這些使用 1 至 4 個字節來代表一個字符的各種漢字延伸編碼方式,稱為 ANSI 編碼 在簡體中文Windows操作系統中,ANSI 編碼代表 GBK 編碼; 在日文Windows操作系統中,ANSI 編碼代表 Shift_JIS 編碼 再看下面一個例子 使用ultraedit 新建了一個文件,里面寫了一個漢字 "春", 其實這個默認格式就是操作系統的編碼,也就是ANSI 也就是GBK 查看二進制編碼為 B4 BA 然后我們再去對照GBK的碼表,你會發現完全對的上 |
任何一個文件,他其實有自帶或者說默認的一個編碼 凡是呈現字符的地方,都有一個編碼在默默地支撐,才能夠讓你看得見,看得清楚字符 這個字符的保存 , 就是字符按照編碼表 編碼 成字節序列的過程 這個字符的呈現 , 就是字節序列按照編碼表 解碼 成字符的過程 當你使用計算機,進行字符處理工作的時候,無時無刻都在進行着編碼與解碼 |
String構造方法
String是常用類之一,所以提供了非常豐富的方法
String是字符序列 內部是char[] char就是一個十六進制的數 16位表示
所以char[] 可以用來構造String
|
char是16位數能夠表示代碼單元, int自然可以直接表示一個代碼點了,所以也可以使用int來構造String |
另外再加上我們剛才關於字節數組與字符關系的介紹,也可以使用字節數組構造String |
下面表格是幾個基本的構造方法
String() | 空String ,沒啥必要因為String是不可變的![]() |
String(char[])
String(char[], int, int)
|
借助於字符數組或者字符數組的一部分創建對象 內部本來就是字符數組 char[] 所以自然可以使用char[]構造 直接進行拷貝,所以對原有字符數組的修改不影響String對象 |
String(int[], int, int) | 使用代碼點構造String public String(int[] codePoints, int offset, int count) offset 和 count為范圍限制 |
String(String) | ![]() |
String(StringBuffer) | |
String(StringBuilder) |
getBytes 方法
先提一下另外一個方法,getBytes
使用指定的字符集將此 String 編碼為 byte 序列
我的編輯器環境是UTF8編碼的
"春" 的UTF8編碼上面已經分析了
![]()
也就是說我這邊有一個UTF8的字符"春" 源文件中保存的是 E6 98 A5
對於下面所有的getBytes來說,"春" 這個字符形狀符號是不變的
獲得的字節數組就是 這個字符形狀符號 根據不同字符集編碼方式, 編碼而得到的字節數組
下面的各種轉換換一個描述就是:UTF8的字符"春" ,在其他的字符集下面,編碼都是多少啊?
|
為什么UTF-8 是-26 -104 -91 ? 而不是e6 98 a5?進制問題 |
getBytes總共三種形式 指定編碼或者使用默認
getBytes(String)
getBytes(Charset)
getBytes()
還有一種已經棄用 了
|
通過字節數組 byte[] 構造
String提供了6個跟byte[] 相關的構造方法
getBytes方法是字符是固定的, 固定的以UTF8格式存儲在我的源文件中,
然后根據不同的編碼方式,轉換為字節數組 byte[]
String的構造方法,則是將各個已經編碼過的字節數組 byte[] 按照指定的編碼方式解析 還原成為一個字符
然后再將這個字符以char[] 也就是UTF-16的方式進行存儲的
我的源文件IDE環境是UTF8那么最終構造的String就是UTF8的,不會是其他的
比如下面的構造方法,使用前面示例中的 bytes數組
然后使用 String(byte[], String) 進行構造
看得很清楚
String字符串 s1 中存儲的value 是Unicode的代碼點U+6695 (0號平面,一個代碼單元就是一個代碼點)
也就是十進制的26149
使用byte[] 字節數組構造String的過程是下圖這樣子的 字節數組,根據指定字符編碼轉換為那個字符 然后在把字符按照UTF16 進行編碼 存儲到String中的char[] 上面的例子可以很好地印證這一點,字節數組是[-76, -70] 也就是 : ffffffb4 ffffffba 也就是 B4 BA 明明是GBK的"春" 根本就不是6625 對應關系就是他們表示的是同一個字符 |
既然字節數組與字符的轉換離不開編碼,所以自然通過byte[] 構造String對象時,必須要有編碼
不設定並不是沒有,而是使用默認的
既然使用字節數組,那么有的時候可能需要指定范圍,所以有兩個根本的構造方法
|
然后還有默認字符編碼的簡化形式 |
再然后就是長度為整個字節數組的簡化形式 |
這幾個構造方法根本在於理解 字節數組與字符的轉換 以及必須的byte[] 字節數組 以及 編碼 |
valueOf
valueOf 系列用來包裝
String中用來將基本類型 以及 Object 轉換為String
char相關的都是直接構造String對象
其余(除了boolean,他是轉換為字符串 true和false返回)
都是toString
copyValueOf
copyValueOf方法內部就是直接調用的兩個構造方法 還不如直接使用new創建來的直接,只不過使用這個方法有更好的可讀性 |
獲取指定位置代碼單元和代碼點的方法
charAt(int) 返回指定索引處的 char 值 索引范圍為從 0 到 length() - 1 簡單粗暴, 不管三七二十一就是代碼單元 如果是輔助平面,那就可能是代理項 |
codePointAt(int) 返回指定索引處的代碼點, 范圍從 0 到 length() - 1 他跟Character中的codePointAt方法邏輯含義是一樣的 如果是高代理,如果下一個也在掌控范圍內,如果下一個是低代理,那么返回代碼點 否則,返回代碼單元 也就是一個char |
codePointBefore(int) 返回指定索引之前的字符(Unicode 代碼點) 其范圍從 1 到 length 他跟Character中的codePointBefore方法邏輯含義是一樣的 如果index-1 是低代理,如果在往前一個index-2 也是有效范圍內,如果他還恰好是一個高代理,返回代碼點 否則,返回代碼單元,也就是一個char |
codePointCount(int, int) 此 String 的指定文本范圍中的 Unicode 代碼點數 文本范圍始於指定的 beginIndex,一直到索引 endIndex - 1 處的 char, 包含頭不包含尾 該文本范圍的長度(用 char 表示)是 endIndex-beginIndex 由於一個代碼點的代碼單元個數可能是1個可能是2個,所以代碼點的個數需要計算,就不直觀了 他跟Character中的codePointCount方法邏輯含義是一樣的 |
offsetByCodePoints(int, int) 他跟Character中的offsetByCodePoints方法邏輯含義是一樣的 返回此 String 中從給定的 index 處偏移 codePointOffset 個代碼點的索引 根本原因還是一個代碼點的代碼單元個數可能是1個可能是2個 所以 偏移codePointOffset個代碼點的 代碼單元的個數不確定,需要調用方法計算 |
getChars(int, int, char[], int)復制
實例方法 就是一個復制方法,名字不太規范 復制String中指定索引開始的srcBegin 和 srcEnd 包含頭不包含尾 到另一個字節數組 char dst[]中, 存放的起始位置為dstBegin public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) |
需要注意的是,復制的是char 代碼單元 就是String 內部char[] 的下標索引 |
開始結束匹配校驗
startsWith(String, int)
startsWith(String)
|
實例方法 測試String是否以指定的前綴開始 還可以指定起始位置處開始比較 從源代碼看得出來,挨個比較內部的char 從頭開始,全部一致才返回true 單參數是雙參數的簡化版本 |
endsWith(String) | endwith就是從最后的指定參數長度的位置開始比較 |
indexOf 和lastIndexOf
indexOf 和XXXIndexOf系列都是獲取下標索引相關
需要注意的是,他們的參數都是int或者String
也就是說這些方法都是真正的字符相關的
int indexOf(int ch)
int indexOf(int ch, int fromIndex)
|
返回 指定字符 在此字符串中第一次出現處的索引 返回的匹配的第一個 也可以指定檢索的起始位置, 如果指定了索引 那么返回的值將 大於等於 指定的索引 換個說法: 如果是0號平面返回的是那個代碼單元也就是代碼點的索引
charAt(k) == ch 為 true 的最小 k 值
如果是輔助平面返回的是高代理位的代碼單元的索引 codePointAt(k) == ch 為 true 的最小 k 值 |
int indexOf(String str)
int indexOf(String str, int fromIndex)
|
返回 指定子字符串 在此字符串中第一次出現處的索引
返回匹配的第一個
也可以指定檢索的起始位置,如果指定了索引
那么返回值需要大於等於 指定的索引
匹配的含義為startsWith(str) 為true
如果指定檢索開始的位置, 那么
不僅僅startsWith(str) 為true 還需要索引滿足指定的下標范圍
否則仍舊是返回-1
|
lastIndexOf(int)
lastIndexOf(int, int)
|
返回指定字符在此字符串中最后一次出現處的索引 返回匹配的最后一個 也可以指定檢索位置,但是這個檢索位置與indexOf不同 indexOf中指定的索引,是從索引處往后 lastIndexOf指定的索引, 是反向,從索引處往前 指定了索引就要求 返回值 小於等於 指定索引 換個說法
如果是0號平面返回的是那個代碼單元也就是代碼點的索引
charAt(k) == ch 為 true 的最大 k 值
如果是輔助平面返回的是高代理位的代碼單元的索引
codePointAt(k) == ch 為 true 的最大 k 值 並且 k 小於等於 指定的索引
|
lastIndexOf(String)
lastIndexOf(String, int)
|
返回指定 子字符串 在此字符串中最后一次出現處的索引
返回匹配的最后一個
也可以指定檢索位置,檢索索引的位置也是反向搜索
匹配的含義為startsWith(str) 為true
指定了索引就要求返回值 小於等於 指定索引
|
總共三個維度
匹配第一個或者最后一個 / 匹配字符或者字符串 / 是否指定查找范圍
8個方法
indexOf是從前往后匹配 匹配的是第一個 如果指定了下標索引,從索引處往后找
返回的值要 大於等於 索引
lastIndexOf是從后往前匹配 匹配的是最后一個 如果指定了開始下表索引,是從索引處往前,反向查找
返回的值要 小於等於 索引
匹配字符如果是BMP,代碼單元就是代碼點,返回的就是那個代碼單元也是代碼點的索引
如果是輔助平面,一個代碼點兩個代碼單元,返回的就是高代理位的索引 lastIndexOf和indexOf都是返回高代理項
length
長度獲取,內部char數組的長度

isEmpty()

hashCode
計算公式
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
|
字符串匹配包含
測試兩個字符串區域是否相等
toffset 表示當前對象this 開始的位置
other 表示另外一個String對象
ooffset 表示另外對象開始的位置
len 要匹配的長度
兩個方法其中一個可以指定是否忽略大小寫
|
s1.regionMatches(1,s2,3,4); 讀作: 把s1 從索引1開始 同 s2 從索引3開始,比較len個長度,查看這個區域是否相等 |
public boolean regionMatches( int toffset, String other, int ooffset,int len)
public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
|
contains contains也是一種匹配 當且僅當此字符串包含指定的 char 值序列時,返回 true ![]() |
matches 此字符串是否匹配給定的正則表達式 public boolean matches(String regex) |
相等比較
equals(Object)
equals方法也進行了重寫
比較的是內部的char 序列是否相等
先看是否同一個對象,否則看是否String類
然后再看長度,長度相同挨個比較
|
contentEquals(StringBuffer)
contentEquals(CharSequence)
這兩個方法 分別針對參數StringBuffer 和 CharSequence
他們都是 當且僅當表示相同的 char 值序列時,結果才為 true
比較的也是內容
上面的equals方法也是比較的內容
|
equalsIgnoreCase(String) 比較忽略大小寫,底層依賴的就是區域的比較 只不過區域是整個字符串而已 |
compareToIgnoreCase(String) 字典順序比較兩個字符串,不考慮大小寫 |
compareTo(String) compareTo(String)方法是按照字典序進行排序的 如果字符本身全都相等,但是長度不同,返回長度差 |
子串獲取
public String substring(int beginIndex) public String substring(int beginIndex, int endIndex) public CharSequence subSequence(int beginIndex, int endIndex) |
subSequence 就是調用的subString方法 為什么還需要呢?就是為了遵循CharSequence協議 |
subString也是處理的char的索引,不是字符的 所以一個好好地字符,可能被你截取后,就成為亂碼了 所以如果你想要截取子串也會出現亂碼,可以通過offsetByCodePoints 獲取指定個代碼點后的索引 那么截取的絕對不會是亂碼 看一個例子 0x1f310的高代理位在Character簡介中計算過,它的值跟十進制的55356一樣的 ![]() 對於s 截取后,子串中僅僅是高代理項了 |
大小寫轉換
大小寫的轉換 可以指定Locale 不指定,等價於 指定默認值Locale.getDefault() 大小寫映射關系基於 Character 類指定的 Unicode 標准版 |
toLowerCase(Locale)
toLowerCase()
toUpperCase(Locale)
toUpperCase()
|
split
根據匹配給定的正則表達式來拆分此字符串 子字符串按它們在此字符串中出現的順序排列 如果表達式不匹配輸入的任何部分,那么所得數組只具有一個元素,那就是這個字符串 public String[] split(String regex, int limit) limit 不是什么索引下標,而是表達式模式應用的次數
如果該限制 n 大於 0,則模式將被最多應用 n - 1 次
數組的長度將不會大於 n,而且數組的最后一項將包含所有超出最后匹配的定界符的輸入
如果 n 為非正,那么模式將被應用盡可能多的次數,而且數組可以是任何長度
如果 n 為 0,那么模式將被應用盡可能多的次數,數組可以是任何長度,並且結尾空字符串將被丟棄
例如,字符串 "boo:and:foo" 使用這些參數可生成以下結果:
![]() |
split(String) 是split(String, int) 的簡化形式![]() |
join
join用於將字符序列使用指定的符號進行拼接 需要注意的是,如果有元素為null ,那么"null" 將會被添加進來 |
public static String join(CharSequence delimiter,
CharSequence... elements)
public static String join(CharSequence delimiter,
Iterable<? extends CharSequence> elements)
|
替換
分為字符/字符序列/正則表達式替換
replace是字符/字符序列的替換
replaceXXX是正則的替換
public String replace(char oldChar, char newChar) | 替換后,返回一個新的字符串 如果 oldChar 不存在,則返回這個 String 對象的引用 否則,創建一個新的 String 對象 所有的 oldChar 都被替換為 newChar |
public String replace(CharSequence target, CharSequence replacement) |
替換后,返回一個新的字符串 使用指定的字符序列進行替換 用 "b" 替換字符串 "aaa" 中的 "aa" 將生成 "ba" 而不是 "ab" |
replaceFirst(String, String)
|
|
replaceAll(String, String) |
concat 連接
將指定字符串連接到此字符串的結尾
如果參數字符串的長度為 0,則返回此 String 對象
否則,創建一個新的 String 對象,返回新創建的連接后的字符串
|
先復制一個到數組中 然后再把參數的復制到那個數組中 然后使用數組創建String |
trim
trim() |
最常用的String方法之一,去掉開頭和結尾的空格 |
toString()
|
返回他自己本身 他本來就是一個String了 ![]() |
toCharArray() | 將此字符串轉換為一個新的字符數組 內部本身就是一個char[] 所以自然可以輕松的轉換為char數組 數組拷貝了下 |
format
format
使用指定的格式字符串和參數返回一個格式化字符串
可以指定語言環境
內部還是使用的Formatter
|
intern
intern()
String 私有地維護了, 一個初始為空的字符串池
當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(用 equals(Object) 方法確定),則返回池中的字符串
否則,將此 String 對象添加到池中,並返回此 String 對象的引用
它遵循以下規則:對於任意兩個字符串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true
|
對於直接定義的 "a" "ab" 會進入這個字符串池 如果是new 創建的字符串對象不進入字符串池 如果使用+ 得到的,兩個都是字面的"a" "ab" 形式會進入字符串池,否則如果有變量也不會進入 ![]() str5 和 str3 內容相同,String重寫了equals方法,比較的是內容,所以true str5 和 str3 一個是new出來的,所以地址不相等 false str5.intern() 查找池中是否有"ab" 有的話返回引用,顯然就是str3 所以true str5.intern() 查找池中是否有"ab" 有的話返回引用,顯然就是str3的地址 但是str4 是一個對象,他與str3 不是同一個對象所以不相等 false 最后一個都是獲取"ab"的引用,顯然是相等的 |
總結
String的根本就是字符序列
內部使用char[] 保存數據,而char 是UTF16中的代碼單元
所以String中的很多方法自然也避免不了與Unicode UTF16的聯系
在實際使用方法的時候,一定要稍微留意代碼點與代碼單元之間的關系
不過也不必過於擔心,因為常用字符大多數都在0號平面內,很多方法用起來並不會有什么問題,哪怕你不曾留意