Java從入門到放棄(7)String

1.字符串的不可變性

一旦一個string對象在內存(堆)中被創建出來,他就無法被修改。特別要注意的是,String類的所有方法都沒有改變字符串本身的值,都是返回了一個新的對象。如果需要一個可修改的字符串,應該使用StringBuffer 或者 StringBuilder。否則會有大量時間浪費在垃圾回收上,因為每次試圖修改都有新的string對象被創建出來。

2. substring(int beginIndex, int endIndex)方法

作用: 截取字符串並返回其[beginIndex,endIndex-1]範圍內的內容。

3.調用substring時發生了什麼

x.substring(1,3);

因為x是不可變的,所以當調用方法給變量賦值時會指向一個新的字符串但是JDK6和JDK7的具體調用細節是不同的

在JDK6中String類包含三個成員變量 char value[], int offset,int count分別用來 存儲真正的字符數組,數組的第一個位置索引以及字符串中包含的字符個數。當調用substring時會創建一個新的string對象但是這個string對象仍然指向堆中的同一個字符數組。這兩個對象只有中只有count和offset 的值是不同的。

存在的問題:如果只是需要很小一段字符串,但是切割的字符串很長,就會導致這個長字符串一直在被引用,所以會無法被回收,就可能會導致內存洩露,一般使用x = x.substring(x, y) + ""解決這個問題

這個問題在JDK7中得到解決,在使用substring時會在堆內存中創建一個新的數組

4. replaceFirst、replaceAll、replace區別

replace()

在字符串中將指定的字符全部替換為另一字符

replaceFirst()、replaceAll()

用法與replace相同,但是這兩個方法都是基於正則表達式替換,replaceFirst只會替換第一次出現的,replaceAll會去用正則表達式匹配整個字符串並替換

public static void main(String[] args) {

String s = "123.dwe.123";

System.out.println(s.replace(".","#"));

System.out.println(s.replaceAll(".","#"));

System.out.println(s.replaceFirst(".","#"));

}

結果

123#dwe#123

###########

#23.dwe.123

5.String對“+”的重載

String s = "a" + "b",編譯器會進行常量摺疊(因為兩個都是編譯期常量,編譯期可知),即變成 String s = "ab"

對於能夠進行優化的(String s = "a" + 變量 等)用 StringBuilder 的 append() 方法替代,最後調用 toString() 方法 (底層就是一個 new String())

6.字符串拼接

String作為不可變類,一旦被實例化就無法修改,所謂的字符串拼接就是重新生成一個新的字符串。

拼接字符串的方法

1)“+”拼接字符串("+"是Java提供的一個語法糖,Java本身是不支持運算符重載的)

public static void main(String[] args) {

String s = "你好";

s +="智障";

System.out.println(s);

}

2)concat

public static void main(String[] args) {

String s = "你好";

String ss = "智障";

String a = s.concat(",").concat(ss);

System.out.println(a);

}

3)StringBuffer

public static void main(String[] args) {

StringBuffer s = new StringBuffer("你好");

s.append(",").append("啊哈");

System.out.println(s);

}

4)StringBuilder(和Stringbuffer用法類似,但是StringBuffer是線程安全的,StringBuilder不是)

5)StringUtils.join( apache.commons.lang中提供的StringUtils類,其中的join方法可以拼接字符串。同時Java8的String類中也提供了一個join的方法用法和StrringUtils中的join方法類似)

public static void main(String[] args) {

String s = "ss";

String a = "aa";

System.out.println(StringUtils.join(s,",",a));

}


不建議在循環體中使用“+”拼接字符串,因為“+”的原理是每次new一個StringBuilder對象,然後進行append操作,最後通過toString()方法返回String對象,造成內存資源的浪費


效率 StringBuilder

由於字符串拼接過程中會創建新的對象,所以如果要在一個循環體中進行字符串拼接,就要考慮內存問題和效率問題。


1)、如果不是在循環體中進行字符串拼接的話,直接使用+就好了。

2)、如果在併發場景中進行字符串拼接的話,要使用StringBuffer來代替StringBuilder。


7.不同方法轉成String型的區別


1.int i = 5;

2.String i1 = "" + i;

3.String i2 = String.valueOf(i);

