在平時咱們使用字符串通常就是拿來直接搞起,不多有深刻的去想過這方面的知識,致使別人在考咱們的時候,會問 String str = new String("123"); 這個一行代碼執行建立了幾個對象, String str1= str + new String("456");這行代碼中str1存儲在內存的哪一個位置,堆or 字符串常量區(方法區)? 會把咱們問的啞口無言了;哈哈哈哈,其實也不是水平問題,是咱們平時能夠仔細的去總結該類問題,下面就詳細的對這類問題進行總結;java
問題1:安全
String str1 = new String("1"); str1.intern(); String str2 = "1"; System.out.println(str1 == str2); //結果是 false or true? String str3 = new String("2") + new String("2"); t3.intern(); String str4 = "22"; System.out.println(str3 == str4); //結果是 false or true?
問題2:app
String str1 = "aaa"; String str2 = "bbb"; String str3 = "aaabbb"; String str4 = str1 + str2; String str5 = "aaa" + "bbb"; System.out.println(str3 == str4); // false or true System.out.println(str3 == str4.intern()); // true or false System.out.println(str3 == str5);// true or false
問題3:jvm
String t1 = new String("2"); String t2 = "2"; t1.intern(); System.out.println(t1 == t2); //false or true String t3 = new String("2") + new String("2"); String t4 = "22"; t3.intern(); System.out.println(t3 == t4); //false or true
問題4:函數
Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; System.out.println(c == d); System.out.Println(e == f); System.out.println(c == (a + b)); System.out.println(c.equals(a+b)); System.out.println(g == (a + b)); System.out.println(g.equals(a + b));
在解答這四個問題的過程當中,咱們首先說一下幾個知識,很重要:優化
intern函數的做用是將對應的符號常量進入特殊處理,在1.6之前 和 1.7之後有不一樣的處理;spa
先看1.6:線程
在1.6中,intern的處理是 先判斷字符串常量是否在字符串常量池中,若是存在直接返回該常量,若是沒有找到,則將該字符串常量加入到字符串常量區,也就是在字符串常量區創建該常量;code
在1.7中:對象
在1.7中,intern的處理是 先判斷字符串常量是否在字符串常量池中,若是存在直接返回該常量,若是沒有找到,說明該字符串常量在堆中,則處理是把堆區該對象的引用加入到字符串常量池中,之後別人拿到的是該字符串常量的引用,實際存在堆中;【這裏感謝覺得網友的糾正,一開始理解爲在堆區創建該字符串對象在添加引用了,其實調用該方法的字符串對象要麼在堆區要麼在常量池中的】
在Class文件中除了有類的版本【高版本能夠加載低版本】、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table)【此時沒有加載進內存,也就是在文件中】,用於存放編譯期生成的各類字面量和符號引用。
下面對字面量和符號引用進行說明
字面量
字面量相似與咱們日常說的常量,主要包括:
符號引用
主要包括如下常量:
咱們知道類加載器會加載對應的Class文件,而上面的class文件中的常量池,會在類加載後進入方法區中的運行時常量池【此時存在在內存中】。而且須要的注意的是,運行時常量池是全局共享的,多個類共用一個運行時常量池。而且class文件中常量池多個相同的字符串在運行時常量池只會存在一份。
注意運行時常量池存在於方法區中。
看名字咱們就能夠知道字符串常量池會用來存放字符串,也就是說常量池中的文本字符串會在類加載時進入字符串常量池。
那字符串常量池和運行時常量池是什麼關係呢?上面咱們說常量池中的字面量會在類加載後進入運行時常量池,其中字面量中有包括文本字符串,顯然從這段文字咱們能夠知道字符串常量池存在於運行時常量池中。也就存在於方法區中。
不過在周志明那本深刻java虛擬機中有說到,到了JDK1.7時,字符串常量池就被移出了方法區,轉移到了堆裏了。
那麼咱們能夠推斷,到了JDK1.7以及以後的版本中,運行時常量池並無包含字符串常量池,運行時常量池存在於方法區中,而字符串常量池存在於堆中。
tring str1 = new String("1");
解析:首先此行代碼建立了兩個對象,在執行前會在常量池中建立一個"1"的對象,而後執行該行代碼時new一個"1"的對象存放在堆區中;而後str1指向堆區中的對象;
str1.intern();
解析:該行代碼首先查看"1"字符串有沒有存在在常量池中,此時存在則直接返回該常量,這裏返回後沒有引用接受他,【假如不存在的話在 jdk1.6中會在常量池中創建該常量,在jdk1.7之後會把堆中該對象的引用放在常量池中】
String str2 = "1";
解析:此時"1"已經存在在常量池中,str2指向常量池中的對象;
System.out.println(str1 == str2); //結果是 false or true?
解析:str1指向堆區的對象,str2指向常量池中的對象,兩個引用指向的地址不一樣,輸入false; String str3 = new String("2") + new String("2");
解析:此行代碼執行的底層執行過程是 首先使用StringBuffer的append方法將"2"和"2"拼接在一塊,而後調用toString方法new出「22」;因此此時的「22」字符串是建立在堆區的;
t3.intern();
解析:此行代碼執行時字符串常量池中沒有"22",因此此時在jdk1.6中會在字符串常量池中建立"22",而在jdk1.7之後會把堆中該對象的引用放在常量池中;
String str4 = "22";
解析:此時的str4在jdk1.6中會指向方法區,而在jdk1,7中會指向堆區;
System.out.println(str3 == str4); //結果是 false or true?
解析:很明顯了 jdk1.6中爲false 在jdk1.7中爲true;
String str1 = "aaa";
解析:str1指向方法區;
String str2 = "bbb";
解析: str2 指向方法區
String str3 = "aaabbb";
解析:str3指向方法區
String str4 = str1 + str2;
解析:此行代碼上邊已經說過原理。str4指向堆區
String str5 = "aaa" + "bbb";
解析:該行代碼重點說明一下,jvm對其有優化處理,也就是在編譯階段就會將這兩個字符串常量進行拼接,也就是"aaabbb";因此他是在方法區中的;’
System.out.println(str3 == str4); // false or true
解析:很明顯 爲false, 一個指向堆 一個指向方法區
System.out.println(str3 == str4.intern()); // true or false
解析:jdk1.6中str4.intern會把「aaabbb」放在方法區,1.7後在堆區,因此在1.6中會是true 可是在1.7中是false
System.out.println(str3 == str5);// true or false
解析:都指向字符串常量區,字符串長常量區在方法區,相同的字符串只存在一份,其實這個地方在擴展一下,由於方法區的字符串常量是共享的,在兩個線程同時共享這個字符串時,若是一個線程改變他會是怎麼樣的呢,其實這種場景下是線程安全的,jvm會將改變後的字符串常量在
字符串常量池中從新建立一個處理,能夠保證線程安全
tring t1 = new String("2");
解析:建立了兩個對象,t1指向堆區
String t2 = "2";
解析:t2指向字符串常量池
t1.intern();
解析:字符串常量池已經存在該字符串,直接返回;
System.out.println(t1 == t2); //false or true 解析:很明顯 false
String t3 = new String("2") + new String("2");
解析:過程同問題1 t3指向堆區
String t4 = "22";
解析:t4 在1.6 和 1.7中指向不一樣 t3.intern();
解析: 字符串常量池中已經存在該字符串 直接返回
System.out.println(t3 == t4); //false or true
解析: 很明顯爲 false 指向不一樣的內存區
這個地方存在一個知識點。多是個盲區,此次要完全記住「
(1). 內存中有一個java基本類型封裝類的常量池。這些類包括Byte, Short, Integer, Long, Character, Boolean。須要注意的是,Float和Double這兩個類並無對應的常量池。
(2).上面5種整型的包裝類的對象是存在範圍限定的;範圍在-128~127存在在常量池,範圍之外則在堆區進行分配。
(3). 在周志明的那本虛擬機中有這樣一句話:包裝類的
「==」運行符在不遇到算術運算的狀況下不會自動拆箱,以及他們的equals()方法不處理數據類型的關係,通俗的講也就是 「==」兩邊若是有算術運算, 那麼自動拆箱和進行數據類型轉換處理,比較的是數值等不等能。
(4).Long的equals方法會先判斷是不是Long類型。
(5).不管是Integer仍是Long,他們的equals方法比較的是數值。
System.out.println(c == d)。
解析:因爲常量池的做用,c與d指向的是同一個對象(注意此時的==比較的是對象,也就是地址,而不是數值)。所以爲true
System.out.println(e == f)。
因爲321超過了127,所以常量池失去了做用,因此e和f數值雖然相同,但不是同一個對象,以此爲false。
System.out.println(c == (a+b))。
此時==兩邊有算術運算,會進行拆箱,所以此時比較的是數值,而並不是對象。所以爲true。
System.out.println(c.equals(a+b))
c與a+b的數值相等,爲true。
System.out.pirnln(g == (a + b))
因爲==兩邊有算術運算,因此比較的是數值,所以爲true。
System.out.println(g.equals(a+b))。Long類型的equal在比較是時候,會先判斷a+b是否爲Long類型,顯然a+b不是,所以false