《Java基礎知識總結》系列是對自己學習Java歷程中知識的一個總結,也是為自己找工作前知識的回顧,為找工作奠定基礎。
String類學習總結
1、String 字符串API 解讀
我們要想真正了解String類,最簡單、最直接的辦法就是讀懂API幫助文檔中對String類的介紹,API是SUN公司寫的,同時這些信息也是最底層的,更有助於幫助我們從根本上了解String類!
首先打開API幫助文檔,我們一起來解讀……
1、public final class String extends Object
1)這句話告訴我們String 類是最終類(final類)不能被繼承。
2)它繼承與Object,它擁有Object類的所有屬性和方法,比如:Object類的toString()、hashCode()、equals()等方法。當然Object類是所有類的父類,即所有類都擁有Object類的屬性和方法,不僅僅是String 類。
2、The String class represents character strings. All string literals in Java programs, such as "abc", are implemented as instances of this class.
1)這句話告訴我們Java中所有字符串的字面值都是String類的實例,即時一個String類的一個具體對象。例如以下程序1:
1 public class StringDemo { 2 public static void main(String[] args) { 3 System.out.println("abc".length());//3 4 System.out.println("abc".equals("abc"));//true 5 } 6 }
如上面的程序,"abc"擁有String類的方法,因此它是一個String類的一個實例化對象。
3、String are constant;their values cannot be changed after they are created.
1) 字符串是常量,一旦被創建,他們的值就不能改變。如以下程序2:
1 public class StringDemo { 2 public static void main(String[] args) { 3 String str="Hello"; 4 String str2=",World!"; 5 String str3=str+str2; 6 System.out.println(str3);// Hello,World! 7 str="Linux"; 8 System.out.println(str);//Linux 9 } 10 }
從上面程序我們可知:str和str2拼接之后形成了一個新的字符串,str和str2本身並沒有變化。有人會有這樣的疑問,當str="Linux";這條語句執行時,str的值不是變了嗎?我們要區分清楚,字符串是引用類型變量,當執行完str="Linux";這條語句后,"Hello"這個字符串常量(也是一個字符串對象)並沒有改變,知識它的句柄str指向了新的字符串對象"Linux"而已!
4、String buffers support mutable strings. Because String objects are immutable they can be shared.For example:
字符串緩沖區支持可變的字符串。因為 String 對象是不可變的,所以可以共享它們。
1)String str="abc";
is equivalent to:
char data[] ={'a','b','c'};
String str=new String(data);
這也給我們提供了把數組轉化成字符串的一種方法!
5、The Java language provides special support for the string concatenation operator ( + ),and for conversion of other objects to strings.
String concatenation is implemented through the StringBuilder(or StringBuffer) class and its append method.
String conversions are implemented through the method toString, defined by Object and inherited by all classes in Java.
1)Java 語言提供對字符串串聯符號("+")以及將其他對象轉換為字符串的特殊支持。字符串串聯是通過 StringBuilder(或 StringBuffer)類及其 append 方法實現的。字符串轉換是通過 toString 方法實現的,該方法由 Object 類定義,並可被 Java 中的所有類繼承。
6、Constant Pool(常量池)
常量池指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(String)的常量值(final)還包含一些以文本形式出現的符號引用,比如:
◆類和接口的全限定名;
◆字段的名稱和描述符;
◆方法的名稱和描述符。
虛擬機必須為每個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和 floating point常量)和對其他類型,字段和方法的符號引用。
對於String常量,它的值是在常量池中的。而JVM中的常量池在內存當中是以表的形式存在的, 對於String類型,有一張固定長度的CONSTANT_String_info表用來存儲文字字符串值,注意:該表只存儲文字字符串值,不存儲符號引用。說到這里,對常量池中的字符串值的存儲位置應該有一個比較明了的理解了。在程序執行的時候,常量池會儲存在Method Area,而不是堆中。
1 public class StringDemo { 2 public static void main(String[] args) { 3 String str="Hello"; 4 String str2="Hello"; 5 System.out.println(str==str2);//true 6 char []ch={'a','b','c'}; 7 char []ch2={'a','b','c'}; 8 System.out.println(ch==ch2);//false 9 } 10 }
由程序可知對象型的只有String類型,數組不可以!
上面程序的執行過程是:想創建一個"Hello"對象,然后str指向它,當執行String str2="Hello";這條語句時,先到常量池中查找看是否有"Hello"這個字符串對象,如果有的話,就把str2指向它,如果沒有,就把新的字符串對象寫入常量池中。
2、String字符串的本質
String字符串 = char[] + 操作(toUpperCase(),concat())
String的底層就是char[]數組,是char[]數組的封裝類
類: 數據+相關的操作
char數組只是數據, 沒有操作
String用來表示文本,即一系列 Unicode 字符。字符串是我們開發過程中頻繁使用的對象,我們在軟件界面上提示用戶的所有一切都是字符串:不管是發票的日期還是發票的編號,或者是發票的金額雖然在 定義數據類型時候分別應該是DateTime、double或string,但當界面呈現的時候,都是以文本的形式呈現,也就是string格式。
字符串是 Unicode 字符的有序集合,用於表示文本。所以String 對象是 System.Char 對象的有序集合,用於表示字符串。String 對象的值是該有序集合的內容,並且該值是不可變的。字符串本質是字符數組——這是一個非常重要的概念,了解這個概念就可以全面的理解和把握字符串的各種特 征。
public static void arraycopy {Object src, int srcPos, Object dest, int destPos, int length)
-
從指定源數組中復制一個數組,復制從指定的位置開始,到目標數組的指定位置結束。從
src引用的源數組到dest引用的目標數組,數組組件的一個子序列被復制下來。被復制的組件的編號等於length參數。源數組中位置在srcPos到srcPos+length-1之間的組件被分別復制到目標數組中的destPos到destPos+length-1位置。如果參數
src和dest引用相同的數組對象,則復制的執行過程就好像首先將srcPos到srcPos+length-1位置的組件復制到一個帶有length組件的臨時數組,然后再將此臨時數組的內容復制到目標數組的destPos到destPos+length-1位置一樣。If 如果
dest為null,則拋出NullPointerException異常。如果
src為null, 則拋出NullPointerException異常,並且不會修改目標數組。否則,只要下列任何情況為真,則拋出
ArrayStoreException異常並且不會修改目標數組:src參數指的是非數組對象。dest參數指的是非數組對象。src參數和dest參數指的是那些其組件類型為不同基本類型的數組。src參數指的是具有基本組件類型的數組且dest參數指的是具有引用組件類型的數組。src參數指的是具有引用組件類型的數組且dest參數指的是具有基本組件類型的數組。
否則,只要下列任何情況為真,則拋出
IndexOutOfBoundsException異常,並且不會修改目標數組:srcPos參數為負。destPos參數為負。length參數為負。srcPos+length大於src.length,即源數組的長度。destPos+length大於dest.length,即目標數組的長度。
否則,如果源數組中
srcPos到srcPos+length-1位置上的實際組件通過分配轉換並不能轉換成目標數組的組件類型,則拋出ArrayStoreException異常。在這種情況下,將 k 設置為比長度小的最小非負整數,這樣就無法將src[srcPos+k]轉換為目標數組的組件類型;當拋出異常時,從srcPos到srcPos+k-1位置上的源數組組件已經被復制到目標數組中的destPos到destPos+k-1位置,而目標數組中的其他位置不會被修改。(因為已經詳細說明過的那些限制,只能將此段落有效地應用於兩個數組都有引用類型的組件類型的情況。) -
- 參數:
-
src- 源數組。 -
srcPos- 源數組中的起始位置。 -
dest- 目標數組。 -
destPos- 目標數據中的起始位置。 -
length- 要復制的數組元素的數量。
public static char[] copyOf(char[] original,int newLength)
- 復制指定的數組,截取或用 null 字符填充(如有必要),以使副本具有指定的長度。對於在原數組和副本中都有效的所有索引,這兩個數組將包含相同的值。對於在副本中有效而在原數組無效的所有索引,副本將包含 '\\u000'。當且僅當指定長度大於原數組的長度時,這些索引存在。
-
- 參數:
-
original- 要復制的數組 -
newLength- 要返回的副本的長度 - 返回:
- 原數組的副本,截取或用 null 字符填充以獲得指定的長度
1 public class P2_CharArrayDemo { 2 public static void main(String[] args) { 3 //Java 可以將char[]作為字符串處理 4 char[] chs1={'北','京'}; 5 char[] chs2={'我','愛','你'}; 6 System.out.println(chs1);//北京 7 //char[]運算需要編程處理,如連接: 8 char[] chs3=Arrays.copyOf(chs1, chs1.length+chs2.length); 9 System.arraycopy(chs2, 0, chs3, chs1.length, chs2.length); 10 System.out.println(chs3);//北京我愛你 11 //String API提供了簡潔的連接運算: 12 String s1="北京"; 13 String s2="我愛你"; 14 String s3=s1.concat(s2);//String API方法 15 //字符串轉大寫底層原理: 16 char[] chs4={'A','a','c','f'}; 17 char[] chs5=Arrays.copyOf(chs4, chs4.length); 18 for(int i=0;i<chs5.length;i++){ 19 char c=chs5[i]; 20 if(c>='a'&&c<='z'){ 21 chs5[i]=(char)(c+'A'-'a');// 22 } 23 } 24 } 25 }
1 public class P2_StringAPIDemo { 2 public static void main(String[] args) { 3 //String API不改變String對象內容: 4 String s1="ABCD"; 5 String s2="Abcd"; 6 //字符串(原)對象是不變的!String API不改變String對象內容 7 String s3=s1.toUpperCase();//不需要改變,返回原對象 8 String s4=s2.toUpperCase();//需要改變,返回新對象 9 System.out.println("s1==s3:"+(s1==s3));//true 10 System.out.println("s2==s4:"+(s2==s4));//false 11 //String 和 char[] 可以相互轉換: 12 char[] chs={'我','愛','中','國'}; 13 String china=new String(chs); 14 System.out.println(china); 15 String str="龍年吉祥"; 16 chs=str.toCharArray(); 17 } 18 }
理解String內存存儲機制的一個經典程序
1 public class P2_StringParamDemo { 2 public static void main(String[] args) { 3 String s="AA"; 4 char[] chs={'A','A'}; 5 test(s,chs); 6 System.out.println(s);//AA 7 System.out.println(chs);//SA 8 } 9 public static void test(String str,char[] chs){ 10 str="BC"; 11 chs[0]='S'; 12 chs=str.toCharArray(); 13 } 14 }
StringBuilder和String 的連接性能測試
1 public class P4_StringBuilderVSStringDemo { 2 public static void main(String[] args) { 3 String str="Hello,"; 4 String str2=str+"World"; 5 //String 字符串常量,創建之后不能再改變。 6 System.out.println(str==str2);//false 7 8 StringBuilder sb=new StringBuilder(); 9 sb.append('中'); 10 sb.append('國'); 11 sb.append('我'); 12 sb.append('愛'); 13 sb.insert(1, '你'); 14 StringBuilder sb2=sb.replace(0,1,"北京"); 15 //StringBuilder 創建之后可以再改變。 16 System.out.println(sb==sb2);//true 17 System.out.println("*************************************"); 18 /* 19 * String 字符串常量,創建之后不能再改變;StringBuilder 創建之后可以再改變。 20 * 當對String 類進行連接時,會產生新的對象; 21 * 而對StringBuilder 進行連接操作不會產生新的對象。 22 * 因此StringBuilder的連接操作比String塊很多。 23 */ 24 System.out.println(testString(50000));//5063 25 System.out.println(testStringBuilder(50000));//0 26 } 27 public static long testString(int n){ 28 long start = System.currentTimeMillis(); 29 String s=""; 30 for(int i=0;i<n;i++){ 31 s+="A"; 32 } 33 long end=System.currentTimeMillis(); 34 return end-start; 35 } 36 public static long testStringBuilder(int n){ 37 long start=System.currentTimeMillis(); 38 StringBuilder buf=new StringBuilder(); 39 for(int i=0;i<n;i++){ 40 buf.append("A"); 41 } 42 long end=System.currentTimeMillis(); 43 return end-start; 44 } 45 }
經典面試題
1 public class Test2 { 2 public static void main(String[] args) { 3 System.out.println('0'+0);//48 4 System.out.println('1'+0);//49 5 System.out.println('a'+0);//97 6 System.out.println('A'+0);//65 7 } 8 }
1 public class StaticStringDemo { 2 public static final int I=123; 3 public static final String S="123ABC"; 4 public static void main(String[] args) { 5 String s1="123ABC";//s1= 123ABC 6 String s2="123"+"ABC";//s2= 123ABC 7 String s3='1'+"23ABC";//s3= 123ABC 8 String s4='1'+'2'+'3'+"ABC";//s4= 150ABC 9 String s5="1"+"2"+"3"+"ABC";//s5= 123ABC 10 String s6=123+"ABC";//s6= 123ABC 11 String s7=1+2+3+"ABC";//s7= 6ABC 12 String s8=S+"";//s8= 123ABC 13 String s9=I+"ABC";//s9= 123ABC 14 15 String ss="ABC"; 16 String s10=123+ss;//編譯期,不處理,運算期間運算。 17 String s11=new String("123ABC");//運行期間運算。 18 ////new String("123ABC");運行期間運算 19 String s12=new String("123"+"ABC"); 20 System.out.println(S==s1);//true 21 System.out.println(S==s2);//true 22 System.out.println(S==s3);//true 23 System.out.println(S==s4);//false 24 System.out.println(S==s5);//true 25 System.out.println(S==s6);//true 26 System.out.println(S==s7);//false 27 System.out.println(S==s8);//true 28 System.out.println(S==s9);//true 29 System.out.println(S==s10);//false 30 System.out.println(S==s11);//false 31 System.out.println(S==s12);//false 32 } 33 }
2、String的兩種實例化方式
1、通過接收一個String類的對象,並重新實例化這個對象(這句話比較拗口,慢慢理解)
String本身是一個類,在String類中定義了如下的構造方法:
String(String original):初始化一個新創建的 String 對象,使其表示一個與參數相同的字符序列;換句話說,新創建的字符串是該參數字符串的副本。
例如:String str=new String("Hello");
2、采用直接賦值的方式進行對象的實例化
例如:String str2="Hello";
1 public class StringDemo { 2 public static void main(String[] args) { 3 String str=new String("Hello"); 4 System.out.println(str=="Hello"); //false 5 String str1="Hello"; 6 System.out.println(str1=="Hello"); //true 7 String str2=new String("Hello"); 8 System.out.println(str==str2); //false 9 } 10 }
你可能對上述層序中的結果產生了疑惑,為什么會是這種結果呢?
下面讓我們引入srueyonder的一篇博客,一切疑惑就會迎刃而解:JAVA String對象和字符串常量的關系解析
3、Java String對象和字符串常量的關系解析
1、字符串內部列表
JAVA中所有的對象都存放在堆里面,包括String對象。字符串常量保存在JAVA的.class文件的常量池中,在編譯期就確定好了。 虛擬機為每個被裝載的類型維護一個常量池。常量池就是該類型所用常量的一個有序集合,包括直接常量(string、integer和float point常量)和對其他類型、字段和方法的符號引用。
例如,String s = new String( "myString" );
其中"myString"是字符串常量,在編譯時被存儲在常量池的某個位置。 在解析階段,虛擬機發現字符串常量"myString",它會在一個內部字符串常量列表中查找,如果沒有找到,那么會在堆里面創建一個包含字符序列 [myString]的String對象s1,然后把這個字符序列和對應的String對象作為名值對( [myString], s1 )保存到內部字符串常量列表中。如下圖所示:
引用的這篇博客到此結束,因為不是太理解,對其正確性保持質疑態度!
接下來我們引用另一篇博客來解釋上面的疑惑:http://www.bianceng.cn/Programming/Java/201101/22511.htm
4、Java String字符串對象的創建及管理
Constant Pool常量池的概念:
在講到String的一些特殊情況時,總會提到String Pool或者Constant Pool,但是我想很多人都不太明白Constant Pool到底是個怎么樣的東西,運行的時候存儲在哪里,所以在這里先說一下Constant Pool的內容。String Pool是對應於在Constant Pool中存儲String常量的區域.習慣稱為String Pool,也有人稱為String Constant Pool.好像沒有正式的命名。在java編譯好的class文件中,有個區域稱為Constant Pool,他是一個由數組組成的表,類型為cp_info constant_pool[],用來存儲程序中使用的各種常量,包括Class/String/Integer等各種基本Java數據類型。
對於Constant Pool,表的基本通用結構為:
1 cp_info { 2 u1 tag; 3 u1 info[]; 4 }
tag是一個數字,用來表示存儲的常量的類型,例如8表示String類型,5表示Long類型,info[]根據類型碼tag的不同會發生相應變化。
對於String類型,表的結構為:
1 CONSTANT_String_info { 2 u1 tag; 3 u2 string_index; 4 }
tag固定為8,string_index是字符串內容信息,類型為:
1 CONSTANT_Utf8_info { 2 u1 tag; 3 u2 length; 4 u1 bytes[length]; 5 }
tag固定為1,length為字符串的長度,bytes[length]為字符串的內容。
(以下代碼在jdk6中編譯)
為了詳細理解Constant Pool的結構,我們參看一些代碼:
1 String s1 = "sss111"; 2 String s2 = "sss222"; 3 System.out.println(s1 + " " + s2);
由於"sss111"和"sss222"都是字符串常量,在編譯期就已經創建好了存儲在class文件中。
在編譯后的class文件中會存在這2個常量的對應表示:
1 08 00 11 01 00 06 73 73 73 31 31 31 ; ......sss111.... 2 08 00 13 01 00 06 73 73 73 32 32 32 ; ......sss222....
根據上面說的String常量結構,我們分析一下:
開始的08為CONSTANT_String_info結構中的tag,而11應該是它的相對引用,01為CONSTANT_Utf8_info的 tag,06為對應字符串的長度,73 73 73 31 31 31為字符串對應的編碼,接着分析,會發現后面的是對應"sss222"的存儲結構。
經過上面分析,我們知道了11和13是兩個字符串的相對引用,就可以修改class文件來修改打印的內容,把class文件中的00 6E 00 04 00 03 00 00 00 24 12 10 4C 12 12 4D改成00 6E 00 04 00 03 00 00 00 24 12 10 4C 12 10 4D,程序就會輸出sss111 sss111,而不是和原程序一樣輸出sss111 sss222,因為我們把對"sss222"的相對引用12改成了對"sss111"的相對引用10。
1 public class Test { 2 public static void main(String[] args) { 3 String s1 = "sss111"; 4 String s2 = "sss111"; 5 } 6 }
在上面程序中存在2個相同的常量"sss111",對於n個值相同的String常量,在Constant Pool中只會創建一個,所以在編譯好的class文件中,我們只能找到一個對"sss111"的表示:
000000abh: 08 00 11 01 00 06 73 73 73 31 31 31 ; ......sss111
在程序執行的時候,Constant Pool會儲存在Method Area,而不是heap中。另外,對於""內容為空的字符串常量,會創建一個長度為0,內容為空的字符串放到Constant Pool中,而且Constant Pool在運行期是可以動態擴展的。
關於String類的說明
1.String使用private final char value[]來實現字符串的存儲,也就是說String對象創建之后,就不能再修改此對象中存儲的字符串內容,就是因為如此,才說String類型是不可變的(immutable)。
2.String類有一個特殊的創建方法,就是使用""雙引號來創建.例如new String("i am")實際創建了2個String對象,一個是"i am"通過""雙引號創建的,另一個是通過new創建的.只不過他們創建的時期不同, 一個是編譯期,一個是運行期!
3.java對String類型重載了+操作符,可以直接使用+對兩個字符串進行連接。
4.運行期調用String類的intern()方法可以向String Pool中動態添加對象。
String的創建方法一般有如下幾種
1.直接使用""引號創建;
2.使用new String()創建;
3.使用new String("someString")創建以及其他的一些重載構造函數創建;
4.使用重載的字符串連接操作符+創建。
例1
1 String s1 = "sss111"; 2 //此語句同上 3 String s2 = "sss111"; 4 System.out.println(s1 == s2); //結果為true
例2
1 String s1 = new String("sss111"); 2 String s2 = "sss111"; 3 System.out.println(s1 == s2); //結果為false
例3
1 String s1 = new String("sss111"); 2 s1 = s1.intern(); 3 String s2 = "sss111"; 4 System.out.println(s1 == s2); //結果為true
public String intern()
返回字符串對象的規范化表示形式。
一個初始時為空的字符串池,它由類 String 私有地維護。
當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(該對象由 equals(Object) 方法確定),則返回池中的字符串。否則,將此 String 對象添加到池中,並且返回此 String 對象的引用。
它遵循對於任何兩個字符串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true。
例4
1 String s1 = new String("111"); 2 String s2 = "sss111"; 3 String s3 = "sss" + "111"; 4 String s4 = "sss" + s1; 5 System.out.println(s2 == s3); //true 6 System.out.println(s2 == s4); //false 7 System.out.println(s2 == s4.intern()); //true
例5
這個是The Java Language Specification中3.10.5節的例子,有了上面的說明,這個應該不難理解了
public class Test { public static void main(String[] args) { String hello = "Hello", lo = "lo"; System.out.print((hello == "Hello") + " "); System.out.print((Other.hello == hello) + " "); System.out.print((hello == ("Hel"+"lo")) + " "); System.out.print((hello == ("Hel"+lo)) + " "); System.out.println(hello == ("Hel"+lo).intern()); //運行結果:true true true false true } } class Other { static String hello = "Hello"; }
輸出結果為true true true true false true,請自行分析!
結果上面分析,總結如下:
1.單獨使用""引號創建的字符串都是常量,編譯期就已經確定存儲到String Pool中;
2.使用new String("")創建的對象會存儲到heap中,是運行期新創建的;
3.使用只包含常量的字符串連接符如"aa" + "aa"創建的也是常量,編譯期就能確定,已經確定存儲到String Pool中;
4.使用包含變量的字符串連接符如"aa" + s1創建的對象是運行期才創建的,存儲在heap中;
5.使用"aa" + s1以及new String("aa" + s1)形式創建的對象是否加入到String Pool中我不太確定,可能是必須調用intern()方法才會加入。
還有幾個經常考的面試題:
1.
String s1 = new String("s1") ;
String s2 = new String("s1") ;
上面創建了幾個String對象?
答案:3個 ,編譯期Constant Pool中創建1個,運行期heap中創建2個.
2.
String s1 = "s1";
String s2 = s1;
s2 = "s2";
s1指向的對象中的字符串是什么?
答案: "s1"
5、Java的String例解
引自博客:http://blog.csdn.net/zk_zhangkai/article/details/7927247
引用理由:簡單明了,值得推薦
1 String a="a"; 2 String b="b"; 3 String c="ab"; 4 String d="ab"; 5 String e=a+b;
程序中用來存放數據的內存分為四塊
1、全局區(靜態區)(static)
2、文字常量區 :常量字符串就是放在這塊區域,即是我們常說起的常量池。
3、棧區(stack):存放函數的參數值,局部變量的值等。
4、堆區(heap) : 存放對象
當我們定義字符串
String a = "a";
a在棧區,“a”是字符串常量,在常量池中
String b = "b";
b在棧區,“b”在常量池
String c="ab";
c在棧區,“ab”在常量池
String d="ab";
d在棧區,這個時候常量池里已經有"ab",所以直接使用已經有的那個“ab”
所以這個時候c和d都指向的常量池里面的同一個“ab”
String e=a+b;
e在棧區,a+b實際上產生了一個新的String對象,既然是String對象,所以結果“ab”放在堆區中,即e指向的是堆里的“ab”
這樣的情況下,c==d為true,c==e為false
另外,如果定義的是字符串對象
String str1 = new String("ab");
str1在棧區,創建的“ab”字符串對象在堆區
String str2 = new String("ab");
str2在棧區,又創建的一個新的“ab”對象也在堆區,不過和剛才的“ab”不是同一個。
相當於堆區中有兩個字符串對象,不過正好內容都是“ab”而已。
所以str1==str2為false
常量池里面放着的常量字符串可以重復使用,但是必須是你直接使用的該字符串,像a+b這種形式雖然得到的結果是“ab”,但並不是使用的字符串常量“ab”
