Java 字符串就是 Unicode 字符序列,Java 沒(méi)有內(nèi)置的字符串類型,而是在 Java 類庫(kù)中提供了一個(gè)預(yù)定義類 String,每個(gè)用雙引號(hào)括起來(lái)的字符串都是 String 類的一個(gè)實(shí)例。
Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared. For examples:[1]
String str = "abc";
char data[] = {'a', 'b', 'c'};
String str = new String(data);
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(orStringBuffer) class and itsappendmethod. String conversions are implemented through the methodtoString, defined byObjectand inherited by all classes in Java. For additional information on string concatenation and conversion, see Gosling, Joy, and Steele, The Java Language Specification.[1]
上面引用JDK 8 API,主要表明 Java 支持 + 進(jìn)行拼接字符串, Java 8中編譯器會(huì)調(diào)用 StringBuilder 或者 StringBuffer 中的 append 方法來(lái)進(jìn)行字符串拼接,然后通過(guò) toString 方法轉(zhuǎn)化為字符串。
源碼實(shí)現(xiàn)
JDK 8
String 類申明為 final ,不能有子類繼承,內(nèi)部定義了 char 數(shù)組進(jìn)行存儲(chǔ)字符串的值,并且用 final 進(jìn)行修飾,表明 String 是不可變的。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
// code
}
JDK 9
Java 9 中,String 類用 byte[] 數(shù)組進(jìn)行存儲(chǔ)字符串, 并且添加了 coder 標(biāo)識(shí)編碼方式。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/**
* The value is used for character storage.
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*
* Additionally, it is marked with {@link Stable} to trust the contents
* of the array. No other facility in JDK provides this functionality (yet).
* {@link Stable} is safe here, because value is never null.
*/
@Stable
private final byte[] value;
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private final byte coder;
}
底層用 byte 數(shù)組實(shí)現(xiàn)后,最大的好處就是可以省空間,因?yàn)楹芏嘧址姆秶荚?u00 - uFF 之間,只需要一個(gè) byte 就能存儲(chǔ),之前 char 數(shù)組 需要兩個(gè) byte 才能存儲(chǔ)。
字符串不可變
通過(guò)查看源碼我們可以知道 Java 中字符串是不可變的,具體的工作方式是 Java 語(yǔ)言的設(shè)計(jì)者將字符串放在一個(gè)公共的存儲(chǔ)池,字符串變量都指向性存儲(chǔ)池中相應(yīng)的位置, 這樣共享字符串常量可以提高效率,并且設(shè)計(jì)者認(rèn)為這種共享帶來(lái)的高效率勝于提取,拼接字符串帶來(lái)的低效率。
String Pool
- 字符串類在 Java 堆內(nèi)存中維護(hù)了一個(gè)字符串常量池,字符串常量池保存著所有字符串字面量(literal strings),目的是為了減少在jvm中創(chuàng)建的字符串的數(shù)量,這些字面量在編譯時(shí)期就確定,在運(yùn)行時(shí)可以通過(guò)
intern()方法將字符串添加到 String Pool中 。 - 當(dāng)創(chuàng)建 String 對(duì)象時(shí),jvm 會(huì)先檢查 String Pool 中是否存在相同的字符串,如果有則返回其引用,如果沒(méi)有就創(chuàng)建一個(gè)相應(yīng)的字符串放入String Pool 中(此過(guò)程為intern),再返回對(duì)應(yīng)的引用。
- 常量池:用于保存 Java 在編譯期就已經(jīng)確定的,已經(jīng)編譯的class文件中的一份數(shù)據(jù)。包括了類、方法、接口中的常量,也包括字符串常量,如String s = "a"這種聲明方式。
使用 new 創(chuàng)建 String 時(shí)創(chuàng)建了幾個(gè)對(duì)象:
String str = new String("hello");
兩個(gè),使用new 創(chuàng)建 String 時(shí),首先創(chuàng)建
hello字符串字面量(String literal)并將其放入字符串常量池中,然后在堆內(nèi)存中創(chuàng)建 String 對(duì)象
@HotSpotIntrinsicCandidate
public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
并且通過(guò)查看原碼得知,使用String帶參構(gòu)造創(chuàng)建字符串時(shí),不會(huì)完全復(fù)制 value 數(shù)組的內(nèi)容,只會(huì)指向同一個(gè)數(shù)組。
代碼示例:
String a1 = "a";
String b1 = "a";
System.out.println(a1 == b1); // true
String a2 = new String("a");
String b2 = new String("a");
System.out.println(a2 == b2); // false
String hello = "hello";
String lo = "lo";
System.out.println(hello == "hel" + "lo"); // true
System.out.println(hello == "hel" + lo); // false
String world = "world";
final String ld = "ld";
System.out.println(world == "wor" + ld); // true
總結(jié):
- 使用
""創(chuàng)建字符串時(shí),在編譯期將字符串放入String Pool中,字符串對(duì)象引用 String Pool 中的對(duì)象。 - 使用
new創(chuàng)建字符串時(shí),會(huì)在堆內(nèi)存中新創(chuàng)建 String 對(duì)象,在運(yùn)行時(shí)創(chuàng)建。 - 使用
String s = "hel" + "lo"創(chuàng)建的字符串指向 String Pool 中的字符串常量 "hello", 常量池中不會(huì)有 "hel" 和 "lo"。 - 使用包含變量的字符串連接符如
"hel" + lo創(chuàng)建的對(duì)象會(huì)存儲(chǔ)在堆中,運(yùn)行時(shí)期才創(chuàng)建;只要lo是變量,不論lo指向池常量池中的字符串對(duì)象還是堆中的字符串對(duì)象,運(yùn)行期"hel" + lo操作實(shí)際上是編譯器創(chuàng)建了StringBuilder對(duì)象進(jìn)行了append操作后通過(guò)toString()返回了一個(gè)字符串對(duì)象存在heap上 - 對(duì)于
final String ld = "ld"是一個(gè)用 final 修飾的變量,在編譯期就已經(jīng)確定了,所以"wor" + ld相當(dāng)于"wor" + "ld", 也指向 常量池中的 "world"。
intern()方法
When the intern method is invoked, if the pool already contains a string equal to this
Stringobject as determined by theequals(Object)method, then the string from the pool is returned. Otherwise, thisStringobject is added to the pool and a reference to thisStringobject is returned.It follows that for any two strings
sandt,s.intern() == t.intern()istrueif and only ifs.equals(t)istrue.
簡(jiǎn)單地說(shuō),intern 方法可以將當(dāng)前字符串對(duì)象添加到 String Pool 中(如果String Pool中不存在通過(guò)equals判斷相同的字符串),并返回其引用,示例代碼如下:
String str = "java";
String str2 = new String("java");
String str3 = str2.intern();
System.out.println(str == str2); // false
System.out.println(str == str3); // true
不可變(immutable)的好處
String 設(shè)計(jì)為不可變主要是從性能和安全方面進(jìn)行考慮
首先只有當(dāng)字符串是不可變時(shí)才能實(shí)現(xiàn)字符串常量池,從而節(jié)約 JVM 內(nèi)存,提高性能。
緩存 hashcode
/** Cache the hash code for the string */
private int hash; // Default to 0
閱讀源碼可知, String 不可變就可以緩存 hashcdoe 對(duì)應(yīng)的 hashcode, 以后每次使用該對(duì)象的 hashcode 時(shí)無(wú)需重新計(jì)算,直接返回即可,這使得字符串很適合作為 HashMap 的 Key,提高效率。
安全性
由于字符串是不可變的,所以用戶名,密碼之類的信息不能被修改,可以確保安全。同時(shí)也不會(huì)存在多線程安全問(wèn)題。
字符串拼接
+ 操作符
使用 + 進(jìn)行字符串拼接效率較低,執(zhí)行一次 String s += "hello";操作,相當(dāng)于
StringBuilder sb = new StringBuilder();
sb.append(str);
sb.appedn("hello");
s = sb.toString();
每次連接字符串都會(huì)構(gòu)建一個(gè)新的 StringBuilder 對(duì)象 ,既費(fèi)時(shí),又耗空間,多次操作不推薦。
concat
// JDK 11
public String concat(String str) {
int olen = str.length();
if (olen == 0) {
return this;
}
if (coder() == str.coder()) {
byte[] val = this.value;
byte[] oval = str.value;
int len = val.length + oval.length;
byte[] buf = Arrays.copyOf(val, len);
System.arraycopy(oval, 0, buf, val.length, oval.length);
return new String(buf, coder);
}
int len = length();
byte[] buf = StringUTF16.newBytesFor(len + olen);
getBytes(buf, 0, UTF16);
str.getBytes(buf, len, UTF16);
return new String(buf, UTF16);
}
查看源碼我們可以知道,concat 方法大致分為三步
- 創(chuàng)建 byte[] 數(shù)組
- 底層調(diào)用
System.arraycopy方法進(jìn)行數(shù)組拷貝 - 返回
new String(buf, coder);
多次調(diào)用會(huì)創(chuàng)建多個(gè) byte[] 數(shù)組 以及多個(gè) String 對(duì)象,多次調(diào)用也不推薦。
StringBuilder 和 StringBuffer
StringBuilder 和 Stringbuffer 都是 AbstractStringBuilder 的子類
// JDK 8
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
}
StringBuilder
// JDK 8
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
/** use serialVersionUID for interoperability */
static final long serialVersionUID = 4383685877147921099L;
// code ...
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
}
StringBuffer
// JDK 8
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
// source code ...
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
}
從上述的源碼中可以看到 StringBuffer 和 StringBuilde 都是調(diào)用其父類的 append 方法
abstract class AbstractStringBuilder implements Appendable, CharSequence {
// source code ...
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
}
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
// source code...
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
}
從源碼中可以看出 append 方法主要有一下三個(gè)步驟:
- 判斷入?yún)?str 是否為 null, 如果為 null, 在 value 數(shù)組中追加 "null"
- 將 value 數(shù)組進(jìn)行擴(kuò)容,基本的擴(kuò)容邏輯為 value 原來(lái)長(zhǎng)度 * 2 + 2,或者 count + str.length, 或者 Integer.MAX_VALUE - 8(減去 8 是因?yàn)橐恍┨摂M機(jī)會(huì)在數(shù)組中保留一些頭信息),取其中的最小值。
- 最后調(diào)用
System.arraycopy進(jìn)行字符串拷貝
append 操作大部分操作在擴(kuò)容與數(shù)組拷貝,不用進(jìn)行重復(fù)的 new 創(chuàng)建對(duì)象操作,因此效率較高。
StringBuilder 與 StringBuffer 的區(qū)別
從源碼中我們可以看到, StringBuffer 的 append 方法中多了 synchronized 關(guān)鍵字,因此它是線程安全的,但是效率較低, StringBuilder 線程不安全,效率較高。
格式化輸出
Java 5 沿用了 C 語(yǔ)言函數(shù)庫(kù)中的 printf 方法進(jìn)行輸出格式化,如
System.out.println("%8.2f", x);
// 增加分組分隔符
System.out.println("%,.2f", 10000.0 / 3.0); // 3,333.33
也可以使用靜態(tài)的 String.format 方法創(chuàng)建一個(gè)格式化的字符串,而不打印輸出
String message = String.format("Hello, %s.Next year, you'll be %d", name, age);
本篇文章同步在Github上,歡迎大家來(lái)Github多提issue。
Reference
- JDK 8 String
- 專題整理之—String的字符串常量池
- 專題整理之—不可變對(duì)象與String的不可變
- [Java核心技術(shù)·卷 I(原書(shū)第10版)](