1. 程式人生 > >【JAVA】java資料儲存-基礎型別,物件和字串

【JAVA】java資料儲存-基礎型別,物件和字串

總所周知,java是一種面向物件的程式設計語言。在java裡,一切都被視為物件,因此可以採用單一固定的語法。儘管一切都看作物件,但是操縱的識別符號實際上是物件的一個“引用”,這個引用指向資料實際存放的地址。

而我們的資料被儲存到什麼地方呢?特別是記憶體是怎麼分配的呢?我們有個不同的地方可以儲存資料(以下內容摘自《java程式設計思想》:

1. 暫存器。這裡是最快的儲存區,因為它位於不同於其他儲存區的地方——處理器內部。但是暫存器的數量是極其有限的,所以暫存器需要根據需求進行分配。我們不能直接控制,也不能在程式中感受到暫存器的任何存在跡象。

2. 堆疊。位於通用RAM(隨機訪問儲存器)中,但通過堆疊指標可以從處理器那裡直接獲得支援。堆疊指標若鄉下移動,則分配新的記憶體,若向上移動則釋放哪些記憶體。這是一種快速有效的分配儲存方法,僅次於暫存器。建立程式時,java系統必須知道在堆疊內所有項的確切的生命週期,以便上下移動堆疊指標。這一約束限制了程式的靈活性,所以雖然某些java資料儲存於堆疊中,特別是物件引用,但是java物件並不儲存其中。

3. 堆。一種通用的記憶體池(也位於RAM區),用於存放所有的java物件。堆不同於堆疊的好處是,編譯器不需要知道儲存的資料在堆裡存活多長時間。因此,在堆裡分配儲存會有很大的靈活性。當需要一個物件時,只需用new寫一行簡單的程式碼。當執行這行程式碼時,會自動在堆裡進行儲存分配。當然為這種靈活性必須付出相應代價:用堆進行儲存分配和清理可能會比用堆疊進行儲存分配需要更多的時間。

4. 常量儲存。常量值通常直接存放在程式程式碼內部,這樣做是安全的,因為它們永遠不會被改變。有時,在嵌入式系統中,常量本身會和其他部分分隔開,所以這種情況下,可以選擇將其存放在ROM(只讀儲存器)中。

5. 非RAM儲存器。如果資料完全存活於程式之外,那麼它可以不受程式的任何控制,在程式沒有執行時也可以存在。其中兩個基本的例子是流物件和持久化物件。在流物件中,物件轉化成位元組流,通常被髮送給另外一臺機器。在“持久化物件”中,物件唄存放於磁碟上,因此即使程式終止,它們仍然可以保持自己的狀態。這種儲存方式的技巧在於:把物件轉化成可以存放的在其他媒介上的事務,在需要時,可恢復成常規的、基於RAM的物件。java提供了對輕量級持久化的支援。

瞭解了資料存放的地方,我們來看看各個型別的資料都存放在哪裡:

1. 基本型別。

java的基本型別有boolean, char, byte,short,int,long,float,double,void。

一般情況下,我們從上面的描述裡已經可以瞭解到,對於java的物件來說,我們在堆疊中存放的實際上是物件的引用,而物件本體(也就是值)是被存放在堆裡的,我們通過new(),在堆裡儲存一個物件,然後把這個物件的引用放到堆疊中。

但是對於基本型別來說,他們是需要特殊對待的,因為他們是在是太小了,太簡單,這樣的變數存在堆裡,往往不是特別有效。因此對於這些型別來說,java採取和C與C++相同的方法,也就是不用new來建立變數,而是建立一個並非是引用的“自動”變數。這個變數直接儲存“值”,並放置在堆疊中,而不是堆中,這樣會更加高效。

但是這種情況僅當數值型是在128以下的時候,因為128以上的資料就會被存放到堆中。

如下示例:


		Integer num1 = 127;
		Integer num2 = 127;
		Integer num3 = 128;
		Integer num4 = 128;

		System.out.println(num1 == num2);
		System.out.println(num3 == num4);
		

返回值為:

true

false

因為128以下的數的值會直接存放在堆疊裡,這樣我們對比堆疊裡存放的值的時候,自然是相等的。

到了128,8位二進位制無法儲存,那麼這個值會被存放到堆中。堆中資料會對應不同的地址,在堆疊中存放的引用不同,自然也就不相等。

2. 物件。

我們從在上面已經說過了,物件在通過new 建立的時候,是在堆中建立了一個物件,並把這個物件的應用存放在堆疊中,我們使用的識別符號實際上是堆疊中的這個引用,也就是,當我們用一個物件a給另一個物件賦值b的時候,是將儲存在堆疊中的引用賦值給了物件b,當我們操作物件b的值的時候,實際上是在操作這個引用對應的堆中儲存的值。因為a和b的堆疊中儲存的實際上是一個引用,所以a和b的值都發生了變化,如下:


public class StringTest {
	
	public class Person {
		private String name;
		public Person(String name) {
			this.name = name;
		}
		
		public String getName() {
			return name;
		}

		
		public void setName(String name) {
			this.name = name;
		}
	}

	public static void main(String[] args) {
		Person a = new StringTest().new Person("a");
		Person b = a;
//		b = new StringTest().new Person("b");
		b.setName("b");
		System.out.println("a name is "+a.getName());
		System.out.println("b name is "+b.getName());
		

	}

}

這塊程式碼的結果是:

a name is b

b name is b

a物件和b物件中name的值同時發生了變化,因為a和b雖在堆疊中是兩個不同的變數,但是他們儲存的引用是指向同一個堆資料的。

但是如果是這樣對b做操作,結果就完全不同了:


public class StringTest {
	
	public class Person {
		private String name;
		public Person(String name) {
			this.name = name;
		}
		
		public String getName() {
			return name;
		}

		
		public void setName(String name) {
			this.name = name;
		}
	}

	public static void main(String[] args) {
		Person a = new StringTest().new Person("a");
		Person b = a;
		b = new StringTest().new Person("b");
//		b.setName("b");
		System.out.println("a name is "+a.getName());
		System.out.println("b name is "+b.getName());
		

	}

}

使用new來對b賦值,就等於是在堆中新開了一塊儲存空間來存放這個新的物件,並把這個新物件的引用賦值給b在堆疊中的記錄,但是a還是用的之前的物件的引用,此時b和a指向的不是同一個物件了,所以結果自然是:

a name is a

b name is b

b是b,a是a。

3. String 型。

我單獨把String型拿出來說,是因為我認為String型是一個特別的型別,它既不像基本型別那樣直接把值存在堆疊裡,又不像物件那樣可以對堆中的值進行修改。

我們要明確一點,對String的操作永遠都是生成一個新的String,而不是對它進行修改。

而對String賦值其實有兩種方法,一種是直接等於賦值一個值,另一種是new一個String物件,兩種方式是不同的。

按我的理解,第一種直接賦值一個值這個是先會查詢堆中是否存放了相同的值,如果有,則把對應的引用賦值給這個String在堆疊中的物件,如果沒有則新開闢一個空間存放該值,並把引用傳遞給變數。也就是如果連續賦值兩個變數,那麼他們的引用實際上是同一個。同樣的String 物件賦值成同一個感覺是java對String 的特殊處理,上面提到的Integer就沒有這種功能。

第二種,就是不會排查堆中是否有相同的值,直接開闢一個新空間存放對應的值。也就是連續new兩次相同的字串,實際上他們的引用並不是同一個。

證實程式碼如下:

package com.example.demo;

public class StringTest {
	
	public class Person {
		private String name;
		public Person(String name) {
			this.name = name;
		}
		
		public String getName() {
			return name;
		}

		
		public void setName(String name) {
			this.name = name;
		}
	}

	public static void main(String[] args) {
		
		String str1 = "123";
		String str2 = "123";
		String str3 = new String("123");
		String str4 = new String("123");
		System.out.println(str1 == str2);
		System.out.println(str2 == str3);
		System.out.println(str3 == str4);
		
//		

	}

}

返回值是:

true

false

false

理由就如同上面解釋的那樣。

而因為String型是隻能生成新的String型,而不能被修改的,所以我們每次對String進行修改,就是更換String識別符號對應的引用。

所以我們對String型進行傳遞的時候從概念上看是引用傳遞,但是實際效果更接近值傳遞,因為對新的String進行操作的時候,老的String並不會受到影響。如下:


	public static void main(String[] args) {

		String str1 = new String("123");
		String str2 = str1;
		System.out.println(str1 == str2);
		str2 = str2+"hello";
		System.out.println(str1 == str2);

		System.out.println(str1);
		System.out.println(str2);
	}

返回值為:

true

false

123

123hello

str2對應的應用在修改字串後發生了變化,雖然String互相賦值確實賦值的是引用,但是相當於是值傳遞了。