------------------------------String在內存中的存儲情況(一下內容摘自參考資料1)-----------------------------------
前提:先了解下什么是聲明,什么時候才算是產生了對象實例
其中x並未看到內存分配,變量在使用前必須先聲明,再賦值,然后才可以使用。java基礎數據類型會用對應的默認值進行初始化
一、首先看看Java虛擬機JVM的內存塊及其變量、對象內存空間是怎么存儲分配的
1、棧:存放基本數據類型及對象變量的引用,對象本身不存放於棧中而是存放於堆中
1)、基礎類型 byte (8位)、boolean (1位)、char (16位)、int (32位)、short (16位)、float (32位)、double (64位)、long (64位)
2)、java代碼作用域中定義一個變量時,則java就在棧中為這個變量分配內存空間,當該變量退出該作用域時,java會自動釋放該變量所占的空間
2、堆:new操作符的對象
1)、new創建的對象和數組
2)、在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理
3、靜態域:static定義的靜態成員變量
4、常量池:存放常量
二、Java String類型
Java中String不是基本數據類型,而是一種特殊的類。String代表的是不可變的字符序列,為不可變對象,一旦被創建,就不能修改它的值,對於已經存在的String對象的修改都是重新創建一個新的對象,然后把新的值保存進去
三 、String實例代碼分析

1 package terry.java.base; 2 3 public class StringTest { 4 public static void main(String[] args) { 5 String a = "hello"; 6 String b = "hello"; 7 8 String newA = new String("hello"); 9 String newB = new String("hello"); 10 11 System.out.println("****** Testing Object == ******"); 12 System.out.println("a==b ? :" + (a==b)); 13 System.out.println("newA==newB ? :" +(newA==newB)); 14 System.out.println("a==newA ? :" + (a==newA)); 15 16 System.out.println("***** Testing String Object intern method******"); 17 System.out.println("a.intern()==b.intern() ? : " + (a.intern()==b.intern())); 18 System.out.println("newA.intern()==newB.intern() ? :" + (newA.intern()==newB.intern())); 19 System.out.println("a.intern()==newA.intern() ? :" + (a.intern()==newA.intern())); 20 System.out.println("a=a.intern() ? :" + (a==a.intern())); 21 System.out.println("newA==newA.intern() ? : " + (newA==newA.intern())); 22 23 System.out.println("****** Testing String Object equals method******"); 24 System.out.println("equals() method :" + a.equals(newA)); 25 26 String c = "hel"; 27 String d = "lo"; 28 final String finalc = "hel"; 29 final String finalgetc = getc(); 30 31 System.out.println("****** Testing Object splice ******"); 32 System.out.println("a==\"hel\"+\"lo\" ? :" + (a=="hel"+"lo")); 33 System.out.println("a==c+d ? : " + (a==c+d)); 34 System.out.println("a==c+\"lo\" ? : " + (a==c+"lo")); 35 System.out.println("a==finalc+\"lo\" ? :" + (a==finalc+"lo")); 36 System.out.println("a==finalgetc+\"lo\" ? :" + (a==finalgetc+"lo")); 37 38 } 39 private static String getc(){ 40 return "hel"; 41 } 42 }
Run As Java Application -- 輸出結果:

