作一個積極的人html
編碼、改bug、提高本身java
我有一個樂園,面向編程,春暖花開!面試
首先JDK API的介紹:shell
public final class String extends Object implements Serializable, Comparable<String>, CharSequence
String
類表明字符串。Java 程序中的全部字符串字面值(如 "abc" )都做爲此類的實例實現。編程
字符串是常量;它們的值在建立以後不能更改。字符串緩衝區支持可變的字符串。由於 String 對象是不可變的,因此能夠共享。例如:數組
String str = "abc";
等效於:安全
char data[] = {'a', 'b', 'c'}; String str = new String(data);
從JDK API中能夠看出:bash
從上面的介紹中發現:字符串是常量,它們的值在建立以後不能更改。爲何會這樣呢?要了解其緣由,簡單看一下String類的源碼實現。數據結構
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); // 從新建立一個新的字符串 return new String(buf, true); } public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } // 從新建立一個新的字符串 return new String(buf, true); } } return this; } }
從上面源碼中能夠看出String類實際上是經過char數組來保存字符串的,注意修飾這個char前面的關鍵字 final。final修飾的字段建立之後就不可改變。ide
注意:private final char value[];
這裏雖然value是不可變,也就是說value這個引用地址不可變。可是由於其是數組類型,根據以前學過的內容,value這個引用地址實際上是在棧上分配 ,而其對應的數據結構是在堆上分配保存。那也就是說棧裏的這個value的引用地址不可變。沒有說堆裏array自己數據不可變。看下面這個例子,
final int[] value={1,2,3} int[] another={4,5,6}; value=another; //編譯器報錯,final不可變
value用final修飾,編譯器不容許我把value指向棧區另外一個地址。但若是直接對數組元素進行修改,分分鐘搞定。
final int[] value={1,2,3}; value[2]=100; //這時候數組裏已是{1,2,100}
因此String是不可變的關鍵都在底層的實現,而不是一個final。
也能夠經過上面的concat(String str)
和replace(char oldChar, char newChar)
方法簡單進行了解,全部的操做都不是在原有的value[]數組中進行操做的,而是從新生成了一個新數組buf[]。也就是說進行這些操做後,最原始的字符串並無被改變。
若是面試有問到的話要修改String中value[] 數組的內容,要怎麼作,那麼能夠經過反射進行修改!實際使用中沒有人會去這麼作。
Java中有字符串常量池,用來存儲字符串字面量! 因爲JDK版本的不一樣,常量池的位置也不一樣,根據網上的一些資料:
jdk1.6及如下版本字符串常量池是在永久區中。
jdk1.七、1.8下字符串常量池已經轉移到堆中了。(JDK1.8已經沒有去掉永久區)
由於字符串常量池發生了變化,在String內對intern()進行了一些修改:
jDK1.6版本中執行intern()方法,首先判斷字符串常量池中是否存在該字面量,若是不存在則拷貝一份字面量放入常量池,最後返回字面量的惟一引用。若是發現字符串常量池中已經存在,則直接返回字面量的惟一引用。
jdk1.7之後執行intern()方法,若是字符串常量池中不存在該字面量,則不會再拷貝一份字面量,而是拷貝字面量對應堆中一個引用,而後返回這個引用。
String 類型的常量池比較特殊。它的主要使用方法有兩種:
說明:直接使用new String() 建立出的String對象會直接存儲在堆上
經過一個栗子,看一下上面說的內容:
String str1 = "aflyun"; String str2 = new String("aflyun"); System.out.println(str1 == str2); String str3 = str2.intern(); System.out.println(str1 ==str3);
使用JDK1.8版本運行輸出的結果: false
和 true
。
先上面示例的示意圖:
str1
直接建立在字符串常量池中,str2
使用new關鍵字,對象建立在堆上。因此str1 == str2
爲false。
str3
是str2.intern()
,根據上面的介紹,在jdk1.8首先在常量池中判斷字符串aflyun
是否存在,若是存在的話,直接返回常量池中字符串的引用,也就是str1
的引用。因此str1 ==str3
爲true。
若是你理解了上面的內容,能夠在看一下下面的栗子,運行結果是在JDK1.8環境:
栗子1:
String str1 = "hello"; String str2 = "world"; //常量池中的對象 String str3 = "hello" + "world"; //在堆上建立的新的對象 String str4 = str1 + str2; //常量池中的對象 String str5 = "helloworld"; System.out.println(str3 == str4);//false System.out.println(str3 == str5);//true System.out.println(str4 == str5);//false
栗子2:
//同時會生成堆中的對象以及常量池中hello的對象,此時str1是指向堆中的對象的 String str1 = new String("hello"); // 常量池中的已經存在hello str1.intern(); //常量池中的對象,此時str2是指向常量池中的對象的 String str2 = "hello"; System.out.println(str1 == str2); // false // 此時生成了四個對象 常量池中的"world" + 2個堆中的"world" +s3指向的堆中的對象(注此時常量池不會生成"worldworld") String str3 = new String("world") + new String("world"); //常量池沒有「worldworld」,會直接將str3的地址存儲在常量池內 str3.intern(); // 建立str4的時候,發現字符串常量池已經存在一個指向堆中該字面量的引用,則返回這個引用,而這個引用就是str3 String str4 = "worldworld"; System.out.println(str3 == str4); //true
栗子3:涉及到final關鍵字,能夠試着理解一下
// str1指的是字符串常量池中的 java6 String str1 = "java6"; // str2是 final 修飾的,編譯時候就已經肯定了它的肯定值,編譯期常量 final String str2 = "java"; // str3是指向常量池中 java String str3 = "java"; //str2編譯的時候已經知道是常量,"6"也是常量,因此計算str4的時候,直接至關於使用 str2 的原始值(java)來進行計算. // 則str4 生成的也是一個常量,。str1和str4都對應 常量池中只生成惟一的一個 java6 字符串。 String str4 = str2 + "6"; // 計算 str5 的時候,str3不是final修飾,不會提早知道 str3的值是什麼,只有在運行經過連接來訪問,這種計算會在堆上生成 java6 String str5 = str3 + "6"; System.out.println((str1 == str4));//true System.out.println((str1 == str5));//false
總結:
直接定義字符串變量的時候賦值,若是表達式右邊只有字符串常量,那麼就是把變量存放在常量池裏。
new出來的字符串是存放在堆裏面。
對字符串進行拼接操做,也就是作"+"運算的時候,分2中狀況:
表達式右邊是純字符串常量,那麼存放在字符串常量池裏面。
表達式右邊若是存在字符串引用,也就是字符串對象的句柄,那麼就存放在堆裏面。:
狀況1:
String s1 = new String("hello");// 堆內存的地址值 String s2 = "hello"; System.out.println(s1 == s2);// 輸出false,由於一個是堆內存,一個是常量池的內存,故二者是不一樣的。 System.out.println(s1.equals(s2));// 輸出true
若是上面代碼的話,這種狀況總共建立2個字符串對象。常量池中沒有字符串"hello" 的話,一個是new String 建立的一個新的對象,一個是常量「hello」對象的內容建立出的一個新的String對象。
狀況2:
```java String s2 = "hello"; String s1 = new String("hello");
String s1 = new String("hello"); 此時就建立一個對象,而常量「hello」則是從字符串常量池中取出來的。
### 二、有時候在面試的時候會遇到這樣的問題:**都說String是不可變的,爲何我能夠這樣作呢,String a = "1";a = "2";**
java public class StringTest {
public static void main(String[] args) { String s = "aflyun"; System.out.println("s1.hashCode() = " + s.hashCode() + "--" + s); s = "hello aflyun"; System.out.println("s2.hashCode() = " + s.hashCode() + "--" + s); //運行後輸出的結果不一樣,兩個值的hascode也不一致, //說明設置的值在內存中存儲在不一樣的位置,也就是建立了新的對象 }
s1.hashCode() = -1420403061--aflyun s2.hashCode() = -855605863--hello aflyun ```
【首先建立一個String對象s,而後讓s的值爲「aflyun」, 而後又讓s的值爲「hello aflyun」。 從打印結果能夠看出,s的值確實改變了。那麼怎麼還說String對象是不可變的呢?】
其實這裏存在一個誤區: s只是一個String對象的引用,並非對象自己。對象在內存中是一塊內存區,成員變量越多,這塊內存區佔的空間越大。引用只是一個4字節的數據,裏面存放了它所指向的對象的地址,經過這個地址能夠訪問對象。
也就是說,s只是一個引用,它指向了一個具體的對象,當s=「hello aflyun」; 這句代碼執行過以後,又建立了一個新的對象「「hello aflyun」, 而引用s從新指向了這個新的對象,原來的對象「aflyun」還在內存中存在,並無改變。內存結構以下圖所示:
相似的一張圖:
總結一下:「String對象一旦被建立就是固定不變的了,對String對象的任何改變都不影響到原對象,相關的任何改變操做都會生成新的對象」。
java的線程安全、單例模式、JVM內存結構等知識學習和整理
備註: 因爲本人能力有限,文中如有錯誤之處,歡迎指正。
謝謝你的閱讀,若是您以爲這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到!祝你天天開心愉快!
無論作什麼,只要堅持下去就會看到不同!在路上,不卑不亢!
博客首頁 : https://aflyun.blog.csdn.net/
願你我在人生的路上能都變成最好的本身,可以成爲一個獨擋一面的人
© 天天都在變得更好的阿飛雲