生活随笔
收集整理的這篇文章主要介紹了
java 常量池详解
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
參考資料:http://chenzehe.iteye.com/blog/1727062
注意:
1.首先,我們平時在討論字符串新建問題時所說的常量池其實指的是字符串常量池。并不是運行時常量池,更加不是class編譯時常量池。
例如:當我們通過new新建一個字符串時
String s1 = new String("abc");
JVM先去常量池(指的是字符串常量池)里邊查看是否存在"abc",如果不存在,則在常量池里面新建一個“abc"變量,然后再在堆里面新建一個“abc"變量。如果常量中已經存在"abc",則直接在堆里面新建一個“abc"變量。
2.
常量池(constant_pool)指的是在編譯期被確定,并被保存在已編譯的.class文件中的一些數(shù)據(jù)。它包括了關于類、方法、接口等中的常量,也包括字符串常量和符號引用。運行時常量池是方法區(qū)的一部分。
???? 在Class文件結構中,最頭的4個字節(jié)用于存儲魔數(shù)Magic Number,用于確定一個文件是否能被JVM接受,再接著4個字節(jié)用于存儲版本號,前2個字節(jié)存儲次版本號,后2個存儲主版本號,再接著是用于存放常量的常量池,由于常量的數(shù)量是不固定的,所以常量池的入口放置一個U2類型的數(shù)據(jù)(constant_pool_count)存儲常量池容量計數(shù)值。常量池主要用于存放兩大類常量:字面量(Literal)和符號引用量(Symbolic References),字面量相當于Java語言層面常量的概念,如文本字符串,聲明為final的常量值等,符號引用則屬于編譯原理方面的概念,包括了如下三種類型的常量:
???? 類和接口的全限定名
???? 字段名稱和描述符
???? 方法名稱和描述符
?
???? Java中八種基本類型的包裝類的大部分都實現(xiàn)了常量池技術,它們是Byte、Short、Integer、Long、Character、Boolean,另外兩種浮點數(shù)類型的包裝類(Float、Double)則沒有實現(xiàn)。另外Byte,Short,Integer,Long,Character這5種整型的包裝類也只是在對應值在-128到127時才可使用對象池。
?
???? 看下面代碼:
Java代碼 ?
????public?class?Test?{??????public?static?void?main(String[]?args)?throws?Exception?{??????????Integer?a?=?127;??????????Integer?b?=?127;??????????Integer?c?=?128;??????????Integer?d?=?128;??????????System.out.println(a?==?b);????????System.out.println(c?==?d);????}??}?? ????? 使用javap查看生成的字節(jié)碼:
Java代碼 ?
E:\chenzehe\workspace_b2b_3th\test\src>javap?-verbose?Test??Compiled?from?"Test.java"??public?class?Test?extends?java.lang.Object????SourceFile:?"Test.java"????minor?version:?0????major?version:?50????Constant?pool:??const?#1?=?Method???????#6.#21;?const?#2?=?Method???????#22.#23;????????const?#3?=?Field????????#24.#25;????????const?#4?=?Method???????#26.#27;????????const?#5?=?class????????#28;????const?#6?=?class????????#29;????const?#7?=?Asciz????????<init>;??const?#8?=?Asciz????????()V;??const?#9?=?Asciz????????Code;??const?#10?=?Asciz???????LineNumberTable;??const?#11?=?Asciz???????main;??const?#12?=?Asciz???????([Ljava/lang/String;)V;??const?#13?=?Asciz???????StackMapTable;??const?#14?=?class???????#30;????const?#15?=?class???????#31;????const?#16?=?class???????#32;????const?#17?=?Asciz???????Exceptions;??const?#18?=?class???????#33;????const?#19?=?Asciz???????SourceFile;??const?#20?=?Asciz???????Test.java;??const?#21?=?NameAndType?#7:#8;const?#22?=?class???????#31;????const?#23?=?NameAndType?#34:#35;const?#24?=?class???????#36;????const?#25?=?NameAndType?#37:#38;const?#26?=?class???????#32;????const?#27?=?NameAndType?#39:#40;const?#28?=?Asciz???????Test;??const?#29?=?Asciz???????java/lang/Object;??const?#30?=?Asciz???????[Ljava/lang/String;;??const?#31?=?Asciz???????java/lang/Integer;??const?#32?=?Asciz???????java/io/PrintStream;??const?#33?=?Asciz???????java/lang/Exception;??const?#34?=?Asciz???????valueOf;??const?#35?=?Asciz???????(I)Ljava/lang/Integer;;??const?#36?=?Asciz???????java/lang/System;??const?#37?=?Asciz???????out;??const?#38?=?Asciz???????Ljava/io/PrintStream;;??const?#39?=?Asciz???????println;??const?#40?=?Asciz???????(Z)V;????{??public?Test();????Code:?????Stack=1,?Locals=1,?Args_size=1?????0:???aload_0?????1:???invokespecial???#1;????4:???return????LineNumberTable:?????line?18:?0??????public?static?void?main(java.lang.String[])???throws?java.lang.Exception;????Code:?????Stack=3,?Locals=5,?Args_size=1?????0:???bipush??127?????2:???invokestatic????#2;????5:???astore_1?????6:???bipush??127?????8:???invokestatic????#2;????11:??astore_2?????12:??sipush??128?????15:??invokestatic????#2;????18:??astore_3?????19:??sipush??128?????22:??invokestatic????#2;????25:??astore??4?????27:??getstatic???????#3;????30:??aload_1?????31:??aload_2?????32:??if_acmpne???????39?????35:??iconst_1?????36:??goto????40?????39:??iconst_0?????40:??invokevirtual???#4;????43:??getstatic???????#3;????46:??aload_3?????47:??aload???4?????49:??if_acmpne???????56?????52:??iconst_1?????53:??goto????57?????56:??iconst_0?????57:??invokevirtual???#4;????60:??return????LineNumberTable:?????line?20:?0?????line?21:?6?????line?22:?12?????line?23:?19?????line?24:?27?????line?25:?43?????line?26:?60??????StackMapTable:?number_of_entries?=?4?????frame_type?=?255??????offset_delta?=?39???????locals?=?[?class?"[Ljava/lang/String;",?class?java/lang/Integer,?class?java/lang/Integer,?class?java/lang/Inte??ger,?class?java/lang/Integer?]???????stack?=?[?class?java/io/PrintStream?]?????frame_type?=?255??????offset_delta?=?0???????locals?=?[?class?"[Ljava/lang/String;",?class?java/lang/Integer,?class?java/lang/Integer,?class?java/lang/Inte??ger,?class?java/lang/Integer?]???????stack?=?[?class?java/io/PrintStream,?int?]?????frame_type?=?79??????stack?=?[?class?java/io/PrintStream?]?????frame_type?=?255??????offset_delta?=?0???????locals?=?[?class?"[Ljava/lang/String;",?class?java/lang/Integer,?class?java/lang/Integer,?class?java/lang/Inte??ger,?class?java/lang/Integer?]???????stack?=?[?class?java/io/PrintStream,?int?]??????Exceptions:?????throws?java.lang.Exception??}?? ?Integer a = 127;對應的指令為:
Java代碼 ?
0:???bipush??127??2:???invokestatic????#2;? Integer c = 128;對應的指令為:
Java代碼 ?
12:??sipush??128??15:??invokestatic????#2;? ???? bipush指令意思是將單字節(jié)的常量值(-128~127)推送到棧頂
???? sipush指令意思是將短型的常量值(-32768~32767)推送到棧頂
???? invokestatic指令意思是調用靜態(tài)方法,這里調用的是常量池中#2指向的方法java/lang/Integer.valueOf,查看Integer.valueOf方法:
Java代碼 ?
public?static?Integer?valueOf(int?i)?{??????final?int?offset?=?128;??????if?(i?>=?-128?&&?i?<=?127)?{?????????return?IntegerCache.cache[i?+?offset];??????}??????return?new?Integer(i);??}?? ?IntegerCache代碼:
Java代碼 ?
???private?static?class?IntegerCache?{??private?IntegerCache(){}????static?final?Integer?cache[]?=?new?Integer[-(-128)?+?127?+?1];????static?{??????for(int?i?=?0;?i?<?cache.length;?i++)??????cache[i]?=?new?Integer(i?-?128);??}?????}?? ???? 其它封裝類如下:
Java代碼 ?
??Boolean?bool1=true;????Boolean?bool2=true;????System.out.println(bool1==bool2);?????Double?d1=1.0;????Double?d2=1.0;????System.out.println(d1==d2);? ?
?
???? String?s?=? new ?String( "xyz" );? 在運行時涉及 幾個String實例?
???? 兩個,一個是字符串字面量"xyz"所對應的、駐留(intern)在一個全局共享的字符串常量池中的實例,另一個是通過new String(String)創(chuàng)建并初始化的、內容與"xyz"相同的實例。
?
???? String中的final用法和理解:
???? final只對引用的"值"(即內存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會導致編譯期錯誤。至于它所指向的對象的變化,final是不負責的。
Java代碼 ?
final?StringBuffer?a?=?new?StringBuffer("111");??final?StringBuffer?b?=?new?StringBuffer("222");??a=b;??final?StringBuffer?a?=?new?StringBuffer("111");??a.append("222"); ?
Java代碼 ?
String?a?=?"a1";??String?b?=?"a"?+?1;??System.out.println((a?==?b));?String?a?=?"atrue";??String?b?=?"a"?+?"true";??System.out.println((a?==?b));?String?a?=?"a3.4";??String?b?=?"a"?+?3.4;??System.out.println((a?==?b));? ??? JVM對于字符串常量的"+"號連接,將程序編譯期,JVM就將常量字符串的"+"連接優(yōu)化為連接后的值,拿"a" + 1來說,經編譯器優(yōu)化后在class中就已經是a1。在編譯期其字符串常量的值就確定下來,故上面程序最終的結果都為true。
?
Java代碼 ?
String?a?=?"ab";??String?bb?=?"b";??String?b?=?"a"?+?bb;??System.out.println((a?==?b));? ?? JVM對于字符串引用,由于在字符串的"+"連接中,有字符串引用存在,而引用的值在程序編譯期是無法確定的,即"a" + bb無法被編譯器優(yōu)化,只有在程序運行期來動態(tài)分配并將連接后的新地址賦給b。所以上面程序的結果也就為false。
?
?
Java代碼 ?
String?a?=?"ab";??final?String?bb?=?"b";??String?b?=?"a"?+?bb;??System.out.println((a?==?b));? ???? 和上面唯一不同的是bb字符串加了final修飾,對于final修飾的變量,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節(jié)碼流中。所以此時的"a" + bb和"a" + "b"效果是一樣的。故上面程序的結果為true。
?
Java代碼 ?
String?a?=?"ab";??final?String?bb?=?getBB();??String?b?=?"a"?+?bb;??System.out.println((a?==?b));???private?static?String?getBB()?{??????return?"b";??}?? ?JVM對于字符串引用bb,它的值在編譯期無法確定,只有在程序運行期調用方法后,將方法的返回值和"a"來動態(tài)連接并分配地址為b,故上面程序的結果為false。
?
通過上面4個例子可以得出得知:
?
Java代碼 ?
String??s??=??"a"?+?"b"?+?"c";???? 就等價于String s = "abc";? int i = 1+2+3;也等價于int i = 6;
?
Java代碼 ?
String??a??=??"a";????String??b??=??"b";????String??c??=??"c";????String??s??=???a??+??b??+??c;????? ?這個就不一樣了,最終結果等于:
Java代碼 ?
StringBuffer?temp?=?new?StringBuffer();????temp.append(a).append(b).append(c);????String?s?=?temp.toString();??? ?由上面的分析結果,可就不難推斷出String 采用連接運算符(+)效率低下原因分析,形如這樣的代碼:
Java代碼 ?
public?class?Test?{??????public?static?void?main(String?args[])?{??????????String?s?=?null;??????????for(int?i?=?0;?i?<?100;?i++)?{??????????????s?+=?"a";??????????}??????}??}??? ???? 每做一次 + 就產生個StringBuilder對象,然后append后就扔掉。下次循環(huán)再到達時重新產生個StringBuilder對象,然后 append 字符串,如此循環(huán)直至結束。 如果我們直接采用 StringBuilder 對象進行 append 的話,我們可以節(jié)省 N - 1 次創(chuàng)建和銷毀對象的時間。所以對于在循環(huán)中要進行字符串連接的應用,一般都是用StringBuffer或StringBulider對象來進行 append操作。
?
String.intern()解析
Java語言并不要求常量一定只能在編譯期產生,運行時也可能將新的常量放入常量池中,這種特性用的最多的就是String.intern()方法。
String的intern()方法就是擴充常量池的一個方法;當一個String實例str調用intern()方法時,Java查找常量池中是否有相同Unicode的字符串常量,如果有,則返回其的引用,如果沒有,則在常量池中增加 一個Unicode等于str的字符串并返回它的引用。
Java代碼 ?
String?s0=?"xyz";??String?s1=new?String("xyz");??String?s2=new?String("xyz");????System.out.println(s0==s1);??s1.intern();??s2=s2.intern();?System.out.println(?s0==s1);??System.out.println(?s0==s1.intern()?);??System.out.println(?s0==s2?);?? ?輸出為:
false
false //雖然執(zhí)行了s1.intern(),但它的返回值沒有賦給s1
true //說明s1.intern()返回的是常量池中”pku”的引用
true
?
有人說,“使用String.intern()方法則可以將一個String類的保存到一個全局String表中,如果具有相同值的Unicode字符串 已經在這個表中,那么該方法返回表中已有字符串的地址,如果在表中沒有相同值的字符串,則將自己的地址注冊到表中“如果我把他說的這個全局的String 表理解為常量池的話,他的最后一句話,“如果在表中沒有相同值的字符串,則將自己的地址注冊到表中”是錯的:
Java代碼 ?
String?s1=new?String("xyz");??String?s2=s1.intern();??System.out.println(?s1==s1.intern()?);??System.out.println(?s1+"?"+s2?);??System.out.println(?s2==s1.intern()?);?? 輸出為:
false
xyz xyz
true
?
?
轉載于:https://www.cnblogs.com/Zchaowu/p/7463806.html
總結
以上是生活随笔為你收集整理的java 常量池详解的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。