1 ****** Testing Object == ****** 2 a==b ? :true 3 newA==newB ? :false 4 a==newA ? :false 5 ***** Testing String Object intern method****** 6 a.intern()==b.intern() ? : true 7 newA.intern()==newB.intern() ? :true 8 a.intern()==newA.intern() ? :true 9 a==a.intern() ? :true 10 newA==newA.intern() ? : false 11 ****** Testing String Object equals method****** 12 equals() method :true 13 ****** Testing Object splice ****** 14 a=="hel"+"lo" ? :true 15 a==c+d ? : false 16 a==c+"lo" ? : false 17 a==finalc+"lo" ? :true 18 a==finalgetc+"lo" ? :false
內存分析:
上述各個變量及引用在JVM分配的內存情況
String類型對象實例直接賦值和new操作符產生的結果在JVM內存分配過程是不同的,如下注釋說明
String常量+的拼接 及 String常量與引用實例+的拼接 的區別
關於String對象的intern()方法的說明
一個初始時為空的字符串池,它由類 String
私有地維護
當調用 intern 方法時,如果池已經包含一個等於此 String
對象的字符串(該對象由 equals(Object)
方法確定),則返回池中的字符串。否則,將此String
對象添加到池中,並且返回此String
對象的引用,因此a.intern(),b.intern(),newA.intern(),newB.intern()隱含的各自在棧中分配了各自的內存區域,同時都將棧中的應用全部指向了String pool常量池中的同一塊區域"hello"
-----------------------------------------------Thinking in Java 讀書筆記--------------------------------------------
《Java編程思想<第四版>》-第13章
本章第一小節標題為“不可變String”,第一句話為“String對象時不可變的”。String類中任何一個看起來會修改String值的方法,實際上都是創建了一個全新的String對象,以包含修改后的字符串內容,原來的String對象絲毫未動。
給Java初學者舉個栗子,有基礎的直接跳過:

1 String s = "ABCabc"; 2 System.out.println("s = " + s); 3 4 s = "123456"; 5 System.out.println("s = " + s);
打印結果是:

s = ABCabc
s = 123456
貌似s的值被改變了,改變的只是s的這個引用,本來s該引用指向了常量池中的“ABCabc”,后來指向了常量池中的“123456”。結合上面的內存模型,常量池中有兩個常量“ABCabc”、“123456”,棧中只有一個引用s,本來這個s指向“ABCabc”,后來被賦值給了“123456”,圖就不畫了,還不明白,就別往下看了,看一點更基礎的比較好。
來看下String的源代碼:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */漢化一下:這個value是用來裝char的.明白了吧,String其實是char數組的包裝類。
//而這個數組是final類型的,不可能指向別的對象,但是可以改,這個請耐心看下去 private final char value[]; /** Cache the hash code for the string */緩存hashCode private int hash; // Default to 0 }
源碼中String的本質是一個final類型的char數組,既然是final類型,那個該數組引用value就不允許再指向其他對象了,因此只從類的設計角度講:如果jdk源碼中並沒有提供對value本身的修改,那么理論上來講String是不可變的。
StringBuilder的故事
大家都知道String的有兩個關系很親近的小伙伴:StringBuffer、StringBuilder,其中的區別大家可以看源代碼,StringBuffer對每個方法(除了構造函數)都用了同步,StringBuilder就內向多了。
大家還知道java中是不支持運算符重載的,但是有且僅有兩個例外:+、+=對String進行的重載。
+、+=被重載了,+用來連接String操作:就是說 “a" + "b" + ”c“= ”abc“;
上文書說道:
String是不可變的,即是說String a = "a" ; a + "b" 之后,a還是”a“,這個”ab“實際上又生成了一個新的String對象。將這個賦值語句剖析一下:
"a" + "b" + "c":先計算前兩個 "a" + "b":這時內存中其實有四個字符串,”a“,"b","c","ab",然后再+”c“,這是內存中有五個String:”a“,"b","c","ab",”abc“。
真的是這樣嗎?
可以通過javap來反編譯上面的賦值語句,會發現,在編譯本條語句時,編譯器會自作主張的引入了StringBuilder,並調用了StringBuilder.append方法,這樣就不用再生成多余的字符串了;
因此在用StringBuilder進行append操作時候,千萬不要使用append("a" + "b")這樣的操作,因為醬,編譯器會為你另外的創建一個StringBuilder對象來處理括號里的字符串操作。
toString的故事,無意識的遞歸:
toString方法里面謹慎返回this,可能因此無限遞歸;
this遇到+“”時候,會將this轉換成String,怎么轉換呢?通過調用toString方法的好了,無限遞歸,棧溢出。
String真的不可變嗎?
從上文可知String的成員變量是private final 的,也就是初始化之后不可改變。那么在這幾個成員中, value比較特殊,因為他是一個引用變量,而不是真正的對象。value是final修飾的,也就是說final不能再指向其他數組對象,那么我能改變value指向的數組嗎? 比如將數組中的某個位置上的字符變為下划線“_”。 至少在我們自己寫的普通代碼中不能夠做到,因為我們根本不能夠訪問到這個value引用,更不能通過這個引用去修改數組。 那么用什么方式可以訪問私有成員呢? 沒錯,用反射, 可以反射出String對象中的value屬性, 進而改變通過獲得的value引用改變數組的結構。下面是實例代碼:

