String深入學習筆記

1.對String對象的比較方法需要了解。
Java裏對象之間的比較有兩種概念,這裏拿String對象來說:一種是用"=="來比較,這種比較是針對兩個String類型的變量的引用,也就是說如果兩個String類型的變量,它們所引用同一個String對象(即指向同一塊內存堆),則"=="比較的結果是true。另一種是用Object對象的equals()方法來比較,String對象繼承自Object,並且對equals()方法進行了重寫。兩個String對象通過equals()方法來進行比較時,其實就是對String對象所封裝的字符串內容進行比較,也就是說如果兩個String對象所封裝的字符串內容相同(包括大小寫相同),則equals()方法將返回true。

現在開始將對String對象的創建做具體的分析。

首先看以下代碼段:

String s1 = new String("Hello");鯤鵬網
String s2 = new String("Hello");

System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

以上代碼段的打印結果是:

false
true

這個結果相信大家很好理解,兩個String類型的變量s1和s2都通過new關鍵字分別創建了一個新的String對象,這個new關鍵字爲創建的每個對象分配一塊新的、獨立的內存堆。因此當通過"=="來比較它們所引用的是否是同一個對象時,將返回false。而通過equals()方法來比較時,則返回true,因爲這兩個對象所封裝的字符串內容是完全相同的。

好,現在把上面的代碼段修改如下:

String s1 = new String("Hello");
String s2 = s1;

System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

以上代碼段的打印結果是:

true
true

這個結果應該更好理解,變量s1還是通過new關鍵字來創建了一個新的String對象,但這裏s2並沒有通過new關鍵字來創建一個新的String對象,而是直接把s1賦值給了s2,即把s1的引用賦值給了s2,所以s2所引用的對象其實就是s1所引用的對象。所以通過"=="來比較時,返回true。既然它們引用的都是同一個對象,那麼通過equals()方法來比較時,肯定也返回true,這裏equals()方法其實在對同一個對象進行比較,自己肯定等於自己咯。

說到此,其實應該沒什麼大問題,因爲這些都是標準的創建對象的動作,但是String對象還有另一種使用方法,也就是開頭所提到的可以作爲一個基本類型來使用,請看以下代碼段:

String s = "Hello";

看到這裏,相信一些初學的朋友或者對String對象還沒搞清楚的朋友開始疑惑了,你肯定會問,這個使用方法在其內部到底發生了什麼?其實這就是String對象容易混淆的關鍵!

其實在啓動程序時,虛擬機會創建一塊String對象的String緩衝池。當String對象作爲一個基本類型來使用時,比如:String s = "Hello";,虛擬機會先在這個String緩衝池內尋找是否有相同值的String對象存在,如果存在,則把這個String對象的引用賦值給s。如果不存在,虛擬機會先在這個String緩衝池內創建此String對象,然後把引用賦值給s。

說到這裏,相信大家已經開始明白了。那麼請看下面的代碼段:

String s1 = "Hello";
String s2 = "Hello";

System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

以上代碼段的打印結果是:

true
true

爲什麼這個結果?那麼來分析一下。首先這兩個String對象都是作爲一個基本類型來使用的,而不是通過new關鍵字來創建的,因此虛擬機不會爲這兩個String對象分配新的內存堆,而是到String緩衝池中來尋找。

首先爲s1尋找String緩衝池內是否有與"Hello"相同值的String對象存在,此時String緩衝池內是空的,沒有相同值的String對象存在,所以虛擬機會在String緩衝池內創建此String對象,其動作就是new String("Hello");。然後把此String對象的引用賦值給s1。

接着爲s2尋找String緩衝池內是否有與"Hello"相同值的String對象存在,此時虛擬機找到了一個與其相同值的String對象,這個String對象其實就是爲s1所創建的String對象。既然找到了一個相同值的對象,那麼虛擬機就不在爲此創建一個新的String對象,而是直接把存在的String對象的引用賦值給s2。

這裏既然s1和s2所引用的是同一個String對象,即自己等於自己,所以以上兩種比較方法都返回ture。

到這裏,對String對象的基本概念應該都已經理解了。
現在我來小結一下:
針對String作爲一個基本類型來使用:

1。如果String作爲一個基本類型來使用,那麼我們視此String對象是String緩衝池所擁有的。
2。如果String作爲一個基本類型來使用,並且此時String緩衝池內不存在與其指定值相同的String對象,那麼此時虛擬機將爲此創建新的String對象,並存放在String緩衝池內。
3。如果String作爲一個基本類型來使用,並且此時String緩衝池內存在與其指定值相同的String對象,那麼此時虛擬機將不爲此創建新的String對象,而直接返回已存在的String對象的引用。

針對String作爲一個對象來使用:

1。如果String作爲一個對象來使用,那麼虛擬機將爲此創建一個新的String對象,即爲此對象分配一塊新的內存堆,並且它並不是String緩衝池所擁有的,即它是獨立的。

理解了以上內容後,請看以下代碼段:

String s1 = "Hello";
String s2 =  new String("Hello");



System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

以上代碼段的打印結果是:

false
true

