您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java String對(duì)象的使用方法有哪些”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Java String對(duì)象的使用方法有哪些”吧!
String 對(duì)象的實(shí)現(xiàn)
String對(duì)象是 Java 中使用最頻繁的對(duì)象之一,所以 Java 公司也在不斷的對(duì)String對(duì)象的實(shí)現(xiàn)進(jìn)行優(yōu)化,以便提升String對(duì)象的性能,看下面這張圖,一起了解一下String對(duì)象的優(yōu)化過(guò)程。
1. 在 Java6 以及之前的版本中
String對(duì)象是對(duì) char 數(shù)組進(jìn)行了封裝實(shí)現(xiàn)的對(duì)象,主要有四個(gè)成員變量:char 數(shù)組、偏移量 offset、字符數(shù)量 count、哈希值 hash。
String對(duì)象是通過(guò) offset 和 count 兩個(gè)屬性來(lái)定位 char[] 數(shù)組,獲取字符串。這么做可以高效、快速地共享數(shù)組對(duì)象,同時(shí)節(jié)省內(nèi)存空間,但這種方式很有可能會(huì)導(dǎo)致內(nèi)存泄漏。
2. 從 Java7 版本開始到 Java8 版本
從 Java7 版本開始,Java 對(duì)String類做了一些改變。String類中不再有 offset 和 count 兩個(gè)變量了。這樣的好處是String對(duì)象占用的內(nèi)存稍微少了些,同時(shí) String.substring 方法也不再共享 char[],從而解決了使用該方法可能導(dǎo)致的內(nèi)存泄漏問(wèn)題。
3. 從 Java9 版本開始
將 char[] 數(shù)組改為了 byte[] 數(shù)組,為什么需要這樣做呢?我們知道 char 是兩個(gè)字節(jié),如果用來(lái)存一個(gè)字節(jié)的字符有點(diǎn)浪費(fèi),為了節(jié)約空間,Java 公司就改成了一個(gè)字節(jié)的byte來(lái)存儲(chǔ)字符串。這樣在存儲(chǔ)一個(gè)字節(jié)的字符是就避免了浪費(fèi)。
在 Java9 維護(hù)了一個(gè)新的屬性 coder,它是編碼格式的標(biāo)識(shí),在計(jì)算字符串長(zhǎng)度或者調(diào)用 indexOf() 函數(shù)時(shí),需要根據(jù)這個(gè)字段,判斷如何計(jì)算字符串長(zhǎng)度。coder 屬性默認(rèn)有 0 和 1 兩個(gè)值, 0 代表Latin-1(單字節(jié)編碼),1 代表 UTF-16 編碼。如果 String判斷字符串只包含了 Latin-1,則 coder 屬性值為 0 ,反之則為 1。
String 對(duì)象的創(chuàng)建方式
1、通過(guò)字符串常量的方式
String str= "pingtouge"的形式,使用這種形式創(chuàng)建字符串時(shí), JVM 會(huì)在字符串常量池中先檢查是否存在該對(duì)象,如果存在,返回該對(duì)象的引用地址,如果不存在,則在字符串常量池中創(chuàng)建該字符串對(duì)象并且返回引用。使用這種方式創(chuàng)建的好處是:避免了相同值的字符串重復(fù)創(chuàng)建,節(jié)約了內(nèi)存。
2、String()構(gòu)造函數(shù)的方式
String str = new String("pingtouge")的形式,使用這種方式創(chuàng)建字符串對(duì)象過(guò)程就比較復(fù)雜,分成兩個(gè)階段,首先在編譯時(shí),字符串pingtouge會(huì)被加入到常量結(jié)構(gòu)中,類加載時(shí)候就會(huì)在常量池中創(chuàng)建該字符串。然后就是在調(diào)用new()時(shí),JVM 將會(huì)調(diào)用String的構(gòu)造函數(shù),同時(shí)引用常量池中的pingtouge字符串,在堆內(nèi)存中創(chuàng)建一個(gè)String對(duì)象并且返回堆中的引用地址。
了解了String對(duì)象兩種創(chuàng)建方式,我們來(lái)分析一下下面這段代碼,加深我們對(duì)這兩種方式的理解,下面這段代碼片中,str是否等于str1呢?
String str = "pingtouge"; String str1 = new String("pingtouge"); system.out.println(str==str1)
我們逐一來(lái)分析這幾行代碼,首先從String str = "pingtouge"開始,這里使用了字符串常量的方式創(chuàng)建字符串對(duì)象,在創(chuàng)建pingtouge字符串對(duì)象時(shí),JVM會(huì)去常量池中查找是否存在該字符串,這里的答案肯定是沒(méi)有的,所以JVM將會(huì)在常量池中創(chuàng)建該字符串對(duì)象并且返回對(duì)象的地址引用,所以str指向的是pingtouge字符串對(duì)象在常量池中的地址引用。
然后是String str1 = new String("pingtouge")這行代碼,這里使用的是構(gòu)造函數(shù)的方式創(chuàng)建字符串對(duì)象,根據(jù)我們上面對(duì)構(gòu)造函數(shù)方式創(chuàng)建字符串對(duì)象的理解,str1得到的應(yīng)該是堆中pingtouge字符串的引用地址。由于str指向的是pingtouge字符串對(duì)象在常量池中的地址引用而str1指向的是堆中pingtouge字符串的引用地址,所以str肯定不等于str1。
String 對(duì)象的不可變性
從我們知道String對(duì)象的那一刻起,我想大家都知道了String對(duì)象是不可變的。那它不可變是怎么做到的呢?Java 這么做能帶來(lái)哪些好處?我們一起來(lái)簡(jiǎn)單的探討一下,先來(lái)看看String 對(duì)象的一段源碼:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; }
從這段源碼中可以看出,String類用了 final 修飾符,我們知道當(dāng)一個(gè)類被 final 修飾時(shí),表明這個(gè)類不能被繼承,所以String類不能被繼承。這是String不可變的第一點(diǎn)
再往下看,用來(lái)存儲(chǔ)字符串的char value[]數(shù)組被private 和final修飾,我們知道對(duì)于一個(gè)被final的基本數(shù)據(jù)類型的變量,則其數(shù)值一旦在初始化之后便不能更改。這是String不可變的第二點(diǎn)。
Java 公司為什么要將String設(shè)置成不可變的,主要從以下三方面考慮:
1、保證 String 對(duì)象的安全性。假設(shè) String 對(duì)象是可變的,那么 String 對(duì)象將可能被惡意修改。
2、保證 hash 屬性值不會(huì)頻繁變更,確保了唯一性,使得類似 HashMap 容器才能實(shí)現(xiàn)相應(yīng)的 key-value 緩存功能。
3、可以實(shí)現(xiàn)字符串常量池
String 對(duì)象的優(yōu)化
字符串是我們常用的Java類型之一,所以對(duì)字符串的操作也是避免不了的,在對(duì)字符串的操作過(guò)程中,如果使用不當(dāng),性能會(huì)天差地別。那么在字符串的操作過(guò)程中,有哪些地方需要我們注意呢?
優(yōu)雅的拼接字符串
字符串的拼接是對(duì)字符串操作使用最頻繁的操作之一,由于我們知道String對(duì)象的不可變性,所以我們?cè)谧銎唇訒r(shí)盡可能少的使用+進(jìn)行字符串拼接或者說(shuō)潛意識(shí)里認(rèn)為不能使用+進(jìn)行字符串拼接,認(rèn)為使用+進(jìn)行字符串拼接會(huì)產(chǎn)生許多無(wú)用的對(duì)象。事實(shí)真的是這樣嗎?我們來(lái)做一個(gè)實(shí)驗(yàn)。我們使用+來(lái)拼接下面這段字符串。
String str8 = "ping" +"tou"+"ge";
一起來(lái)分析一下這段代碼會(huì)產(chǎn)生多少個(gè)對(duì)象?如果按照我們理解的意思來(lái)分析的話,首先會(huì)創(chuàng)建ping對(duì)象,然后創(chuàng)建pingtou對(duì)象,最后才會(huì)創(chuàng)建pingtouge對(duì)象,一共創(chuàng)建了三個(gè)對(duì)象。真的是這樣嗎?其實(shí)不是這樣的,Java 公司怕我們程序員手誤,所以對(duì)編譯器進(jìn)行了優(yōu)化,上面的這段字符串拼接會(huì)被我們的編譯器優(yōu)化,優(yōu)化成一個(gè)String str8 = "pingtouge";對(duì)象。除了對(duì)常量字符串拼接做了優(yōu)化以外,對(duì)于使用+號(hào)動(dòng)態(tài)拼接字符串,編譯器也做了相應(yīng)的優(yōu)化,以便提升String的性能,例如下面這段代碼:
String str = "pingtouge"; for(int i=0; i<1000; i++) { str = str + i; }
編譯器會(huì)幫我們優(yōu)化成這樣:
String str = "pingtouge"; for(int i=0; i<1000; i++) { str = (new StringBuilder(String.valueOf(str))).append(i).toString(); }
可以看出 Java 公司對(duì)這一塊進(jìn)行了不少的優(yōu)化,防止由于程序員不小心導(dǎo)致String性能急速下降,盡管 Java 公司在編譯器這一塊做了相應(yīng)的優(yōu)化,但是我們還是能看出 Java 公司優(yōu)化的不足之處,在動(dòng)態(tài)拼接字符串時(shí),雖然使用了 StringBuilder 進(jìn)行字符串拼接,但是每次循環(huán)都會(huì)生成一個(gè)新的 StringBuilder 實(shí)例,同樣也會(huì)降低系統(tǒng)的性能。
所以我們?cè)谧鲎址唇訒r(shí),我們需要從代碼的層面進(jìn)行優(yōu)化,在動(dòng)態(tài)的拼接字符串時(shí),如果不涉及到線程安全的情況下,我們顯示的使用 StringBuilder 進(jìn)行拼接,提升系統(tǒng)性能,如果涉及到線程安全的話,我們使用 StringBuffer 來(lái)進(jìn)行字符串拼接
巧妙的使用 intern() 方法
* <p> * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * <p> public native String intern();
這是 intern() 函數(shù)的官方注釋說(shuō)明,大概意思就是 intern 函數(shù)用來(lái)返回常量池中的某字符串,如果常量池中已經(jīng)存在該字符串,則直接返回常量池中該對(duì)象的引用。否則,在常量池中加入該對(duì)象,然后 返回引用。
有一位Twitter工程師在QCon全球軟件開發(fā)大會(huì)上分享了一個(gè)他們對(duì) String對(duì)象優(yōu)化的案例,他們利用String.intern()方法將以前需要20G內(nèi)存存儲(chǔ)優(yōu)化到只需要幾百兆內(nèi)存。這足以體現(xiàn)String.intern()的威力,我們一起來(lái)看一個(gè)例子,簡(jiǎn)單的了解一下String.intern()的用法。
public static void main(String[] args) { String str = new String("pingtouge"); String str1 = new String("pingtouge"); System.out.println("未使用intern()方法:"+(str==str1)); System.out.println("未使用intern()方法,str:"+str); System.out.println("未使用intern()方法,str1:"+str1); String str2= new String("pingtouge").intern(); String str3 = new String("pingtouge").intern(); System.out.println("使用intern()方法:"+(str2==str3)); System.out.println("使用intern()方法,str2:"+str2); System.out.println("使用intern()方法,str3:"+str3); }
從結(jié)果中可以看出,未使用String.intern()方法時(shí),構(gòu)造相同值的字符串對(duì)象返回不同的對(duì)象引用地址,使用String.intern()方法后,構(gòu)造相同值的字符串對(duì)象時(shí),返回相同的對(duì)象引用地址。這能幫我們節(jié)約不少空間。
String.intern()方法雖然好,但是我們要結(jié)合場(chǎng)景使用,不能亂用,因?yàn)槌A砍氐膶?shí)現(xiàn)是類似于一個(gè)HashTable的實(shí)現(xiàn)方式,HashTable 存儲(chǔ)的數(shù)據(jù)越大,遍歷的時(shí)間復(fù)雜度就會(huì)增加。如果數(shù)據(jù)過(guò)大,會(huì)增加整個(gè)字符串常量池的負(fù)擔(dān)。
靈活的字符串的分割
字符串的分割是字符串操作的常用操作之一,對(duì)于字符串的分割,大部分人使用的都是 Split() 方法,Split() 方法大多數(shù)情況下使用的是正則表達(dá)式,這種分割方式本身沒(méi)有什么問(wèn)題,但是由于正則表達(dá)式的性能是非常不穩(wěn)定的,使用不恰當(dāng)會(huì)引起回溯問(wèn)題,很可能導(dǎo)致 CPU 居高不下。在以下兩種情況下 Split() 方法不會(huì)使用正則表達(dá)式:
傳入的參數(shù)長(zhǎng)度為1,且不包含“.$|()[{^?*+\”regex元字符的情況下,不會(huì)使用正則表達(dá)式
傳入的參數(shù)長(zhǎng)度為2,第一個(gè)字符是反斜杠,并且第二個(gè)字符不是ASCII數(shù)字或ASCII字母的情況下,不會(huì)使用正則表達(dá)式
所以我們?cè)谧址指顣r(shí),應(yīng)該慎重使用 Split() 方法,首先考慮使用 String.indexOf() 方法進(jìn)行字符串分割,如果 String.indexOf() 無(wú)法滿足分割要求,再使用 Split() 方法,使用 Split() 方法分割字符串時(shí),需要注意回溯問(wèn)題。
感謝各位的閱讀,以上就是“Java String對(duì)象的使用方法有哪些”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Java String對(duì)象的使用方法有哪些這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。