[十二]基礎數據類型之String


在正式介紹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號平面內,很多方法用起來並不會有什么問題,哪怕你不曾留意
 
 


免責聲明!

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



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