4.String i3 = Integer.toString(i);

第三行和第四行沒有任何區別,因為String.valueOf(i)也是調用Integer.toString(i)來實現的

第二行代碼其實是String i1 = (new StringBuilder()).append(i).toString();,首先創建一個StringBuilder對象,然後再調用append方法,再調用toString方法。

8.switch對String的支持

其實switch只支持一種數據類型,那就是整型,其他數據類型都是轉換成整型之後在使用switch的。 字符串的switch是通過equals()和hashCode()方法來實現的。會將字符串先轉成hashcode,再進行equals判斷。 而對char類型進行比較的時候,實際上比較的是ascii碼,編譯器會把char型變量轉換成對應的int型變量。

9.字符串池

Java中有兩種 創建字符串對象的方式,一是採用字面量的方式賦值二是通過new關鍵字新建一個字符串對象,

當採用字面量賦值的方式時,JVM會先去字符串池中查找是否存在這個對象,如果不存在,則在字符串池中新建這個對象,然後將這個對象的引用地址返回給字符串常量,這樣對應的字符串常量會指向池中這個字符串對象;如果存在,則不創建任何對象,直接將這個對象的地址返回,賦給字符串常量

採用new關鍵字新建一個字符串對象時,JVM首先在字符串池中查找有沒有這個字符串對象時,如果有,則不在池中再去創建這個對象,直接在堆中new一個對象,然後將堆中的這個對象的地址返回 ,如果沒有,則首先在字符串池中創建一個對象,然後再在堆中創建一個字符串對象,然後將堆中的這個對象的地址返回

字符串池的優缺點:字符串池的優點就是避免了相同內容的字符串的創建,節省了內存,省去了創建相同字符串的時間,同時提升了性能,缺點就是犧牲了JVM在常量池中遍歷對象所需要的時間,不過時間成本相對較低

10.靜態常量池和運行時常量池

靜態常量池就是class文件中的常量池,class文件中的常量池不僅僅包含字符串字面量,還包含類、方法的信息,佔用class文件絕大部分空間。這種常量池主要存放兩大類常量:字面量和符號引用量。而運行時常量池則是在jvm虛擬機完成類裝載操作後,將class文件中的常量池載入到內存中,並保存在方法區中。

示例:

public static void main(String[] args) {

String s1 = "Hello";

String s2 = "Hello";

String s3 = "Hel" + "lo";

String s4 = "Hel" + new String("lo");

String s5 = new String("Hello");

String s6 = s5.intern();

String s7 = "H";

String s8 = "ello";

String s9 = s7 + s8;

System.out.println(s1 == s2); // true

System.out.println(s1 == s3); // true

System.out.println(s1 == s4); // false

System.out.println(s1 == s9); // false

System.out.println(s4 == s5); // false

System.out.println(s1 == s6); // true

}

s1 == s3,s3雖然是動態拼接出來的字符串,但是所有參與拼接的部分都是已知的字面量,在編譯期間,這種拼接會被優化,編譯器直接幫你拼好,因此String s3 = "Hel" + "lo";在class文件中被優化成String s3 = "Hello",所以s1 == s3成立。 s1 == s4當然不相等,s4雖然也是拼接出來的,但new String("lo")這部分不是已知字面量,是一個不可預料的部分,編譯器不會優化,必須等到運行時才可以確定結果,結合

字符串不變定理地址肯定不同。

s1 == s9也不相等,雖然s7、s8在賦值的時候使用的字符串字面量,但是拼接成s9的時候,s7、s8作為兩個變量,都是不可預料的,編譯器畢竟是編譯器,不可能當解釋器用,不能在編譯期被確定,所以不做優化,只能等到運行時,在堆中創建s7、s8拼接成的新字符串,在堆中地址不確定,不可能與方法區常量池中的s1地址相同。s4 == s5,絕對不相等,二者都在堆中,但地址不同。 s1 == s6這兩個相等完全歸功於intern方法,s5在堆中,內容為Hello ,intern方法會嘗試將Hello字符串添加到常量池中,並返回其在常量池中的地址,因為常量池中已經有了Hello字符串,所以intern方法直接返回地址;而s1在編譯期就已經指向常量池了,因此s1和s6指向同一地址,相等。



分享到:


相關文章: