終於等到你
寫在前面
String 算是 Java 源碼中先要學習的,今天就從源碼的角度來重新認識一下
1.存儲結構
看主流的 JDK 版本 1.8 ,String 內部實際存儲結構爲 char 數組,源碼如下:
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
//其他內容......
2.常用方法
2.1.構造方法
其中 StringBuffer 和 StringBuilder 爲參數的構造函數用的比較少,但也要知道
/**
* String 爲參數的構造方法
* @param original
* A {@code String}
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
/**
* char[] 爲參數構造方法
* @param value
* The initial value of the string
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/**
* StringBuffer 爲參數的構造方法
* @param buffer
* A {@code StringBuffer}
*/
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
/**
* StringBuilder 爲參數的構造方法
* @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])//轉化爲字符數組,對比每個字符,有一個不相同就是 fasle
return false;
i++;
}
return true;
}
}
return false;
}
有一個和 equels 相似的方法 equalsIgnoreCase(String),忽略大小寫對比,傳參爲 String 類型
2.3.compareTo()
compareTo() 和 equels() 處理方式類似,都是字符對比,不同的是,equels() 比較兩字符串相同返回 true,不相同返回 false;compareTo() 比較兩字符串相同返回 0,不相同返回 其他 int 類型數值。並且 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():查詢字符串首次出現的下標位置
- lastIndexOf():查詢字符串最後出現的下標位置
- contains():查詢字符串中是否包含另一個字符串
- toLowerCase():把字符串全部轉換成小寫
- toUpperCase():把字符串全部轉換成大寫
- length():查詢字符串的長度
- trim():去掉字符串首尾空格
- replace():替換字符串中的某些字符
- split():把字符串分割並返回字符串數組
- join():把字符串數組轉爲字符串
3.常遇問題
3.1.String 和 StringBuilder、StringBuffer 的區別
String 是不可變的,在字符串拼接的時候使用 String 會很耗性能,因此有了 StringBuilder 和 StringBuffer,它們有 2 方法 append 和 insert 可以實現字符串拼接,唯一不同的是 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";
只有字符串是不可變時,我們才能實現字符串常量池,字符串常量池可以爲我們緩存字符串,提高程序的運行效率,如下圖所示:
3.3.JVM 中存儲
String 常用的 2 種創建方式,有 String a1 = “java” 和 String a2 = new Strring(“java”),但他們在內存中的存放方式不同,JDK1.8 中創建啊變量 a1,會先從常量池中找字符串 “java”,如果有直接返回,如果沒有則先在常量池中創建該字符串再返回,而變量 a2會直接在堆內存上創建,a2 調用方法 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 之後把永生代換成的元空間,把字符串常量池從方法區移到了 Java 堆上。
結束…
如果有哪些不對的地方煩請指認,先行感謝