搜尋
首頁JavaJava基礎洞察 String字串

洞察 String字串

Dec 28, 2020 pm 05:27 PM

java基礎教學欄位介紹洞察String字串

洞察 String字串

#推薦(免費):java基礎教學

#實作原理

##在Java6 以及在先前的版本中,String 物件是對char 陣列進行了封裝實作的對象,主要有四個成員變數:char 陣列、偏移量offset、字元數量count、雜湊值hash。

從 Java7 版本開始到 Java8 版本,String 類別中不再有 offset 和 count 兩個變數了。這樣的好處是 String 物件佔用的記憶體稍微少了些。

從 Java9 版本開始,將 char[]字段改為 byte[]字段,又維護了一個新的屬性 coder,它是一個編碼格式的標識。

一個 char 字元佔 16 位,2 個位元組。這個情況下,儲存單字節編碼內的字元(佔一個位元組的字元)就顯得非常浪費。 JDK1.9 的 String 類別為了節省記憶體空間,於是使用了佔 8 位,1 個位元組的 byte 陣列來存放字串。

而新屬性 coder 的作用是,在計算字串長度或使用 indexOf()函數時,我們需要根據這個字段,判斷如何計算字串長度。 coder 屬性預設有 0 和 1 兩個值,0 代表 Latin-1(單字節編碼),1 代表 UTF-16。如果 String 判斷字串只包含了 Latin-1,則 coder 屬性值為 0,反之則為 1。

不可變

查看String類別的程式碼可以發現,String類別被final關鍵字修飾,因此這個類別不能被繼承,並且String類別裡面的變數char 陣列也被final 修飾了,因此String物件不能被修改。

String物件不可變主要有以下幾個優點:

第一,保證 String 物件的安全性。假設 String 物件是可變的,那麼 String 物件將可能被惡意修改。

第二,保證 hash 屬性值不會頻繁變更,確保了唯一性,使得類似 HashMap 容器才能實現對應的 key-value 快取功能。

第三,可以實作字串常數池。

在Java 中,通常有兩種創建字串物件的方式:

第一種是透過字串常數的方式創建,如

String str = "abc"

第二種是字串變數透過new 形式的創建,如

String str = new String("abc")

當程式碼中使用第一種方式建立字串物件時,在編譯類別檔案時,」abc」常數字串將會放入到常數結構中,在類別載入時,「abc」將會在常數池中建立;然後,str將引用常數池中的字串物件。這種方式可以減少同一個值的字串物件的重複創建,節約記憶體。

String str = new String("abc") 這種方式,首先在編譯類別檔案時,」abc」常數字串將會放入到常數結構中,在類別載入時,「abc」將會在常數池中建立;其次,在呼叫new時,JVM 指令將會呼叫String 的建構函數,String 物件中的char 陣列將會引用常數池中」abc」字符串的char 數組,在堆內存中創建一個String 對象;最後,str 將引用String 對象,String對象的引用跟常數池中”abc”字符串的引用是不一樣的。

物件與引用:物件的內容儲存在記憶體中,作業系統透過記憶體位址來找到儲存的內容,引用就是指記憶體的位址。

例如:

String str = new String("abc"),變數str指向的是String物件的儲存位址,也就是說 str 並不是對象,而只是一個物件參考。

字串拼接

#常數相加##

String str = "ab" + "cd" + "ef";
查看編譯後的字節碼

0 ldc #2 <abcdef>2 astore_13 return</abcdef>

可以發現編譯器將程式碼最佳化成如下所示

String str= "abcdef";

變數相加##

String a = "ab";String b = "cd";String c = a + b;
查看編譯後的字節碼
 0 ldc #2 <ab>
 2 astore_1 3 ldc #3 <cd>
 5 astore_2 6 new #4 <java>
 9 dup10 invokespecial #5 <java>>13 aload_114 invokevirtual #6 <java>17 aload_218 invokevirtual #6 <java>21 invokevirtual #7 <java>24 astore_325 return</java></java></java></java></java></cd></ab>
