1. 程式人生 > >關於字串的幾個常見問題

關於字串的幾個常見問題

Java程式碼

String s = new String(“abc”);
String s1 = “abc”;
String s2 = new String(“abc”);
System.out.println(s == s1);
System.out.println(s == s2);
System.out.println(s1 == s2);
String s = new String(“abc”);

請問以上程式執行結果是什麼?

第一句執行後記憶體中有兩個物件,而不是一個。一個由new String(“abc”)中的”abc”在String Pool裡生成一個值為”abc”的物件;第二個由new在堆裡產生一個值為”abc”的物件,該物件完全是String Pool裡的”abc”的一個拷貝。變數s最後指向堆中產生的”abc”物件;
第二句執行時,s1先去String Pool找是否有值為”abc”的物件,很顯然在上一步中java已經在String Pool裡生成一個”abc”物件了,所以s1直接指向String Pool中的這個”abc”;
第三句中又有一個new,在java中凡遇到new時,都會在堆裡產生一個新的物件。因此,該句執行後堆裡又多了一個”abc”物件,這與執行第一句後生成的”abc”是不同的兩個物件,s2最後指向這個新生成的物件。
因此,執行後面的列印語句的結果是三個false

問題2:

Java程式碼

System.out.println(s == s.intern());
System.out.println(s1 == s1.intern());
System.out.println(s1.intern() == s2.intern());
System.out.println(s == s.intern());

System.out.println(s1 == s1.intern());

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

請問以上程式執行結果是什麼?

設s為String型別的變數,當執行s.intern()時,java先在String Pool裡找與字串變數s相等(用equals()方法)的字串,若有則將其引用返回;若沒有則在String Pool裡建立一個與s的值相等的字串物件,並將其引用返回。從中我們可以總結出intern()方法無論如何都將返回String Pool裡的字串物件的引用。
因此,以上程式執行的結果是false,true,true。
PS:設s和t為兩個字串變數,若有s.equals(t),必有s.intern() == t.intern();
PS:”==”永遠比較的是兩邊物件的地址是否相等。

問題3:

Java程式碼

String hello = “hello”;
String hel = “hel”;
String lo = “lo”;
System.out.println(hello == “hel” + “lo”);
System.out.println(hello == “hel” + lo);

請問以上程式執行結果是什麼?

前三句在String Pool裡分別產生“hello”、“hel”、“lo”三個常量字串物件
當做第一個加法連線時,+號兩邊都是常量字串,java就會將兩者拼起來後到String Pool裡找與之相等(用equals)的字串,若存在則將其地址返回;不存在則在String Pool裡新建一個常量物件,其值等於拼接後的字串,並將其地址返回。
第二個+號兩邊有一個是變數,此時,java會在堆裡新建一個物件,其值是兩字串拼接後的值,此時返回的地址是堆中新物件的地址。
所以,第一句做+連線後返回String Pool中“hello”的地址,顯然與變數hello的地址相等;
第二句返回的是堆中地址,顯然與變數hello的地址不等;

Java的String,StringBuffer,StringBuilder有什麼區別?
那就是:String是不可變類(immutable),每次在String物件上的操作都會生成一個新的物件;StringBuffer和StringBuilder則允許在原來物件上進行操作,而不用每次增加物件;StringBuffer是執行緒安全的,但效率較低,而StringBuilder則不是效率最高。
這個答案我是很早都知道的,而且實際應用中也是這樣做的,經常變化的時候用StringBuilder或者StringBuffer。但是為什麼是這樣的是最近才曉得的,而瞭解的方法非常簡單,就是閱讀jdk的原始碼:
String和StringBuffer,StringBuilder都是用字元陣列來表示的。但是在String中這個字元陣列是這樣定義的:

/* The value is used for character storage. /
private final char value[];

而在StringBuffer和StringBuilder中,這個字元陣列都是繼承於java.lang.AbstractStringBuilder中的

/**
* The value is used for character storage.
*/
char value[];
這樣答案就很明顯了,原因就在這個final關鍵字上。
而同時通過原始碼可以發現StringBuffer的很多方法和屬性都有synchronized關鍵字修飾,而StringBuilder則沒有。

String str=new String(“abc”); 緊接著這段程式碼之後的往往是這個問題,那就是這行程式碼究竟建立了幾個String物件呢?

相信大家對這道題並不陌生,答案也是眾所周知的,2個。

接下來我們就從這道題展開,一起回顧一下與建立String物件相關的一些JAVA知識。

我們可以把上面這行程式碼分成String str、=、”abc”和new String()四部分來看待。String str只是定義了一個名為str的String型別的變數,因此它並沒有建立物件;=是對變數str進行初始化,將某個物件的引用(或者叫控制代碼)賦值給它,顯然也沒有建立物件;現在只剩下new String(“abc”)了。那麼,new String(“abc”)為什麼又能被看成”abc”和new String()呢?

我們來看一下被我們呼叫了的String的構造器:

public String(String original) { //other code … } 大家都知道,我們常用的建立一個類的例項(物件)的方法有以下兩種:

一、使用new建立物件。

二、呼叫Class類的newInstance方法,利用反射機制建立物件。

我們正是使用new呼叫了String類的上面那個構造器方法建立了一個物件,並將它的引用賦值給了str變數。同時我們注意到,被呼叫的構造器方法接受的引數也是一個String物件,這個物件正是”abc”。由此我們又要引入另外一種建立String物件的方式的討論——引號內包含文字。

這種方式是String特有的,並且它與new的方式存在很大區別。

String str=”abc”;

毫無疑問,這行程式碼建立了一個String物件。

String a=”abc”; String b=”abc”; 那這裡呢?

答案還是一個。

String a=”ab”+”cd”; 再看看這裡呢?

答案是三個。
說到這裡,我們就需要引入對字串池相關知識的回顧了。

在JAVA虛擬機器(JVM)中存在著一個字串池,其中儲存著很多String物件,並且可以被共享使用,因此它提高了效率。由於String類是final的,它的值一經建立就不可改變,因此我們不用擔心String物件共享而帶來程式的混亂。字串池由String類維護,我們可以呼叫intern()方法來訪問字串池。

我們再回頭看看String a=”abc”;,這行程式碼被執行的時候,JAVA虛擬機器首先在字串池中查詢是否已經存在了值為”abc”的這麼一個物件,它的判斷依據是String類equals(Object obj)方法的返回值。如果有,則不再建立新的物件,直接返回已存在物件的引用;如果沒有,則先建立這個物件,然後把它加入到字串池中,再將它的引用返回。因此,我們不難理解前面三個例子中頭兩個例子為什麼是這個答案了。

只有使用引號包含文字的方式建立的String物件之間使用“+”連線產生的新物件才會被加入字串池中。對於所有包含new方式新建物件(包括null)的“+”連線表示式,它所產生的新物件都不會被加入字串池中,對此我們不再贅述。因此我們提倡大家用引號包含文字的方式來建立String物件以提高效率,實際上這也是我們在程式設計中常採用的。

棧(stack):主要儲存基本型別(或者叫內建型別)(char、byte、short、int、long、float、double、boolean)和物件的引用,資料可以共享,速度僅次於暫存器(register),快於堆。

堆(heap):用於儲存物件