Java String類的分析

1.String的特殊性

1.1對象的創建

public class Test {
	public static void main(String[] args) {
		String str1 = new String("abcd");
		String str2 = "abcd";
	}
}

1.1.1下面解釋對象創建過程:

Java 對String 類的特殊管理:

>Java的每一個類都有一個常量池,這個常量池定義在class文件中有描述(javap -v 類的全類名),包括值、標識符(舉個例子:String a ="astr";int b = 1;這些代碼中的 標識符a,b和值 “astr” 都是常量池的內容,而1 則會嵌入的操作指令中)、屬性名、類名、方法名等。
>每個JVM實例同時會在方法區維持一個String pool,在裝載每個類時的會解析類的常量池,先在javaheap內創建這些這些常量對象返回其引用然後=》將這些字符串常量的引用存儲到String pool中=》最後將這些引用給每個類的常量池。
> ==是比較地址,String 類重寫了 equals()方法,比較對象內容(比較的是底層的char[] 內每一個字符是否相等)
> 個人猜測:string pool中存的是引用值而不是具體的實例對象,具體的實例對象是在堆中開闢的一塊空間存放的。但是怎麼驗證還沒有教好的思路。

1.1.1.1 String str1 = "abcd"的創建過程:

實現過程:

    >首先棧區創建str1引用,然後在String池中尋找指向的內容爲"abcd"的對象的引用,如果String池中沒有,則javaheap創建一個對象返回引用到String Pool,最後返回指向String池中的引用賦值給str1;如果有,則直接返回引用賦值給str1;

推論及驗證:

    >如果後來又定義了字符串變量 str2 = "abcd",則直接將str2引用指向String池中已經存在的“abcd”,不再重新創建對象;這時str1==str2。

但是需要注意的一點是::

    >Java 語言提供了 字符串連接符號("+")以及將其他對象轉換爲字符串的特殊支持。
        >字符串連接 底層是通過 StringBuilder(或 StringBuffer)類及其 append 方法實現的(jjava heap創建buffer或者builder對象,然後append返回);
        >字符串轉換是通過 toString 方法實現的,該方法由 Object 類定義,並可被 Java 中的所有類繼承。

注意點的驗證:

        >如果內容爲"abc"的str2進行了字符串的"+"連接str2 = str2+"d";此時str2指向的是在堆中新建的內容爲"abcd"的對象,即此時進行str1==str2,返回值false,因爲地址不一樣。

1.1.1.2 String str = new String("abcd")的創建過程:

實現過程:

>直接在堆中創建對象返回引用。如果後來又有String str3 = new String("abcd"),str3不會指向String pool裏面的對象,而是在堆中重新創建一個對象並指向它。

驗證方式:

>如果此時進行str2==str3以及str1 == str3 均會返回false,因爲兩個對象的地址不一樣,如果是str2.equals(str3),返回true,因爲內容相同。

注意的一點:

>str.intern()這個方法就是將str指向的String對象內容,存儲一份到String pool裏並返回在String pool裏的“引用”;

1.2String對象的不可性(immutable)

1.2.1 爲什麼設計爲final的?

因爲String類的創建銷燬等涉及到JVM的機制,一般程序員不具備這個能力。

1.2.2我們無法利用String提供的API來改變原對象的內容

public class Base {

	public static void main(String[] args) {
		String str1 = new String("abcd");
		String str2 = "abcd";
		String str3 = "abcd";
		String str4 = new String("abc") + "d";// 連接 請勿直接使用 "abc"+"d" 會被編譯器有化成  "abcd"

		System.out.println(str2 == str1);// false
		System.out.println(str2 == str3);// true
		System.out.println(str2 == str4);// false
		System.out.println(str2 == str1.intern() && str2 == str3.intern() && str2 == str4.intern());// true 都是String pool裏面的引用

		String str5 = str1.replace("a", "z");// 替換字符
		System.out.println(str1);
		System.out.println(str5);

		String str6 = str1.substring(2);// 截取字符
		System.out.println(str1);
		System.out.println(str6);

		String str7 = str1.toLowerCase();// 轉小寫
		System.out.println(str1);
		System.out.println(str7);

		String str8 = str1.toUpperCase();// 轉大寫
		System.out.println(str1);
		System.out.println(str8);

		String str9 = str1.trim();// 去除兩端空格字符
		System.out.println(str1);
		System.out.println(str9);
	}

}

如以上等等的方法均未改變str1的值及其指向String對象的值。因爲所有的API 相關的操作都是對底層的char[] value進行deepcopy後進行的操作

1.2.3 但是反射可以做到

public class Test {
	public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		String str1 = new String("abcd");
		//here is an way to change str1
		Class clazz = str1.getClass();
		Field valueField = clazz.getDeclaredField("value");
		valueField.setAccessible(true);
		char [] str1Changed = new char[]{'z','x','c','v'};
		valueField.set(str1, str1Changed);
		System.out.println(str1);//zxcv
	}
}

這是利用反射修改str1指向對象的內容

1.2.4不可變對象的優勢

1 天生的線程安全性(只能讀取不能修改)
2 在性能上的提升(可緩存,不必每次都要申請內存初始化等從而提升性能)

2.關於字符及編碼及亂碼

String:"字符"串,這個字符就是我們人通常理解的抽象的符號(例如"a","b","中"等),但是在計算機是無法存儲這種抽象的符號(只能存儲數值),只能依靠數值與符號的映射關係(編碼字符集)來解決數值和符號的對應關係然後顯示字符。 計算機上很常見的顯示亂碼一般有下面幾個原因:

第一個字節序列本身有問題(文件破壞掉了,這個情況較少,而且基本無解);
第二個就是我們解碼的方式不對(這種常見,本身是UTF-8格式編碼的,我們卻以GBK的形式解碼,修改解碼方式),
第三個就是我們缺少對應的顯示方法(這種也常見,文件是UTF-8的我們以UTF-8解碼,但是其中某些字碼對應國外的文字符號,我們缺少顯示方法,解決方法爲安裝缺少對應字符集),

參考如下代碼:

public class Test {
	public static void main(String[] args) throws UnsupportedEncodingException {
		String str = "wo是中國人";//默認編碼格式UTF-8
		byte [] gbkbytes = str.getBytes("GBK");//進行GBK編碼
		byte [] utf8bytes = str.getBytes("UTF-8");//進行UTF-8編碼
		System.out.println(gbkbytes.length);
		assert gbkbytes.length == 10;//1英2中
		System.out.println(utf8bytes.length);
		assert utf8bytes.length == 14;//1英三中
		//對gbkbytes 進行 UTF8解碼
		System.out.println(new String(gbkbytes, "UTF-8"));//wo���й�
		//對utf8bytes 進行GBK解碼
		System.out.println(new String(gbkbytes, "GBK"));//wo是中國人
	}
	
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章