//創建字符串"Hello World", 並賦給引用s String s = "Hello World"; System.out.println("s = " + s); //Hello World //獲取String類中的value字段 Field valueFieldOfString = String.class.getDeclaredField("value"); //改變value屬性的訪問權限 valueFieldOfString.setAccessible(true); //獲取s對象上的value屬性的值 char[] value = (char[]) valueFieldOfString.get(s); //改變value所引用的數組中的第5個字符 value[5] = '_'; System.out.println("s = " + s); //Hello_World }
打印結果為: s = Hello World
s = Hello_World
在這個過程中,s始終引用的同一個String對象,但是再反射前后,這個String對象發生了變化, 也就是說,通過反射是可以修改所謂的“不可變”對象的。但是一般我們不這么做。這個反射的實例還可以說明一個問題:如果一個對象,他組合的其他對象的狀態是可以改變的,那么這個對象很可能不是不可變對象。例如一個Car對象,它組合了一個Wheel對象,雖然這個Wheel對象聲明成了private final 的,但是這個Wheel對象內部的狀態可以改變, 那么就不能很好的保證Car對象不可變。
常用方法:

public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index];//先判斷越界,然后直接返回合法值 }

public boolean isEmpty() { return value.length == 0; }

public int length() { return value.length; }
從源碼的實現來看,length() == 0 和 isEmpty()效率是一樣的。

public boolean equals(Object anObject) { if (this == anObject) { return true; }//重寫了Object的equals方法,判斷字符串內容 if (anObject instanceof String) { String anotherString = (String) anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }

public boolean contentEquals(StringBuffer sb) { synchronized (sb) { return contentEquals((CharSequence) sb); } }

public boolean contentEquals(CharSequence cs) { if (value.length != cs.length()) return false; // Argument is a StringBuffer, StringBuilder if (cs instanceof AbstractStringBuilder) { char v1[] = value; char v2[] = ((AbstractStringBuilder) cs).getValue(); int i = 0; int n = value.length; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } // Argument is a String if (cs.equals(this)) return true; // Argument is a generic CharSequence char v1[] = value; int i = 0; int n = value.length; while (n-- != 0) { if (v1[i] != cs.charAt(i)) return false; i++; } return true; }
contentEquals(charSequence cs)參數可以是StringBuilder、StringBuffer、String以及CharSequence,contentEquals(StringBuilder sb)只是為了保證sb的一致性,在外面加了互斥鎖。equals方法也只是contentEquals的一種情況的實現,完全可以用contentEquals(cs)來取代。

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);//返回了新的string }
concat方法可以看出,最后返回的其實是一個新new的string。並不對原value內容進行改動。
同樣的:

public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); }
這兩個方法很好用

public boolean startsWith(String prefix, int toffset) { char ta[] = value; int to = toffset; char pa[] = prefix.value; int po = 0; int pc = prefix.value.length; // Note: toffset might be near -1>>>1. if ((toffset < 0) || (toffset > value.length - pc)) { return false; } while (--pc >= 0) { if (ta[to++] != pa[po++]) { return false; } } return true; }

public boolean endsWith(String suffix) { return startsWith(suffix, value.length - suffix.value.length); }
endsWith是用startsWith實現的,只看名字有點搞笑。
--------------------------------------未完成,但是不想寫了,覺得有點浪費時間---------------------------------------
參考材料:
http://blog.csdn.net/yihuiworld/article/details/13511925
http://www.2cto.com/kf/201401/272974.html#comment_iframe