寫在前面
String 算是 Java 源碼中先要學習的,今天就從源碼的角度來重新認識一下
1.存儲結(jié)構(gòu)
看主流的 JDK 版本 1.8 ,String 內(nèi)部實際存儲結(jié)構(gòu)為 char 數(shù)組外臂,源碼如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
//其他內(nèi)容......
2.常用方法
2.1.構(gòu)造方法
其中 StringBuffer 和 StringBuilder 為參數(shù)的構(gòu)造函數(shù)用的比較少,但也要知道
/**
* String 為參數(shù)的構(gòu)造方法
* @param original
* A {@code String}
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
/**
* char[] 為參數(shù)構(gòu)造方法
* @param value
* The initial value of the string
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/**
* StringBuffer 為參數(shù)的構(gòu)造方法
* @param buffer
* A {@code StringBuffer}
*/
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
/**
* StringBuilder 為參數(shù)的構(gòu)造方法
* @param builder
* A {@code StringBuilder}
* @since 1.5
*/
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
2.2.equals()
String 中 equals() 是比較兩個字符串的值是否相等,== 才是比較字符串的引用是否相等,equals() 重寫了父類 Object 方法锉屈,傳參也為 Object 類型,方法中會通過 instanceof 判斷垮耳,是 String 類型才進行下一步颈渊。
這里提一下遂黍,Object 父類中 equals() 和 == 對于引用類型的作用是一樣的。
/**
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
if (this == anObject) {// 對象引用相同直接返回 true
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])//轉(zhuǎn)化為字符數(shù)組,對比每個字符俊嗽,有一個不相同就是 fasle
return false;
i++;
}
return true;
}
}
return false;
}
有一個和 equels 相似的方法 equalsIgnoreCase(String)雾家,忽略大小寫對比,傳參為 String 類型
2.3.compareTo()
compareTo() 和 equels() 處理方式類似绍豁,都是字符對比芯咧,不同的是,equels() 比較兩字符串相同返回 true竹揍,不相同返回 false;compareTo() 比較兩字符串相同返回 0鬼佣,不相同返回 其他 int 類型數(shù)值驶拱。并且 compareTo() 只能接收 String 類型晶衷。
還有一個和 compareTo() 比較類似的方法 compareToIgnoreCase()阴孟,用于忽略大小寫后比較兩個字符串。
/**
* @param anotherString the {@code String} to be compared.
* @return the value {@code 0} if the argument string is equal to
* this string; a value less than {@code 0} if this string
* is lexicographically less than the string argument; and a
* value greater than {@code 0} if this string is
* lexicographically greater than the string argument.
*/
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
2.4.其他重要方法
- indexOf():查詢字符串首次出現(xiàn)的下標位置
- lastIndexOf():查詢字符串最后出現(xiàn)的下標位置
- contains():查詢字符串中是否包含另一個字符串
- toLowerCase():把字符串全部轉(zhuǎn)換成小寫
- toUpperCase():把字符串全部轉(zhuǎn)換成大寫
- length():查詢字符串的長度
- trim():去掉字符串首尾空格
- replace():替換字符串中的某些字符
- split():把字符串分割并返回字符串數(shù)組
- join():把字符串數(shù)組轉(zhuǎn)為字符串
3.常遇問題
3.1.String 和 StringBuilder锹漱、StringBuffer
String 是不可變的,在字符串拼接的時候使用 String 會很耗性能慕嚷,因此有了 StringBuilder 和 StringBuffer,它們有 2 方法 append 和 insert 可以實現(xiàn)字符串拼接喝检,唯一不同的是 StringBuffer 使用 synchronized 來保證線程安全
//StringBuffer 截取片段,具體可以看 StringBuffer 類源碼
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
//其他......
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public synchronized StringBuffer insert(int offset, Object obj) {
toStringCache = null;
super.insert(offset, String.valueOf(obj));
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public synchronized StringBuffer insert(int offset, String str) {
toStringCache = null;
super.insert(offset, str);
return this;
}
//其他......
//StringBuilder截取片段挠说,具體可以看 StringBuilder類源碼
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
//其他......
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder insert(int offset, Object obj) {
super.insert(offset, obj);
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder insert(int offset, String str) {
super.insert(offset, str);
return this;
}
//其他......
StringBuffer 保證線程安全,所以性能不是很高,JDK 1.5 就有了 StringBuilder
3.2.String 為什么用 final 修飾
使用 final 修飾的第一個好處是安全损俭;第二個好處是高效,例如
String s1 = "java";
String s2 = "java";
只有字符串是不可變時,我們才能實現(xiàn)字符串常量池杆兵,字符串常量池可以為我們緩存字符串,提高程序的運行效率琐脏,如下圖所示:
3.3.JVM 中存儲
String 常用的 2 種創(chuàng)建方式,有 String a1 = "java" 和 String a2 = new Strring("java"),但他們在內(nèi)存中的存放方式不同,JDK1.8 中創(chuàng)建啊變量 a1祭衩,會先從常量池中找字符串 “java”,如果有直接返回灶体,如果沒有則先在常量池中創(chuàng)建該字符串再返回,而變量 a2會直接在堆內(nèi)存上創(chuàng)建掐暮,a2 調(diào)用方法 intern() 會把字符串保存到常量池蝎抽,例如
String a1 = "java";
String a2 = new Strring("java");
String a3 = "a2.intern();
System.out.println(a1 == a2); // false
System.out.println(a1 == a3); // true
JVM 存儲位置如圖
PS:JDK 1.7 之后把永生代換成的元空間,把字符串常量池從方法區(qū)移到了 Java 堆上路克。
結(jié)束......
如果有哪些不對的地方煩請指認樟结,先行感謝