作一個積極的人html
編碼、改bug、提高本身java
我有一個樂園,面向編程,春暖花開!面試
推薦閱讀編程
第一季
0、Java的線程安全、單例模式、JVM內存結構等知識梳理設計模式
三、Java內存管理-JVM內存模型以及JDK7和JDK8內存模型對比總結(三)數據結構
七、Java內存管理-掌握自定義類加載器的實現(七)
第一季總結:由淺入深JAVA內存管理 Core Story第二季
九、Java內存管理-」一文掌握虛擬機建立對象的祕密」(九)
十、Java內存管理-你真的理解Java中的數據類型嗎(十)
十一、Java內存管理-Stackoverflow問答-Java是傳值仍是傳引用?(十一)
十二、Java內存管理-探索Java中字符串String(十二)
實戰
分享一位老師的人工智能教程。零基礎!通俗易懂!風趣幽默!你們能夠看看是否對本身有幫助,點擊這裏查看【人工智能教程】。接下來進入正文。
首先JDK API的介紹:
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中能夠看出:
從上面的介紹中發現:字符串是常量,它們的值在建立以後不能更改。爲何會這樣呢?要了解其緣由,簡單看一下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修飾的字段建立之後就不可改變。
注意: 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複製代碼
總結:
狀況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:
String s2 = "hello";
String s1 = new String("hello");複製代碼
String s1 = new String("hello"); 此時就建立一個對象,而常量「hello」則是從字符串常量池中取出來的。
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內存結構等知識學習和整理
備註: 因爲本人能力有限,文中如有錯誤之處,歡迎指正。
謝謝你的閱讀,若是您以爲這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到!祝你天天開心愉快!