java中有幾種不同的常量池,以下的內容是對java中幾種常量池的介紹以及重點研究一下字符串常量池。
class常量池
我們寫的每一個Java類被編譯后,就會形成一份class文件;class文件中除了包含類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池(constant pool table),用于存放編譯器生成的各種 字面量 (Literal)和 符號引用 (Symbolic References),每個class文件都有一個class常量池。
其中 字面量 包括:1.文本字符串 2.八種基本類型的值 3.被聲明為final的常量等; 符號引用 包括:1.類和方法的全限定名 2.字段的名稱和描述符 3.方法的名稱和描述符。
運行時常量池
運行時常量池存在于內存中,也就是class常量池被加載到內存之后的版本,是方法區的一部分。不同之處是:它的字面量可以動態的添加(String類的intern()),符號引用可以被解析為直接引用。
JVM在執行某個類的時候,必須經過加載、連接、初始化,而連接又包括驗證、準備、解析三個階段。而當類加載到內存中后,jvm就會將class常量池中的內容存放到運行時常量池中,由此可知,運行時常量池也是每個類都有一個。在解析階段,會把符號引用替換為直接引用,解析的過程會去查詢字符串常量池,也就是我們下面要說的StringTable,以保證運行時常量池所引用的字符串與字符串常量池中是一致的。
字符串常量池
在JDK6.0及之前版本,字符串常量池存放在方法區中在JDK7.0版本以后,字符串常量池被移到了堆中了。至于為什么移到堆內,大概是由于方法區的內存空間太小了。
在HotSpot VM里實現的string pool功能的是一個StringTable類,它是一個Hash表,默認值大小長度是1009;這個StringTable在每個HotSpot VM的實例只有一份,被所有的類共享。字符串常量由一個一個字符組成,放在了StringTable上。
在JDK6.0中,StringTable的長度是固定的,長度就是1009,因此如果放入String Pool中的String非常多,就會造成hash沖突,導致鏈表過長,當調用String#intern()時會需要到鏈表上一個一個找,從而導致性能大幅度下降;在JDK7.0中,StringTable的長度可以通過參數指定。
下面看一下實例:
String s = new String("abc")
這條語句創建了幾個對象?
答案:共2個。第一個對象是”abc”字符串存儲在常量池中,第二個對象在JAVA Heap中的 String 對象。這里不要混淆了s是放在棧里面的指向了Heap堆中的String對象。
比較下列兩種創建字符串的方法:
String str1 = new String("abc");
String str2 = "abc";
答案:第一種是用new()來新建對象的,它會在存放于堆中。每調用一次就會創建一個新的對象。 運行時期創建 。
第二種是先在棧中創建一個對String類的對象引用變量str2,然后通過符號引用去字符串常量池里找有沒有”abc”,如果沒有,則將”abc”存放進字符串常量池,并令str2指向”abc”,如果已經有”abc” 則直接令str2指向“abc”?!癮bc”存于常量池在 編譯期間完成 。
String s1 = new String("s1") ;
String s1 = new String("s1") ;
上面一共創建了幾個對象?
答案:答案:3個 ,編譯期Constant Pool中創建1個,運行期heap中創建2個.(用new創建的每new一次就在堆上創建一個對象,用引號創建的如果在常量池中已有就直接指向,不用創建)
比較字符串的‘==’和‘equals()’區別?
答案:
‘==’ 比較的是變量(棧)內存中存放的對象的(堆)內存地址,用來判斷兩個對象的地址是否相同,即是否是指相同一個對象。比較的是真正意義上的指針操作。注意:
1、比較的是操作符兩端的操作數是否是同一個對象。
2、兩邊的操作數必須是同一類型的(可以是父子類之間)才能編譯通過。
3、引用類型比較的是地址(即是否指向同一個對象),基本數據類型比較的是值,值相等則為true,如:int a=10 與 long b=10L 與 double c=10.0都是相同的(為true),因為他們都指向地址為10的堆。
‘equals()’用來比較的是兩個對象是否相等,由于所有的類都是繼承自java.lang.Object類的,在Object中的基類中定義了一個equals的方法,這個方法的初始行為是比較對象的內存地址,但String類中重寫了equals方法, 比較的是字符串的內容 ,而不再是比較類在堆內存中的存放地址了。
總結:在沒有重寫equals方法的情況下,他們之間的比較還是基于他們在內存中的存放位置的地址值的,因為Object的equals方法也是用雙等號(==)進行比較的,所以比較后的結果跟雙等號(==)的結果相同。String類中重寫了equals方法,變成了字符串內容的比較。
String s1 = "sss111";
String s2 = "sss111";
System.out.println(s1 == s2); //結果為true
String s1 = new String("sss111");
String s2 = "sss111";
System.out.println(s1 == s2); //結果為false
String s0 = "111"; //pool
String s1 = new String("111"); //heap
final String s2 = "111"; //pool
String s3 = "sss111"; //pool
String s4 = "sss" + "111"; //pool
String s5 = "sss" + s0; //heap
String s6 = "sss" + s1; //heap
String s7 = "sss" + s2; //pool
String s8 = "sss" + s0; //heap
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //false
System.out.println(s3 == s6); //false
System.out.println(s3 == s7); //true
System.out.println(s5 == s6); //false
System.out.println(s5 == s8); //false
結合上面分析,總結如下:向大家推薦一個架構學習交流裙。交流學習裙號:687810532,里面會分享一些資深架構師錄制的視頻錄像
1.單獨使用””引號創建的字符串都是常量,編譯期就已經確定存儲到Constant Pool中;
2.使用new String(“”)創建的對象會存儲到heap中,是運行期新創建的;
3.使用只包含常量的字符串連接符如”aa” + “aa”創建的也是常量,編譯期就能確定,已經確定存儲到String Pool中,String pool中存有“aaaa”;但不會存有“aa”。
4.使用包含變量的字符串連接符如”aa” + s1創建的對象是運行期才創建的,存儲在heap中;只要s1是變量,不論s1指向池中的字符串對象還是堆中的字符串對象,運行期s1 + “aa”操作實際上是編譯器創建了StringBuilder對象進行了append操作后通過toString()返回了一個字符串對象存在heap上。
5.String s2 = “aa” + s1; String s3 = “aa” + s1; 這種情況,雖然s2,s3都是指向了使用包含變量的字符串連接符如”aa” + s1創建的存在堆上的對象,并且都是s1 + “aa”。但是卻指向兩個不同的對象,兩行代碼實際上在堆上new出了兩個StringBuilder對象來進行append操作。在Thinking in java一書中285頁的例子也可以說明。
6.對于final String s2 = “111”。s2是一個用final修飾的變量,在編譯期已知,在運行s2+”aa”時直接用常量“111”來代替s2。所以s2+”aa”等效于“111”+ “aa”。在編譯期就已經生成的字符串對象“111aa”存放在常量池中。