每天都在用String,你真的了解嗎?
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
? 作者?|?wugongzi?
來源 | urlify.cn/fABr6j
66套java從入門到精通實(shí)戰(zhàn)課程分享
一、String概述
java.lang.String?類代表字符串。Java程序中所有的字符串文字(例如"abc")都可以被看作是實(shí)現(xiàn)此類的實(shí)例
String 中包括用于檢查各個(gè)字符串的方法,比如用于比較字符串,搜索字符串,提取子字符串以及創(chuàng)建具有翻譯為大寫或小寫的所有字符的字符串的副本。
二、String源碼分析
2.1.String成員變量
// String的屬性值,String的內(nèi)容本質(zhì)上是使用不可變的char類型的數(shù)組來存儲(chǔ)的。
private?final?char?value[];
/*String類型的hash值,hash是String實(shí)例化對(duì)象的hashcode的一個(gè)緩存值,這是因?yàn)镾tring對(duì)象經(jīng)常被用來進(jìn)行比較,如果每次比較都重新計(jì)算hashcode值的話,是比較麻煩的,保存一個(gè)緩存值能夠進(jìn)行優(yōu)化 */
private?int?hash; // Default to 0
//serialVersionUID為序列化ID
private?static?final?long?serialVersionUID = -6849794470754667710L;
//serialPersistentFields屬性用于指定哪些字段需要被默認(rèn)序列化
private?static?final?ObjectStreamField[] serialPersistentFields = new?ObjectStreamField[0];
serialPersistentFields具體用法為:
private?static?final?ObjectStreamField[] serialPersistentFields = {
????new?ObjectStreamField("name", String.class),
????new?ObjectStreamField("age", Integer.Type)
}transient用于指定哪些字段不會(huì)被默認(rèn)序列化,兩者同時(shí)使用時(shí),transient會(huì)被忽略。
在 Java 9 及之后,String 類的實(shí)現(xiàn)改用 byte 數(shù)組存儲(chǔ)字符串,同時(shí)使用?coder來標(biāo)識(shí)使用了哪種字符集編碼。
public?final?class?String
????implements?java.io.Serializable, Comparable<String>, CharSequence?{
????/** The value is used for character storage. */
????private?final?byte[] value;
????/** The identifier of the encoding used to encode the bytes in {@code?value}. */
????private?final?byte coder;
}
2.2.String構(gòu)造方法
1、空參構(gòu)造
/**
* final聲明的 value數(shù)組不能修改它的引用,所以在構(gòu)造函數(shù)中一定要初始化value屬性
*/
public?String() {
??this.value?= "".value;
}
2、用一個(gè)String來構(gòu)造
// 初始化一個(gè)新創(chuàng)建的 String 對(duì)象,使其表示一個(gè)與參數(shù)相同的字符序列;換句話說,新創(chuàng)建的字符串是該參數(shù)字符串的副本。
public?String(String original) {
????this.value?= original.value;
????this.hash = original.hash;
}
3、使用char數(shù)組構(gòu)造
// 分配一個(gè)新的 String,使其表示字符數(shù)組參數(shù)中當(dāng)前包含的字符序列。
public?String(char?value[]) {
????this.value?= Arrays.copyOf(value, value.length);
}
// 分配一個(gè)新的 String,它包含取自字符數(shù)組參數(shù)一個(gè)子數(shù)組的字符。
public?String(char?value[], int?offset, int?count)
4、使用int數(shù)組構(gòu)造
// 分配一個(gè)新的 String,它包含 Unicode 代碼點(diǎn)數(shù)組參數(shù)一個(gè)子數(shù)組的字符。
public?String(int[] codePoints, int?offset, int?count)5、使用byte數(shù)組構(gòu)造
// 通過使用平臺(tái)的默認(rèn)字符集解碼指定的 byte 數(shù)組,構(gòu)造一個(gè)新的 String。
public?String(byte?bytes[])
????
// 通過使用平臺(tái)的默認(rèn)字符集解碼指定的 byte 數(shù)組,構(gòu)造一個(gè)新的 String。
public?String(byte[] bytes)
// 通過使用指定的 charset 解碼指定的 byte 數(shù)組,構(gòu)造一個(gè)新的 String。
public?String(byte[] bytes, Charset charset)
// 通過使用平臺(tái)的默認(rèn)字符集解碼指定的 byte 子數(shù)組,構(gòu)造一個(gè)新的 String。
public?String(byte[] bytes, int?offset, int?length)
// 通過使用指定的 charset 解碼指定的 byte 子數(shù)組,構(gòu)造一個(gè)新的 String。
public?String(byte[] bytes, int?offset, int?length, Charset charset)
???????????
// 通過使用指定的字符集解碼指定的 byte 子數(shù)組,構(gòu)造一個(gè)新的 String。
public?String(byte[] bytes, int?offset, int?length, String charsetName)
?????????
//通過使用指定的 charset 解碼指定的 byte 數(shù)組,構(gòu)造一個(gè)新的 String。
public?String(byte[] bytes, String charsetName)6、使用StringBuffer或者StringBuilder構(gòu)造
//分配一個(gè)新的字符串,它包含字符串緩沖區(qū)參數(shù)中當(dāng)前包含的字符序列。
public?String(StringBuffer buffer)
??????????
????
// 分配一個(gè)新的字符串,它包含字符串生成器參數(shù)中當(dāng)前包含的字符序列。
public?String(StringBuilder builder)三、字符串常量池
作為最基礎(chǔ)的引用數(shù)據(jù)類型,Java 設(shè)計(jì)者為 String 提供了字符串常量池以提高其性能,那么字符串常量池的具體原理是什么?
3.1常量池的實(shí)現(xiàn)思想
字符串的分配,和其他的對(duì)象分配一樣,耗費(fèi)高昂的時(shí)間與空間代價(jià),作為最基礎(chǔ)的數(shù)據(jù)類型,大量頻繁的創(chuàng)建字符串,極大程度地影響程序的性能
JVM為了提高性能和減少內(nèi)存開銷,在實(shí)例化字符串常量的時(shí)候進(jìn)行了一些優(yōu)化
為字符串開辟一個(gè)字符串常量池,類似于緩存區(qū)
創(chuàng)建字符串常量時(shí),首先查看字符串常量池是否存在該字符串
存在該字符串,返回引用實(shí)例,不存在,實(shí)例化該字符串并放入池中
實(shí)現(xiàn)的基礎(chǔ)
實(shí)現(xiàn)該優(yōu)化的基礎(chǔ)是因?yàn)樽址遣豢勺兊模梢圆挥脫?dān)心數(shù)據(jù)沖突進(jìn)行共享
運(yùn)行時(shí)實(shí)例創(chuàng)建的全局字符串常量池中有一個(gè)表,總是為池中每個(gè)唯一的字符串對(duì)象維護(hù)一個(gè)引用,這就意味著它們一直引用著字符串常量池中的對(duì)象,所以,在常量池中的這些字符串不會(huì)被垃圾收集器回收
3.2常量池的內(nèi)存位置
堆
存儲(chǔ)的是對(duì)象,每個(gè)對(duì)象都包含一個(gè)與之對(duì)應(yīng)的class
JVM只有一個(gè)堆區(qū)(heap)被所有線程共享,堆中不存放基本類型和對(duì)象引用,只存放對(duì)象本身
對(duì)象的由垃圾回收器負(fù)責(zé)回收,因此大小和生命周期不需要確定
棧
每個(gè)線程包含一個(gè)棧區(qū),棧中只保存基礎(chǔ)數(shù)據(jù)類型的對(duì)象和自定義對(duì)象的引用(不是對(duì)象)
每個(gè)棧中的數(shù)據(jù)(原始類型和對(duì)象引用)都是私有的
棧分為3個(gè)部分:基本類型變量區(qū)、執(zhí)行環(huán)境上下文、操作指令區(qū)(存放操作指令)
數(shù)據(jù)大小和生命周期是可以確定的,當(dāng)沒有引用指向數(shù)據(jù)時(shí),這個(gè)數(shù)據(jù)就會(huì)自動(dòng)消失
方法區(qū)
靜態(tài)區(qū),跟堆一樣,被所有的線程共享
方法區(qū)中包含的都是在整個(gè)程序中永遠(yuǎn)唯一的元素,如class,static變量
字符串常量池則存在于方法區(qū)
3.3案例分析
String?str1 = "abc";
String?str2 = "abc";
String?str3 = "abc";
String?str4 = new?String("abc");
String?str5 = new?String("abc");
String?str6 = new?String("abc");