可以發現,Java在進行字串相加的時候,底層使用的是StringBuilder,程式碼被最佳化成如下所示:

String c = new StringBuilder().append("ab").append("cd").toString();

String.intern

String a = new String("abc").intern();String b = new String("abc").intern();System.out.print(a == b);
輸出結果:
true
在字串

常數

中,預設會將物件放入常量池。例如:

String a = "123"

在字符串变量中,对象是会创建在堆内存中,同时也会在常量池中创建一个字符串对象,String 对象中的 char 数组将会引用常量池中的 char 数组,并返回堆内存对象引用。例如:String b = new String("abc")

如果调用 intern 方法,会去查看字符串常量池中是否有等于该对象的字符串的引用,如果没有,在 JDK1.6 版本中会复制堆中的字符串到常量池中,并返回该字符串引用,堆内存中原有的字符串由于没有引用指向它,将会通过垃圾回收器回收。

在 JDK1.7 版本以后,由于常量池已经合并到了堆中,所以不会再复制具体字符串了,只是会把首次遇到的字符串的引用添加到常量池中;如果有,就返回常量池中的字符串引用。

下面开始分析上面的代码块:

在一开始字符串”abc”会在加载类时,在常量池中创建一个字符串对象。

创建 a 变量时,调用 new Sting() 会在堆内存中创建一个 String 对象,String 对象中的 char 数组将会引用常量池中字符串。在调用 intern 方法之后,会去常量池中查找是否有等于该字符串对象的引用,有就返回常量池中的字符串引用。

创建 b 变量时,调用 new Sting() 会在堆内存中创建一个 String 对象,String 对象中的 char 数组将会引用常量池中字符串。在调用 intern 方法之后,会去常量池中查找是否有等于该字符串对象的引用,有就返回常量池中的字符串引用。

而在堆内存中的两个String对象,由于没有引用指向它,将会被垃圾回收。所以 a 和 b 引用的是同一个对象。

如果在运行时,创建字符串对象,将会直接在堆内存中创建,不会在常量池中创建。所以动态创建的字符串对象,调用 intern 方法,在 JDK1.6 版本中会去常量池中创建运行时常量以及返回字符串引用,在 JDK1.7 版本之后,会将堆中的字符串常量的引用放入到常量池中,当其它堆中的字符串对象通过 intern 方法获取字符串对象引用时,则会去常量池中判断是否有相同值的字符串的引用,此时有,则返回该常量池中字符串引用,跟之前的字符串指向同一地址的字符串对象。

以一张图来总结 String 字符串的创建分配内存地址情况:

使用 intern 方法需要注意的一点是,一定要结合实际场景。因为常量池的实现是类似于一个 HashTable 的实现方式,HashTable 存储的数据越大,遍历的时间复杂度就会增加。如果数据过大,会增加整个字符串常量池的负担。

判断字符串是否相等

// 运行环境 JDK1.8String str1 = "abc";String str2 = new String("abc");String str3= str2.intern();System.out.println(str1==str2); // falseSystem.out.println(str2==str3); // falseSystem.out.println(str1==str3); // true
// 运行环境 JDK1.8String s1 = new String("1") + new String("1");s1.intern();String s2 = "11";System.out.println(s1 == s2); // true , 如果不执行1.intern(),则返回false

String s1 = new String("1") + new String("1")会在堆中组合一个新的字符串对象"11",在s1.intern()之后,由于常量池中没有该字符串的引用,所以常量池中生成一个堆中字符串"11"的引用,此时String s2 = "11"返回的是堆字符串"11"的引用,所以s1==s2

在JDK1.7版本以及之后的版本运行以下代码,你会发现结果为true,在JDK1.6版本运行的结果却为false:

String s1 = new String("1") + new String("1");System.out.println( s1.intern()==s1);

StringBuilder与StringBuffer

由于String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。

和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的对象

StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

以上是洞察 String字串的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:learnku。如有侵權,請聯絡admin@php.cn刪除

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。