標籤:
此文轉自csdn,看完瞬間就明白了
首先,我們知道,Java中的資料類型分為兩種,基礎資料型別 (Elementary Data Type)和引用資料類型。而基礎資料型別 (Elementary Data Type),為什麼不直接使用他們的封裝類呢,例如Integer、Long等等呢?下面是Thinking in Java 中的解釋:
有 一系列類需特別對待;可將它們想象成“基本”、“主要”或者“主”(Primitive)類型,進行程式設計時要頻繁用到它們。之所以要特別對待,是由於 用new建立對象(特別是小的、簡單的變數)並不是非常有效,因為new將對象置於“堆”裡。對於這些類型,Java採納了與C和C++相同的方法。也就 是說,不是用new建立變數,而是建立一個並非控制代碼的“自動”變數。這個變數容納了具體的值,共置於堆棧中,能夠更高效地存取。
Java決定了每種主要類型的大小。就象在大多數語言裡那樣,這些大小並不隨著機器結構的變化而變化。這種大小的不可更改正是Java程式具有很強移植能力的原因之一。
可以看到,Sun是為了系統的最佳化,才採用基本的資料類型。
那麼,基礎資料型別 (Elementary Data Type)和引用資料類型到底有什麼區別呢 ?下面依然是Thinking in Java中的話:程式運行時,我們最好對資料儲存到什麼地方做到心中有數。特別要注意的是記憶體的分配。有六個地方都可以儲存資料:
(1) 寄存器。這是最快的儲存地區,因為它位於和其他所有儲存方式不同的地方:處理器內部。然而,寄存器的數量
十分有限,所以寄存器是根據需要由編譯器分配。我們對此沒有直接的控制權,也不可能在自己的程式裡找到寄存器
存在的任何蹤跡。
(2) 堆棧。駐留於常規RAM(隨機訪問儲存空間)地區,但可通過它的“堆棧指標”獲得處理的直接支援。堆棧指標若向
下移,會建立新的記憶體;若向上移,則會釋放那些記憶體。這是一種特別快、特別有效資料儲存方式,僅次於寄存器
。建立程式時,Java編譯器必須準確地知道堆棧內儲存的所有資料的“長度”以及“存在時間”。這是由於它必鬚生
成相應的代碼,以便向上和向下移動指標。這一限制無疑影響了程式的靈活性,所以儘管有些Java資料要儲存在堆棧
裡——特別是物件控點,但Java對象並不放到其中。
(3) 堆。一種常規用途的記憶體池(也在RAM地區),其中儲存了Java對象。和堆棧不同,“記憶體堆”或“堆”(Heap)
最迷人的地方在於編譯器不必知道要從堆裡分配多少儲存空間,也不必知道儲存的資料要在堆裡停留多長的時間。
因此,用堆儲存資料時會得到更大的靈活性。要求建立一個對象時,只需用new命令編製相關的代碼即可。執行這些
代碼時,會在堆裡自動進行資料的儲存。當然,為達到這種靈活性,必然會付出一定的代價:在堆裡分配儲存空間時
會花掉更長的時間!
(4) 靜態儲存。這兒的“靜態”(Static)是指“位於固定位置”(儘管也在RAM裡)。程式運行期間,靜態儲存的
資料將隨時等候調用。可用static關鍵字指出一個對象的特定元素是靜態。但Java對象本身永遠都不會置入靜態存
儲空間。
(5) 常數儲存。常數值通常直接置於程式碼內部。這樣做是安全的,因為它們永遠都不會改變。有的常數需要嚴格地保護,所以可考慮將它們置入唯讀記憶體(ROM)。
從上面我們就可以看到,基礎資料型別 (Elementary Data Type)的變數,是存放在堆棧中的,我更喜歡直接稱之為棧(stack)。而引用資料類型的引用,
也就是Thinking in Java所說的控制代碼,也是直接放在棧裡面,而引用資料類型引用的對象,則是放在堆(heap)中的
明白了基礎資料型別 (Elementary Data Type)和引用資料類型在記憶體中的區別,那麼理解下面的問題就和簡單了。一、String對象的建立問題ss這本是一個簡單的問題,無關輕重,大家用天天使用字串也用的很happy,你們真的瞭解字串嗎?大家知道,String的建立有兩種形式,一種是直接放在雙引號("")中,一種是直接用new的方式建立,那麼這兩種方式有什麼區別呢?1、String a="abc";
2、String b=new String("abcdef")上面的兩行代碼,分別建立了幾個對象呢?還是先說說JVM對字串是怎麼處理的吧。字串是一個引用資料類型,根據我們上面對java中基礎資料型別 (Elementary Data Type)和引用資料類型的
分析,String對象肯定是存放在堆中的。關鍵在於,String對象是一個final的常量,JVM為了管理String和高效性,在堆中,會用一個
特殊的字串池來儲存字串直接量。所謂的字串直接量,就是指字串由字串、數值常量直接構成,沒有變數,也沒有方法。
例如
String a="abc";
String b="abc"+10;
String c="abc"+"def";這樣的字串,JVM在編譯的時候就能完全確定這個字串,就可以在字串池中建立該字串,如果字串池中已經有這樣的
String,就讓該變數直接指向在字串池中的該字串。
而另外一些不是字串直接量的字串例如
String a="abc"+"abc".lenght();
String b=a+"asc";JVM無法在編譯的時候就確定該字串,就只能在程式運行期間,在堆中非字串池中的地方開闢記憶體,存放這樣的字串。 好了,明白JVM對字串的處理,在來談談String對象建立的問題:(這兩行代碼是無關的)1、String a="abc";
2、String b=new String("abcdef");上面的兩個建立字串的方法到底分別建立了幾個對象?第1行,建立了一個對象,它是一個字串直接量,直接被建立存放在字串池中。第2行,建立了2個對象,"abcdef"本身就是一個字串直接量,被建立後存放在字串池中,然後,它又作為一個參數,傳遞給了構造器new String(),這樣又在堆中非字串池的地方開闢了塊記憶體,建立了一個字串對象。 那麼,就在問去幾個問題,下面的幾段代碼分別建立了幾個對象1、String a="ab";2、String b="ab";3、String d="abcd";4、String c=a+"cd";5、String e=new String("abcd")6、String f="aaa"+"bbb";答案:第1行,1個、因為字串池還是空的,不存在字串"ab",所以在字串池建立了一個對象"ab"。第2行,0個、因為字串池中"ab"已經存在,所以沒有建立,直接將b指向"ab";
第3行,1個、因為也是在字串池中建立了一個"abcd"對象
第4行,2個、先在字串池中建立"cd",因為採用了變數,所以要在堆中非字串池中在建立一個對象a+"cd";第5行,1個、因為字串池中已經存在"abcd",直接將他作為一個參數傳給構造器new一個新的對象。第6行,1個,因為在編譯的時候就能確定f的值,所以只建立一個"aaabbb"到字串池中;(很多人誤解為3個) 在看下面的final String a="aaa";final String b="bbb";String c=a+b;字串c對被建立在哪裡,字串池,還是堆中的其他地方,答案是字串池中,因為a、b是常量,不是變數。
二、String和StringBuffer的區別
在Java中,String和StringBuffer,StringBuilder都能表示一個字串。
String的定義是:表示一個字串常量。那麼,看下面的一段代碼:
String str="abc";
str="def";
常量,基礎資料型別 (Elementary Data Type)的常量是不可變的,可是我們可以看到,這裡所謂的字串常量str是可變的。這是為什麼呢?
引用資料類型的常量,指的是引用的對象不可變,而引用,也即控制代碼是可變的。也就是多,當一個String類型的字串
改變時,實際上是它指向的記憶體單元發生了改變,原來的記憶體單元的內容並沒有發生改變。
StringBuffer的定義是:表示一個字串變數。StringBuffer strbuf="abc";這樣的初始化是錯誤的,要想初始化一個
StringBuffer類型的字串必須使用new來初始化,如
由此可見,StringBuffer字串變數,指的是不僅引用可變,引用的對象也是可變的。
下面的兩個例子可以很好的說明這個問題。
1、
String str1="abc";
String str2=str1;
StringBuffer str3=new StringBuffer("abc");
StringBuffer str4=str3;
str1=str1+str2;
str3=str3.append(str4);
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
System.out.println(str4);
輸出的結果:
abcabc
abc
abcabc
abcabc
2、
public class Test5
{
public static void main(String[] args)
{
String str1="java";
StringBuffer str2=new StringBuffer("java");
test(str1);
test(str2);
System.out.println(str1);
System.out.println(str2);
}
public static void test(String str)
{
str=str.replace("j", "i");
}
public static void test(StringBuffer str)
{
str.append("c");
}
}
程式的輸出結果為
java
javac
(String對象的處理確實與其他的對象有所區別,在深度複製中也有體現,對它的處理更像是對一個基礎資料型別 (Elementary Data Type)的處理。
這主要就是因為它是一個常量的原因。
三、==和equals的問題
==的含義,如果是比較基礎資料型別 (Elementary Data Type),那麼就是比較資料類型字面值的大小。如果是比較引用資料類型,就是比較它們
在記憶體位址上是否是相同的。
而equals方法,是Object的方法之一,所有的java類都有這個方法,區別只是自己有沒有重寫的問題。如果沒有重寫,那麼
也是直接比較記憶體位址是否相同。重寫了,那就要看它們是怎麼重寫的。
看下面的例子
String str1 = "abc";
String str2 = "abc";
String str3=new String("abc");
String str4=new String("abc");
StringBuffer str5=new StringBuffer("abc");
StringBuffer str6=new StringBuffer("abc");
System.out.println("1:"+(str1==str2));
System.out.println("2:"+(str1.equals(str2)));
System.out.println("3:"+(str2==str3));
System.out.println("4:"+(str2.equals(str3)));
System.out.println("5:"+(str3==str4));
System.out.println("6:"+(str3.equals(str4)));
//System.out.println("7:"+(str4==str5));
System.out.println("8:"+(str4.equals(str5)));
System.out.println("9:"+(str5==str6));
System.out.println("10:"+(str5.equals(str6)));
輸出結果是
1:true
2:true
3:false
4:true
5:false
6:true
8:false
9:false
10:false
解釋:
String對象的初始化有兩種方式,前面已經解釋了。
可見,str1和str2表示的"abc"都是存放在字串池中,而在字串池中,這兩個"abc"其實是一個記憶體中的資料,
所以str1==str2是true。str1.equals(str2)是true。
str3和str4採用的new方式,那麼它們對用的字串"abc"都是在堆中非字串池中,分別存放在堆中不同的地方,
所以str2==str3是false。str3==str4是false。
而String和Strinbuffer除了都是直接繼承Object之外,並沒有其他的直接聯絡,兩者完全是不相干的類。
所以才有str4==str5是false、str4.equals(str5)是false。
而StringBuffer也根本沒有重寫從父類繼承的equals方法,所以
str5==str6是false。str5.equals(str6)是false。
四、數組的問題
數組也是一個引用資料類型,Java中的數組是靜態,也就是說,一旦數組初始化完成指定了它的長度,就不能在去改變它的長度。
可是,數組也是和String一樣的,引用的對象不可變,但是引用是可變的
int[] a=new int[5];
a代表的是引用,new int[5]才代表引用的對象。也就是說new int[5]這個真正代表數組的對象在記憶體裡面是不能改變所佔記憶體的大小,
但是,卻可以去改變陣列變數a的引用,指向別的數組對象。
在來說下數組在Java中的記憶體配置問題。
數組的引用是放在棧中的,而引用的對象是放在堆中的,不管這個數組中的元素是基礎資料型別 (Elementary Data Type),還是引用資料類型。
只要記住一點:
在Java中,所有局部變數都是放在棧裡面儲存的,不管是基礎資料型別 (Elementary Data Type)的變數,還是引用資料類型的變數,都是儲存在各自的方法棧區;但參考型別變數所引用的對象,都是放在堆記憶體中的。
值得一說的是,如果是一個參考型別的數組,例如
String[] str=new String[6];
引用str存放在棧裡面,而str指向的堆記憶體中也是引用,它說指向的引用才真正的指向堆記憶體中的字串。
java中資料在記憶體中的狀態