變量str1到str6的內(nèi)存分布如圖所示;str1 = "abc"會(huì)先去常量池中看有沒有abc,如果有則引用這個(gè)字符串,沒有則創(chuàng)建一個(gè);str2和str3都是直接引用常量池中的abc;
String str4 = new String("abc") 這段代碼會(huì)做兩步操作,第一步在常量池中查找是否有"abc"對(duì)象,有則返回對(duì)應(yīng)的引用實(shí)例,沒有則創(chuàng)建對(duì)應(yīng)的實(shí)例對(duì)象;在堆中new一個(gè)String("abc")對(duì)象,將對(duì)象地址賦值給Str4,創(chuàng)建一個(gè)引用。
四、String內(nèi)存分析
我們先來看一段代碼
public?class?TestString?{
????public?static?void?main(String[] args) {
????????String str1 = "wugongzi";
????????String str2 = new?String("wugongzi");
????????String str3 = str2; //引用傳遞,str3直接指向st2的堆內(nèi)存地址
????????String str4 = "wugongzi";
????????/**
?????????* ==:
?????????* 基本數(shù)據(jù)類型:比較的是基本數(shù)據(jù)類型的值是否相同
?????????* 引用數(shù)據(jù)類型:比較的是引用數(shù)據(jù)類型的地址值是否相同
?????????* 所以在這里的話:String類對(duì)象==比較,比較的是地址,而不是內(nèi)容
?????????*/
?????????System.out.println(str1==str2);//false
?????????System.out.println(str1==str3);//false
?????????System.out.println(str3==str2);//true
?????????System.out.println(str1==str4);//true
????}
}下面我們來分析一下這段代碼的內(nèi)存分布

