最近見版內對String討論的帖子很多,但是又眾說風云,為了讓大家徹底理解String這個特殊對象,我趁此機會來對String對象做進一步的分析。
友情提示:希望在閱讀之前,你已經對String對象有了基礎的了解。并且已經熟知"類型"、"引用"、"對象"、"實例化"、"堆棧"和關于Object對象的equals()方法的使用機制,并且已經熟知String對象對equals()方法重寫后的使用機制!
首先大家知道,String既可以作為一個對象來使用,又可以作為一個基本類型來使用。這里指的作為一個基本類型來使用只是指使用方法上的,比如String s = "Hello",它的使用方法如同基本類型int一樣,比如int i = 1;,而作為一個對象來使用,則是指通過new關鍵字來創建一個新對象,比如String s = new String("Hello")。但是它的內部動作其實還是創建了一個對象,這點稍后會說到。
其次,對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對象還有更深層次的應用。
這里我將分析一下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應用都可以建立在此基礎上來進行分析。
posted on 2007-02-04 20:13
高天賜 閱讀(271)
評論(1) 編輯 收藏