字符串應該是咱們在Java中用的最頻繁、最多的,可見字符串對於咱們來講是多麼的重要,因此咱們很是有必要去深刻的瞭解一下。正則表達式
String就表明字符串,在Java中字符串屬於對象。咱們剛剛接觸Java時,在學習數據類型的時候應該提到過String。Java有基本數據類型和引用數據類型,而String就是一個引用數據類型,它是一個類,既然它是一個類,那咱們就來看看它的源碼結構。數組
從上面的圖能夠看出,String類是用final修飾的,代表它不能再被繼承了,由於String這個類中對字符串的操做的方法已經很是豐富,不須要咱們再去擴展功能了。同時還實現了序列化、比較排序、字符序列這三個接口,代表字符串能夠被序列化、能夠用於比較排序。關於實現了CharSequence接口下面有講到。咱們還能夠看到String類中定義了一個char型數組value[ ],這個是用於存儲字符串的內容,而且使用final修飾的,代表這個是常量。因此字符串一旦被初始化,就不能夠被改變,表示String是不可變性,這就致使每次對String的操做都會生成新的String對象。安全
關於String實現CharSequence接口這裏,我去百度一下:多線程
CharSequence是一個接口,表示char值的一個可讀序列。此接口對許多不一樣種類的char序列提供統一的自讀訪問。此接口不修改該equals和hashCode方法的常規協定,所以,一般未定義比較實現 CharSequence 的兩個對象的結果。他有幾個實現類:CharBuffer、String、StringBuffer、StringBuilder。app
CharSequence與String都能用於定義字符串,但CharSequence的值是可讀可寫序列,而String的值是隻讀序列。性能
對於一個抽象類或者是接口類,不能使用new來進行賦值,可是能夠經過如下的方式來進行實例的建立:學習
CharSequence cs="hello";測試
實例:ui
1 public class StringTest { 2 public static void main(String[] args) { 3 String str="String"; 4 StringBuffer sBuffer=new StringBuffer("StringBuffer"); 5 StringBuilder sBuilder=new StringBuilder("StringBuilder"); 6 show(str); 7 show(sBuffer); 8 show(sBuilder); 9 } 10 //若是參數類型爲String則不能接收StringBuffer和StringBuilder 11 public static void show(CharSequence cs){ 12 System.out.println(cs); 13 } 14 }
運行結果:spa
多是這樣理解的吧,CharSequence是一個接口,自己是沒有什麼讀寫意義的。String只是它的一個實現類,雖然String是隻讀,可是CharSequence的實現類還有StringBuffer,StringBuilder這些可寫的,因此用CharSequence做爲參數能夠接收String,StringBuffer,StringBuilder這些類型。
參考:https://blog.csdn.net/a78270528/article/details/46785949
String的實例:
建立String的實例有兩種方式,一種是直接給String的變量賦值,另外一種是使用String的構造器建立實例。那麼這兩種方式建立的實例有什麼區別呢?區別就是前者會建立一個對象,然後者會建立兩個對象。
舉例:
1 public class StringTest { 2 public static void main(String[] args) { 3 //方式一:直接賦值 4 String s1="abc"; 5 String s2="abc"; 6 //方式二:new+構造器 7 String s3=new String("abc"); 8 String s4=new String("abc"); 9 System.out.println(s1 == s2);//true 10 System.out.println(s1 == s3);//false 11 System.out.println(s1 == s4);//false 12 System.out.println(s3 == s4);//false 13 System.out.println(s1.equals(s3));//true 14 } 15 }
運行結果一目瞭然,String的值是常量,它的值是放在方法區的處理池中,而常量池中相同的值在只會存在一份。咱們知道==比較的地址,equal()比較的是內容,s1和s2指向同一個引用,因此地址相同,而s3和s4它們分別建立了兩個對象,地址值顯然不一樣。
經過這個圖咱們也容易分析出建立實例時建立了幾個對象。
String s1 = "abc"建立對象的過程:
首先檢查常量池中是否存在內容爲"abc"的字符串,若是有,則再也不建立對象,直接讓s1變量指向該字符串的引用,若是沒有則在常量池中建立"abc"對象而後讓s1引用該對象。
String s3 = new String("abc")建立實例的過程:
首先在堆建立一個String的對象,並讓s3引用指向該對象,而後再到常量池中查看是否存在內容爲"abc"字符串對象,若是存在,則將String對象中的value引用指向常量對象,將new出來的字符串對象與字符串常量池中的對象聯繫起來,若是不存在,則在字符串常量池中建立一個內容爲"abc"的字符串對象,並將堆中的String對象與之聯繫起來。
String的拼接:
字符串的拼接有三種方式:直接使用"+"、使用concat()方法、使用append()方法。這裏我主要來討論一下"+"問題,舉例:
1 public class StringTest { 2 public static void main(String[] args) { 3 String s1="Hello"; 4 String s2="World"; 5 6 String s3="HelloWorld"; 7 String s4="Hello"+"World"; 8 String s5=s1+"World"; 9 String s6="Hello"+s2; 10 String s7=s1+s2; 11 12 System.out.println(s3==s4);//true 13 System.out.println(s3==s5);//false 14 System.out.println(s3==s6);//false 15 System.out.println(s3==s7);//false 16 System.out.println(s5==s6);//false 17 System.out.println(s5==s7);//false 18 System.out.println(s6==s7);//false 19 20 String s8=s7.intern(); 21 System.out.println(s3==s8);//true 22 23 } 24 }
從運行結果咱們能夠得出結論:①、常量與常量的拼接結果是常量,它們在在常量池中完成。②、只要涉及到有變量的(很是量),結果都是在堆內存中完成的。③、若是拼接後的結果調用了intern()方法,則返回值就是在常量池中。
String中的經常使用方法咱們須要熟練的掌握, 這樣平時作一些字符串的常規操做就能夠快速知道,而不用去查找API了。
①、常規:
若是還想學習更多的方法,能夠自行去看String的API。
StringBuffer和StringBuilder十分類似,它們都表明可變的字符序列,能夠對字符序列進行增刪改查操做,此時不會產生新的對象,並且它兩內部的方法也是同樣的。那麼String、StringBuffer和StringBuilder的區別是什麼。
String(JDK1.0):字符串常量,不可變字符序列,線程安全,效率低。
StringBuffer(JDK1.0):字符串變量,可變字符序列,線程安全,效率低。
StringBuilder(JDK5.0):字符串變量,可變字符序列,線程不安全,效率高。
爲何說String和StringBuffer是線程安全,StringBuilder是線程不安全呢?
由於String是final修飾的常量,它是不可變的字符串,全部的操做都是不可能改變它的值,因此線程是安全的。再經過看StringBuffer和StringBuilder的源碼,能夠很明顯發現,StringBuffer是線程安全的,由於其下的全部方法都加上了synchronized。而StringBuilder則沒有加這個關鍵字。
它們之間經常使用的方法:
String、StringBuffer和StringBuilder涉及可變序列與不可變序列、線程是否安全狀況,這些因素必然影響到它們之間的運行效率,因此咱們來比較一下他們之間的運行效率。
簡單示例:
1 public class StringTest { 2 public static void main(String[] args) { 3 long startTime=0L; 4 long endTime=0L; 5 String text=" "; 6 StringBuffer buffer=new StringBuffer(""); 7 StringBuilder builder = new StringBuilder(""); 8 9 //String 10 startTime=System.currentTimeMillis(); 11 for (int i = 0; i < 50000; i++) { 12 text=text+i; 13 } 14 endTime=System.currentTimeMillis(); 15 System.out.println("String執行時間:"+(endTime-startTime)); 16 17 //StringBuffer 18 startTime=System.currentTimeMillis(); 19 for (int i = 0; i < 50000; i++) { 20 buffer.append(String.valueOf(i)); 21 } 22 endTime=System.currentTimeMillis(); 23 System.out.println("StringBuffer執行時間:"+(endTime-startTime)); 24 25 //StringBuilder 26 startTime=System.currentTimeMillis(); 27 for (int i = 0; i < 50000; i++) { 28 builder.append(String.valueOf(i)); 29 } 30 endTime=System.currentTimeMillis(); 31 System.out.println("StringBuilder執行時間:"+(endTime-startTime)); 32 } 33 }
運行結果可能須要等個5-8秒,運行結果以下:
咱們屢次運行的結果也大體相同,因此運行效率爲:StringBuilder > StringBuffer > String。String如此的慢是由於它是字符串常量,在建立對象後是不可改變的,然而每次改變String類型的值都會在常量池中新建一個常量對象,因此很是耗時間。而StringBuffer和StringBuilder的可變的字符序列,它們只是在原有內容發生了改變,並無新建立對象。因此常常改變內容的字符串最好不要用 String類型,推薦使用StringBuffer,由於每次生成對象都會對系統性能產生影響,特別當內存中無引用對象多了之後, JVM 的 GC 就會開始工做,性能就會下降。
經過上面的學習,咱們簡單小結一下:
String:適用於少許字符串操做的狀況。
StringBuffer:適用多線程下在字符緩衝區進行大量操做的狀況。
StringBuilder:適用於單線程下在字符緩衝區進行大量操做的狀況。