第一步:String str1 = "wugongzi" ,首先會(huì)去常量池中看有沒有wugongzi,發(fā)現(xiàn)沒有,則在常量池中創(chuàng)建了一個(gè)wugongzi,然后將wugongzi的內(nèi)存地址賦值給str1;
第二步:String str2 = new String("wugongzi"),這段代碼因?yàn)閚ew了一個(gè)String對(duì)象,它首先常量池中查找是否有wugongzi,發(fā)現(xiàn)已經(jīng)有了,則返回對(duì)應(yīng)的引用實(shí)例;然后再去堆中new一個(gè)String("wugongzi")對(duì)象,將對(duì)象地址賦值給Str2,創(chuàng)建一個(gè)引用。
第三步:String str3 = str2,// 引用傳遞,str3直接指向st2的堆內(nèi)存地址;
第四步:String str4 = "wugongzi",同第一步
五、String常用方法
5.1.equals方法
這里重寫了Object中的equals方法,用來判斷兩個(gè)對(duì)象實(shí)際意義上是否相等,也就是值是否相等
public?boolean equals(Object anObject) {
????//如果引用的是同一個(gè)對(duì)象,則返回真
????if?(this?== anObject) {
????????return?true;
????}
????//如果不是String類型的數(shù)據(jù),返回假
????if?(anObject instanceof String) {
????????String anotherString = (String) anObject;
????????int?n = value.length;
????????//如果char數(shù)組長(zhǎng)度不相等,返回假
????????if?(n == anotherString.value.length) {
????????????char?v1[] = value;
????????????char?v2[] = anotherString.value;
????????????int?i = 0;
????????????//從后往前單個(gè)字符逐步判斷,如果有不相等,則返回假
????????????while?(n-- != 0) {
????????????????if?(v1[i] != v2[i])
????????????????????????return?false;
????????????????i++;
????????????}
????????????//每個(gè)字符都相等,則返回真
????????????return?true;
????????}
????}
????return?false;
}
5.2.compareTo方法
用于比較兩個(gè)字符串的大小,如果兩個(gè)字符串長(zhǎng)度相等則返回0,如果長(zhǎng)度不相等,則返回當(dāng)前字符串的長(zhǎng)度減去被比較的字符串的長(zhǎng)度。
public?int?compareTo(String anotherString) {
????//自身對(duì)象字符串長(zhǎng)度len1
????int?len1 = value.length;
????//被比較對(duì)象字符串長(zhǎng)度len2
????int?len2 = anotherString.value.length;
????//取兩個(gè)字符串長(zhǎng)度的最小值lim
????int?lim = Math.min(len1, len2);
????char?v1[] = value;
????char?v2[] = anotherString.value;
?
????int?k = 0;
????//從value的第一個(gè)字符開始到最小長(zhǎng)度lim處為止,如果字符不相等,返回自身(對(duì)象不相等處字符-被比較對(duì)象不相等字符)
????while?(k < lim) {
????????char?c1 = v1[k];
????????char?c2 = v2[k];
????????if?(c1 != c2) {
????????????return?c1 - c2;
????????}
????????k++;
????}
????//如果前面都相等,則返回(自身長(zhǎng)度-被比較對(duì)象長(zhǎng)度)
????return?len1 - len2;
}5.3.hashCode方法
這里重寫了hashCode方法,采用多項(xiàng)式進(jìn)行計(jì)算,可以通過不同的字符串得到相同的hash,所以兩個(gè)String對(duì)象的hashCode相同,并不代表兩個(gè)String是相同的。
算法:假設(shè)n = 3
i=0 -> h = 31 * 0 + val[0]
i=1 -> h = 31 * (31 * 0 + val[0]) + val[1]
i=2 -> h = 31 * (31 * (31 * 0 + val[0]) + val[1]) + val[2]
h = 3131310 + 3131val[0] + 31val[1] + val[2]
h = 31^(n-1)val[0] + 31^(n-2)val[1] + val[2]
public?int?hashCode() {
????int?h = hash;
????//如果hash沒有被計(jì)算過,并且字符串不為空,則進(jìn)行hashCode計(jì)算
????if?(h == 0?&& value.length > 0) {
????????char?val[] = value;
?
????????//計(jì)算過程
????????//s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
????????for?(int?i = 0; i < value.length; i++) {
????????????h = 31?* h + val[i];
????????}
????????//hash賦值
????????hash = h;
????}
????return?h;
}
5.4.startWith方法
startsWith和endWith方法也是比較常用的方法,常用來判斷字符串以特定的字符開始或結(jié)尾。
public?boolean startsWith(String prefix, int?toffset) {
????char?ta[] = value;
????int?to = toffset;
????char?pa[] = prefix.value;
????int?po = 0;
????int?pc = prefix.value.length;
????// Note:?toffset might be near -1>>>1.
????//如果起始地址小于0或者(起始地址+所比較對(duì)象長(zhǎng)度)大于自身對(duì)象長(zhǎng)度,返回假
????if?((toffset < 0) || (toffset > value.length - pc)) {
????????return?false;
????}
????//從所比較對(duì)象的末尾開始比較
????while?(--pc >= 0) {
????????if?(ta[to++] != pa[po++]) {
????????????return?false;
????????}
????}
????return?true;
}
?
public?boolean startsWith(String prefix) {
????return?startsWith(prefix, 0);
}
?
public?boolean endsWith(String suffix) {
????return?startsWith(suffix, value.length - suffix.value.length);
}5.5.concat方法
concat方法用于將指定的字符串參數(shù)連接到字符串上。
public?String concat(String str) {
????int?otherLen = str.length();
????//如果被添加的字符串為空,則返回對(duì)象本身
????if?(otherLen == 0) {
????????return?this;
????}
????int?len = value.length;
????char?buf[] = Arrays.copyOf(value, len + otherLen);
????str.getChars(buf, len);
????return?new?String(buf, true);
}5.6.replace方法
replace的參數(shù)是char和charSequence,即可以支持字符的替換,也支持字符串的替換(charSequence即字符串序列的意思)
replaceAll的參數(shù)是regex,即基于規(guī)則表達(dá)式的替換,比如可以通過replaceAll("\d","*")把一個(gè)字符串所有的數(shù)字字符都替換成星號(hào);
相同點(diǎn):都是全部替換,即把源字符串中的某一字符或者字符串全部替換成指定的字符或者字符串。
不同點(diǎn):replaceAll支持正則表達(dá)式,因此會(huì)對(duì)參數(shù)進(jìn)行解析(兩個(gè)參數(shù)均是),如replaceAll("\d",""),而replace則不會(huì),replace("\d","")就是替換"\d"的字符串,而不會(huì)解析為正則。
public?String replace(char?oldChar, char?newChar) {
????//新舊值先對(duì)比
????if?(oldChar != newChar) {
????????int?len = value.length;
????????int?i = -1;
????????char[] val = value;
?
????????//找到舊值最開始出現(xiàn)的位置
????????while?(++i < len) {
????????????if?(val[i] == oldChar) {
????????????????break;
????????????}
????????}
????????//從那個(gè)位置開始,直到末尾,用新值代替出現(xiàn)的舊值
????????if?(i < len) {
????????????char?buf[] = new?char[len];
????????????for?(int?j = 0; j < i; j++) {
????????????????buf[j] = val[j];
????????????}
????????????while?(i < len) {
????????????????char?c = val[i];
????????????????buf[i] = (c == oldChar) ? newChar : c;
????????????????i++;
????????????}
????????????return?new?String(buf, true);
????????}
????}
????return?this;
}5.7.trim方法
trim用于刪除字符串的頭尾的空格。
public?String trim() {
????int?len = value.length;
????int?st = 0;
????char[] val = value; /* avoid getfield opcode */
?
????//找到字符串前段沒有空格的位置
????while?((st < len) && (val[st] <= ' ')) {
????????st++;
????}
????//找到字符串末尾沒有空格的位置
????while?((st < len) && (val[len - 1] <= ' ')) {
????????len--;
????}
????//如果前后都沒有出現(xiàn)空格,返回字符串本身
????return?((st > 0) || (len < value.length)) ? substring(st, len) : this;
}5.8.其他方法
//字符串是否包含另一個(gè)字符串
public?boolean?contains(CharSequence s)
//返回字符串長(zhǎng)度
public?int?length()
//返回在指定index位置的字符,index從0開始
public?char?charAt(int?index)
//返回str字符串在當(dāng)前字符串首次出現(xiàn)的位置,若沒有返回-1
public?int?indexOf(String str)
//返回str字符串最后一次在當(dāng)前字符串中出現(xiàn)的位置,若無(wú)返回-1
public?int?lastIndexOf(String str)
//返回s字符串從當(dāng)前字符串startpoint位置開始的,首次出現(xiàn)的位置
public?int?indexOf(String s ,int?startpoint)
//返回s字符串從當(dāng)前字符串startpoint位置開始的,最后一次出現(xiàn)的位置
public?int?lastIndexOf(String s ,int?startpoint)
//返回從start開始的子串
public?String substring(int?startpoint)
//返回從start開始到end結(jié)束的一個(gè)左閉右開的子串。start可以從0開始的
public?String substring(int?start,int?end)
//按照regex將當(dāng)前字符串拆分,拆分為多個(gè)字符串,整體返回值為String[]
public?String[] split(String regex)
六、String常用轉(zhuǎn)化
6.1字符串 --->基本數(shù)據(jù)類型、包裝類
調(diào)用相應(yīng)的包裝類的parseXxx(String str);
String str1 = "wugongzi";
int?i = Integer.parseInt(str1);
System.out.println(i);6.2字符串---->字節(jié)數(shù)組
調(diào)用字符串的getBytes()
String str = "wugongzi520";
byte[] b = str.getBytes();
for(int?j = 0;j < b.length;j++){
????System.out.println((char)b[j]);
}6.3字節(jié)數(shù)組---->字符串
調(diào)用字符串的構(gòu)造器
String?str = "wugongzi520";
byte[] b = str.getBytes();
String?str3 = new?String(b);
System.out.println(str3);6.4字符串---->字符數(shù)組
調(diào)用字符串的toCharArray();
String str4 = "abc123";
char[] c = str4.toCharArray();
for(int?j = 0;j < c.length;j++){
????System.out.println(c[j]);
}6.5字符數(shù)組---->字符串
調(diào)用字符串的構(gòu)造器
粉絲福利:108本java從入門到大神精選電子書領(lǐng)取
???
?長(zhǎng)按上方鋒哥微信二維碼?2 秒 備注「1234」即可獲取資料以及 可以進(jìn)入java1234官方微信群
感謝點(diǎn)贊支持下哈?