根據上面的小結來進行分析。第一行是把String作爲一個基本類型來使用的,因此s1所引用的對象是屬於String緩衝池內的。並且此時String緩衝池內並沒有與其值相同的String對象存在,因此虛擬機會爲此創建一個新的String對象,即new String("Hello");。第二行是把String作爲一個對象來使用的,因此s2所引用的對象不屬於String緩衝池內的,即它是獨立的。通過new關鍵字,虛擬機會爲此創建一個新的String對象,即爲它分配了一塊新的內存堆。因此"=="比較後的結果是false,因爲s1和s2所引用的並不是同一個對象,它們是獨立存在的。而equals()方法所返回的是true,因爲這兩個對象所封裝的字符串內容是完全相同的。

現在,相信大家已經完全搞清楚String對象是怎麼一回事了:)但是到此並沒有結束,因爲String對象還有更深層次的應用。

2.對String的更深的理解
這裏我將分析一下String對象的intern()方法的應用。
intern()方法將返回一個字符串對象的規範表示法,即一個同該字符串內容相同的字符串,但是來自於唯一字符串的String緩衝池。這聽起來有點拗口,其實它的機制有如以下代碼段:

String s = new String("Hello");
s = s.intern();

以上代碼段的功能實現可以簡單的看成如下代碼段:

String s = "Hello";

你一定又開始疑惑了?那麼你可以先看第二個代碼段。第二個代碼段的意思就是從String緩衝池內取出一個與其值相同的String對象的引用賦值給s。如果String緩衝池內沒有與其相同值的String對象存在,則在其內爲此創建一個新的String對象。那麼第一段代碼的意思又是什麼呢?我們知道通過new關鍵字所創建出的對象,虛擬機會爲它分配一塊新的內存堆。如果平凡地創建相同內容的對象,虛擬機同樣會爲此分配許多新的內存堆,雖然它們的內容是完全相同的。拿String對象來說,如果連續創建10個相同內容的String對象(new String("Hello")),那麼虛擬機將爲此分配10塊獨立的內存堆。假設所創建的String對象的字符串內容十分大,假設一個Stirng對象封裝了1M大小的字符串內容,那麼如果我們創建10個此相同String對象的話,我們將會毫無意義的浪費9M的內存空間。我們知道String是final類,它所封裝的是字符串常量,因此String對象在創建後其內部(字符串)值不能改變,也因此String對象可以被共享。所以對於剛纔提到的假設,我們所創建的10個相同內容的String對象,其實我們只需爲此創建一個String對象,然後被其它String變量所共享。要實現這種機制,唯一的、簡單的方法就是使用String緩衝池,因爲String緩衝池內不會存在相同內容的String對象。而intern()方法就是使用這種機制的途徑。在一個已實例化的String對象上調用intern()方法後,虛擬機會在String緩衝池內尋找與此Stirng對象所封裝的字符串內容相同值的String對象,然後把引用賦值給引用原來的那個String對象的String類型變量。如果String緩衝池內沒有與此String對象所封裝的字符串內容相同值的String對象存在,那麼虛擬機會爲此創建一個新的String對象,並把其引用賦值給引用原來的那個String對象的String類型變量。這樣就達到了共享同一個String對象的目的,而原先那個通過new關鍵字所創建出的String對象將被拋棄並被垃圾回收器回收掉。這樣不但降低了內存的使用消耗,提高了性能,而且在String對象的比較上也同樣更方便了,因爲相同的String對象將被共享,所以要判斷兩個String對象是否相同,則只需要使用"=="來比較,而無需再使用equals()方法來比較,這樣不但使用起來更方便,而且也提高了性能,因爲String對象的equals()方法將會對字符串內容拆解,然後逐個進行比較,如果字符串內容十分大的話,那麼這個比較動作則大大降低了性能。

說到此,大家可能對具體應用還有點模糊,那麼我來舉個簡單的示例,以便闡述以上概念:

假設有一個類,它有一個接收消息的方法,這個方法記錄用戶傳來的消息(假設消息內容可能較大,並且重複率較高),並且把消息按接收順序記錄在一個列表中。我想有些朋友會這樣設計:

import java.util.*;

public class Messages {

ArrayList messages = new ArrayList();

public void record(String msg) {
messages.add(msg);
}

public List getMessages() {
return messages;
}
}

這種設計方案好嗎?假設我們重複的發送給record()方法同一個消息(消息來自不同的用戶,所以可以視每個消息爲一個new String("...")),並且消息內容較大,那麼這個設計將會大大浪費內存空間,因爲消息列表中記錄的都是新創建的、獨立的String對象,雖然它們的內容都相同。那麼怎麼樣可以對其進行優化呢,其實很簡單,請看如下優化後的示例:

import java.util.*;

public class Messages {

ArrayList messages = new ArrayList();

public void record(String msg) {
messages.add(msg.intern());
}

public List getMessages() {
return messages;
}
}

正如你所看到的,原先record()方法中的messages.add(msg);代碼段變成了messages.add(msg.intern());,僅僅對msg參數調用了intern()方法,這樣將對重複的消息進行共享機制,從而降低了內存消耗,提高了性能。

這個例子的確有點牽強,但是這裏只是爲了闡述以上概念!

至此,String對象的迷霧都被消除了,大家只要牢記這些概念,以後再複雜的String應用都可以建立在此基礎上來進行分析。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章