JAVA 棧與堆
棧:運算受限的線性表,只允許在表的一端進行插入和刪除操作
特征:先進后出
過程:
向一個棧插入新元素稱為入棧,刪除一個元素稱為出棧或退棧
例: 棧S為(a,b,c),字符c為棧頂元素,若向S壓入一個元素d
則S變為(a,b,c,d)d變為棧頂元素,若直接刪除2個元素,則首先刪除元素d,再刪除元素c,棧為(a,b)棧頂元素為b
堆:樹形數據結構
JAVA:
JVM是基於堆棧的虛擬機.JVM為每個新創建的線程都分配一個堆棧。
內涵:
JAVA程序的運行是以堆棧的操作來完成的。
特征:
堆棧以幀為單位保存線程的狀態。
JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
棧內存:
基本類型的變量和對象的引用變量都是在函數的棧內存中分配
釋放:
當超過變量的作用域后,java會自動釋放掉為該變量分配的內存空間
特征:
存取速度比堆要快,僅次於寄存器,棧數據可以共享
存在棧中的數據大小與生存期必須是確定的,缺乏靈活性
堆內存:
用於存放由new創建的對象和數組,內存的分配由Java虛擬機的自動垃圾回收器來管理
釋放:
數組和對象在沒有引用變量指向它的時候,才變為垃圾,不能在被使用,但仍 然占據內存空間不放,在隨后的一個不確定的時間被垃圾回收器收走
特點:
動態地分配內存大小,生存期也不必事先告訴編譯器
在運行時動態分配內存,存取速度較慢
內存分配策略:
1.靜態存儲分配: 指在編譯時就能確定每個數據目標在運行時刻的存儲空間需求,因而在編譯時就可以給他們分配固定的內存空間
特征:確定的存儲空間
代碼中不允許有可變數據結構(比如可變數組)的存在,也不允許有嵌套或者遞歸的結構出現
2. 棧式存儲分配:動態存儲分配
程序對數據區的需求在編譯時是完全未知的,只有到運行的時候才能夠知道,但是規定在運行中進入一個程序模塊時,必須知道該程序模塊所需的數據區大小才能夠為其分配內存.
特征:先進后出
3. 靜態存儲分配要求在編譯時能知道所有變量的存儲要求,棧式存儲分配要求在過程的入口處必須知道所有的存儲要求,而堆式存儲分配則專門負責在編譯時或運行時模塊入口處都無法確定存儲要求的數據結構的內存分配,比如可變長度串和對象實例.堆由大片的可利用塊或空閑塊組成,堆中的內存可以按照任意順序分配和釋放.
從堆和棧的功能和作用來通俗的比較,堆主要用來存放對象的,棧主要是用來執行程序的
隊列、堆、棧:
隊列是先進先出:
棧:只是指一種使用堆的方法(即先進后出)。
堆:動態分配內存
鏈表的特點是元素之間的關系通過指針來表示,是跟數組相對應的概念
String:
String str = new String("abc"); String str = "abc";創建形式
第一種是用new()來新建對象的,它會在存放於堆中。每調用一次就會創建一個新的對象。而第二種是先在棧中創建一個對String類 的對象引用變量str,然后通過符號引用去字符串常量池 里找有沒有"abc",如果沒有,則將"abc"存放進字符串常量池 ,並令str指向”abc”,如果已經有”abc” 則直接令str指向“abc”。
測試:
比較類里面的數值是否相等用equals()方法,測試兩個包裝類的引用是否指向同一個對象時,用==
這個對於String簡單來說就是比較兩字符串的Unicode序列是否相當,如果相等返回true;而==是 比較兩字符串的地址是否相同,也就是是否是同一個字符串的引用。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一個對象的。
String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
用new的方式是生成不同的對象。每一次生成一個
String str = "abc";的格式定義類時,總是想當然地認為,創建了String類的對象str。擔心陷阱!對象可能並沒有被創建!而可能只是指向一個先前已經創建的 對象。只有通過new()方法才能保證每次都創建一個新的對象。
String是一個對象。因為對象的默認值是null,所以String的默認值也是null;但它又是一種特殊的對象,有其它對象沒有的一些特性。
new String()和new String(”")都是申明一個新的空字符串,是空串不是null;
StringBuffer類可以經常改變String的值
前提:根據棧與堆的特點:
例1:
String s0="kvill";
String s1="kvill";
String s2="kv" + "ill";
System.out.println( s0==s1 );
System.out.println( s0==s2 );
結果為:true true
我們要知結果為道JAVA 會確保一個字符串常量只有一個拷貝
s0,s1,s2都是在編譯期就確定的
例2:
String s0="kvill";
String s1=new String("kvill");
String s2="kv" + new String("ill");
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );
結果為:false false false
例2中s0還是常量池 中"kvill”的應用,s1因為無法在編譯期確定,所以是運行時創建的新對象”kvill”的引用,s2因為有后半部分 new String(”ill”)所以也無法在編譯期確定,所以也是一個新創建對象”kvill”的應用;明白了這些也就知道為何得出此結果了。
示例: 4. String.intern():
再補充介紹一點:存在於.class文件中的常量池,在運行期被JVM裝載,並且可以擴充。String的 intern()方法就是擴充常量池的 一個方法;當一個String實例str調用intern()方法時,Java 查找常量池中 是否有相同Unicode的字符串常量,如果有,則返回其的引用,如果沒有,則在常 量池中增加一個Unicode等於str的字符串並返回它的引用;看示例就清楚了www.2cto.com
示例:
String s0= "kvill";
String s1=new String("kvill");
String s2=new String("kvill");
System.out.println( s0==s1 );
System.out.println( "**********" );
s1.intern();
s2=s2.intern(); //把常量池中"kvill"的引用賦給s2
System.out.println( s0==s1);
System.out.println( s0==s1.intern() );
System.out.println( s0==s2 );
結果為:false false //雖然執行了s1.intern(),但它的返回值沒有賦給s1 true //說明s1.intern()返回的是常量池中"kvill"的引用 true
最后我再破除一個錯誤的理解:有人說,“使用 String.intern() 方法則可以將一個 String 類的保存到一個全局 String 表中 ,如果具有相同值的 Unicode 字符串已經在這個表中,那么該方法返回表中已有字符串的地址,如果在表中沒有相同值的字符串,則將自己的地址注冊到表中”如果我把他說的這個全局的 String 表理解為常量池的話,他的最后一句話,”如果在表中沒有相同值的字符串,則將自己的地址注冊到表中”是錯的:
示例:
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() );
System.out.println( s1+" "+s2 );
System.out.println( s2==s1.intern() );
結果:false kvill kvill true
在這個類中我們沒有聲名一個”kvill”常量,所以常量池中一開始是沒有”kvill”的,當我們調用s1.intern()后就在常量池中新添加了一 個”kvill”常量,原來的不在常量池中的”kvill”仍然存在,也就不是“將自己的地址注冊到常量池中”了。
s1==s1.intern() 為false說明原來的”kvill”仍然存在;s2現在為常量池中”kvill”的地址,所以有s2==s1.intern()為true。
關於String是不可變的
這一說又要說很多,大家只 要知道String的實例一旦生成就不會再改變了,比如說:String str=”kv”+”ill”+” “+”ans”; 就是有4個字符串常量,首先”kv”和”ill”生成了”kvill”存在內存中,然后”kvill”又和” ” 生成 “kvill “存在內存中,最后又和生成了”kvill ans”;並把這個字符串的地址賦給了str,就是因為String的”不可變”產生了很多臨時變量,這也就是為什么建議用StringBuffer的原 因了,因為StringBuffer是可改變的。
下面是一些String相關的常見問題:
String中的final用法和理解
final StringBuffer a = new StringBuffer("111");
final StringBuffer b = new StringBuffer("222");
a=b;//此句編譯不通過 final StringBuffer a = new StringBuffer("111");
a.append("222");// 編譯通過
可見,final只對引用的"值"(即內存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會導致編譯期錯誤。至於它所指向的對象 的變化,final是不負責的。
例:
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false
分析:JVM對於字符串引用,由於在字符串的"+"連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的,即"a" + bb無法被編譯器優化,只有在程序運行期來動態分配並將連接后的新地址賦給b。所以上面程序的結果也就為false。
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true
分析:和[3]中唯一不同的是bb字符串加了final修飾,對於final修飾的變量,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節碼流中。所以此時的"a" + bb和"a" + "b"效果是一樣的。故上面程序的結果為true。
String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println((a == b));
//result = false
private static String getBB() {
return "b";
}
分析:JVM對於字符串引用bb,它的值在編譯期無法確定,只有在程序運行期調用方法后,將方法的返回值和"a"來動態連接並分配地址為b,故上面 程序的結果為false。
通過上面4個例子可以得出得知:
String s = "a" + "b" + "c"; 就等價於String s = "abc";
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c;
這個就不一樣了,最終結果等於:
StringBuffer temp = new StringBuffer();
temp.append(a).append(b).append(c);
String s = temp.toString();
由上面的分析結果,可就不難推斷出String 采用連接運算符(+)效率低下原因分析,形如這樣的代碼:
public class Test {
public static void main(String args[]) {
String s = null;
for(int i = 0; i < 100; i++) {
s += "a";
}
}
}
每做一次 + 就產生個StringBuilder對象,然后append后就扔掉。下次循環再到達時重新產生個StringBuilder對象,然后 append 字符串,如此循環直至結束。如果我們直接采用 StringBuilder 對象進行 append 的話,我們可以節省 N - 1 次創建和銷毀對象的時間。所以對於在循環中要進行字符串連接的應用,一般都是用StringBuffer或StringBulider對象來進行 append操作。
String對象的intern方法理解和分析:
public class Test4 {
private static String a = "ab";
public static void main(String[] args){
String s1 = "a";
String s2 = "b";
String s = s1 + s2;
System.out.println(s == a);//false
System.out.println(s.intern() == a);//true
}
}
這里用到JAVA里面是一個常量池的問題。對於s1+s2操作,其實是在堆里面重新創建了一個新的對象,s保存的是這個新對象在堆空間的的內容,所 以s與a的值是不相等的。而當調用s.intern()方法,卻可以返回s在常量池中的地址值,因為a的值存儲在常量池中,故s.intern和a的值相 等。
總結
棧中用來存放一些原始數據類型的局部變量數據和對象的引用(String,數組.對象等等)但不存放對象內容堆中存放使用new關鍵字創建的對象. 字符串是一個特殊包裝類,其引用是存放在棧里的,而對象內容必須根據創建方式不同定(常量池和堆).有的是編譯期就已經創建好,存放在字符串常 量池中,而有的是運行時才被創建.使用new關鍵字,存放在堆中。
java在執行過程中會划分4個內存區域(heap、stack、data segment、code segment)
java開始執行會把代碼加載到code segment區域然后找到main方法開始執行
data segment是存放靜態變量字符串常量
stack(堆):是存放局部變量等
heap(棧):放new出來的東西
舉例:Student st1=new Student();
st1是對象的引用,它存放在棧中。可以把它理解為一個指針,指向所存放在堆中的對象。
而int,double,float等原始數據類型則存放在棧中。這是由於分配棧的速度比分配堆的速度快的多,而這些數據經常使用。
Java棧與堆
----對這兩個概念的不明好久,終於找到一篇好文,拿來共享
1. 棧(stack)與堆(heap)都是Java用來在Ram中存放數據的地方。與C++不同,Java自動管理棧和堆,程序員不能直接地設置棧或堆。
2. 棧的優勢是,存取速度比堆要快,僅次於直接位於CPU中的寄存器。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。另外,棧數據可以共 享,詳見第3點。堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由於要 在運行時動態分配內存,存取速度較慢。