Java中String不是基本數(shù)據(jù)類型,而是一種特殊的類。
String代表的是不可變的字符序列(被final修飾),為不可變對(duì)象,一旦被創(chuàng)建,就不能修改它的值,對(duì)于已經(jīng)存在的String對(duì)象的修改都是重新創(chuàng)建一個(gè)新的對(duì)象,然后把新的值保存進(jìn)去。
源碼分析
屬性
String中有兩個(gè)較為重要的屬性:
private final char value[];
private int hash;
從value[]可以看出,String是通過(guò)字符數(shù)組的方式實(shí)現(xiàn)的。hash用于保存當(dāng)前字符串的hash值。
構(gòu)造方法
// 通過(guò)該構(gòu)造函數(shù)的String值為空字符串
public String()
// 使用字符串構(gòu)造函數(shù)
public String(String original)
// 字符串?dāng)?shù)組構(gòu)造函數(shù)
public String(char value[])
// 從傳入value數(shù)組中offset位置(包含offset)開始,截取count個(gè)字符構(gòu)造String
public String(char value[], int offset, int count)
// 基本同上
public String(int[] codePoints, int offset, int count)
// 下面兩個(gè)為過(guò)時(shí)構(gòu)造函數(shù)
public String(byte ascii[], int hibyte, int offset, int count)
public String(byte ascii[], int hibyte)
// 使用byte[]字節(jié)數(shù)組構(gòu)造String
public String(byte bytes[], int offset, int length, String charsetName)
public String(byte bytes[], int offset, int length, Charset charset)
public String(byte bytes[], String charsetName)
public String(byte bytes[], Charset charset)
public String(byte bytes[], int offset, int length)
public String(byte bytes[])
// 使用StringBuffer和StringBuilder構(gòu)造String
public String(StringBuffer buffer)
public String(StringBuilder builder)
/**
* 保護(hù)類型的構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)比較特別
* 1. 傳入share并未使用,share主要作用是為了和上面String(char[] value)做區(qū)別
* 2. String(char[] value)方法在創(chuàng)建String的時(shí)候會(huì)用到 會(huì)用到Arrays的copyOf方法將value中的內(nèi)容逐一復(fù)制到String當(dāng)中,而這個(gè)String(char[] value, boolean share)方法則是直接將value的引用賦值給String的value。那么也就是說(shuō),這個(gè)方法構(gòu)造出來(lái)的String和參數(shù)傳過(guò)來(lái)的char[] value共享同一個(gè)數(shù)組
**/
String(char[] value, boolean share){
this.value = value;
}
方法
charAt、codePointAt、codePointBefore、codePointCount、offsetByCodePoints
獲取并返回索引對(duì)應(yīng)的字符:
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
獲取并返回索引對(duì)應(yīng)字符的Unicode編碼:
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
獲取并返回給定索引前面的Unicode代碼點(diǎn):
public int codePointBefore(int index) {
int i = index - 1;
if ((i < 0) || (i >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointBeforeImpl(value, index, 0);
}
準(zhǔn)確計(jì)算unicode字符的數(shù)量:
public int codePointCount(int beginIndex, int endIndex) {
if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
throw new IndexOutOfBoundsException();
}
return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
}
獲取索引偏移后指定代碼點(diǎn)的索引:
public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > value.length) {
throw new IndexOutOfBoundsException();
}
return Character.offsetByCodePointsImpl(value, 0, value.length,
index, codePointOffset);
}
getBytes
在創(chuàng)建String的時(shí)候,可以使用byte[]數(shù)組,將一個(gè)字節(jié)數(shù)組轉(zhuǎn)換成字符串,同樣,我們可以將一個(gè)字符串轉(zhuǎn)換成字節(jié)數(shù)組,那么String提供了很多重載的getBytes方法。但是,值得注意的是,在使用這些方法的時(shí)候一定要注意編碼問(wèn)題。比如:
String s = "你好,世界!";
byte[] bytes = s.getBytes();
這段代碼在不同的平臺(tái)上運(yùn)行得到結(jié)果是不一樣的。由于我們沒(méi)有指定編碼方式,所以在該方法對(duì)字符串進(jìn)行編碼的時(shí)候就會(huì)使用系統(tǒng)的默認(rèn)編碼方式,比如在中文操作系統(tǒng)中可能會(huì)使用GBK或者GB2312進(jìn)行編碼,在英文操作系統(tǒng)中有可能使用iso-8859-1進(jìn)行編碼。這樣寫出來(lái)的代碼就和機(jī)器環(huán)境有很強(qiáng)的關(guān)聯(lián)性了,所以,為了避免不必要的麻煩,我們要指定編碼方式。如使用以下方式:
String s = "你好,世界!";
byte[] bytes = s.getBytes("utf-8");
replace、replaceAll、replaceFirst
- replace的參數(shù)是char和CharSequence,即可以支持字符的替換,也支持字符串的替換
- replaceAll和replaceFirst的參數(shù)是regex,即基于規(guī)則表達(dá)式的替換,比如,可以通過(guò)replaceAll(“\d”, “*”)把一個(gè)字符串所有的數(shù)字字符都換成星號(hào);
相同點(diǎn)是都是全部替換,即把源字符串中的某一字符或字符串全部換成指定的字符或字符串, 如果只想替換第一次出現(xiàn)的,可以使用 replaceFirst(),這個(gè)方法也是基于規(guī)則表達(dá)式的替換,但與replaceAll()不同的是,只替換第一次出現(xiàn)的字符串;
另外,如果replaceAll()和replaceFirst()所用的參數(shù)據(jù)不是基于規(guī)則表達(dá)式的,則與replace()替換字符串的效果是一樣的,即這兩者也支持字符串的操作;
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
split
按照字符regex將字符串分成limit份:
public String[] split(String regex, int limit) {
/* fastpath if the regex is a
(1)one-char String and this character is not one of the
RegEx's meta characters ".$|()[{^?*+\\", or
(2)two-char String and the first char is the backslash and
the second is not the ascii digit or ascii letter.
*/
char ch = 0;
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
return Pattern.compile(regex).split(this, limit);
}
equals、contentEquals、equalsIgnoreCase
equals:
- 如果兩個(gè)對(duì)象指向地址值一樣,就返回true;
- 判斷傳入類型是否為String類型
- 先判斷長(zhǎng)度是否一樣,在循環(huán)判斷每個(gè)字符是否相等
public boolean equals(Object anObject) {
if (this == anObject) {
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])
return false;
i++;
}
return true;
}
}
return false;
}
contentEquals:
接收StringBuffer對(duì)象,比較兩個(gè)內(nèi)容是否相等
public boolean contentEquals(StringBuffer sb) {
return contentEquals((CharSequence)sb);
}
public boolean contentEquals(CharSequence cs) {
// Argument is a StringBuffer, StringBuilder
if (cs instanceof AbstractStringBuilder) {
if (cs instanceof StringBuffer) {
synchronized(cs) {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
} else {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
}
// Argument is a String
if (cs instanceof String) {
return equals(cs);
}
// Argument is a generic CharSequence
char v1[] = value;
int n = v1.length;
if (n != cs.length()) {
return false;
}
for (int i = 0; i < n; i++) {
if (v1[i] != cs.charAt(i)) {
return false;
}
}
return true;
}
equalsIgnoreCase:忽略字符串大小寫進(jìn)行比較是否相等
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
compareTo:比較兩個(gè)字符串大小
compareTo
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;
}
subsString
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
intern
public native String intern();
該方法返回一個(gè)字符串對(duì)象的內(nèi)部化引用。 String類維護(hù)一個(gè)初始為空的字符串的對(duì)象池,當(dāng)intern方法被調(diào)用時(shí),如果對(duì)象池中已經(jīng)包含這一個(gè)相等的字符串對(duì)象則返回對(duì)象池中的實(shí)例,否則添加字符串到對(duì)象池并返回該字符串的引用。
相關(guān)面試題
如果自己寫一個(gè)String類,路徑與Java中String類路徑相同,會(huì)發(fā)生什么?
這題和Java的類加載機(jī)制有關(guān),Java采用雙親委派機(jī)制,如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把請(qǐng)求委托給父加載器去完成,依次向上,因此,所有的類加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器在它的搜索范圍中沒(méi)有找到所需的類時(shí),即無(wú)法完成該加載,子加載器才會(huì)嘗試自己去加載該類。
所有對(duì)于用戶自定義的String類,不會(huì)被加載,當(dāng)然,也不會(huì)被使用。
String,StringBuilder,StringBuffer的區(qū)別?
String是字符串常量,final修飾;StringBuffer字符串變量(線程安全);
StringBuilder 字符串變量(線程不安全).
- String和StringBuffer
String和StringBuffer主要區(qū)別是性能:String是不可變對(duì)象,每次對(duì)String類型進(jìn)行操作都等同于產(chǎn)生了一個(gè)新的String對(duì)象,然后指向新的String對(duì)象.所以盡量不在對(duì)String進(jìn)行大量的拼接操作,否則會(huì)產(chǎn)生很多臨時(shí)對(duì)象,導(dǎo)致GC開始工作,影響系統(tǒng)性能.
StringBuffer是對(duì)對(duì)象本身操作,而不是產(chǎn)生新的對(duì)象,因此在有大量拼接的情況下,建議使用StringBuffer.
但是需要注意現(xiàn)在JVM會(huì)對(duì)String拼接做一定的優(yōu)化:
String s = “This is only ” + ”simple” + ”test”
會(huì)被虛擬機(jī)直接優(yōu)化成String s=“This is only simple test”,此時(shí)就不存在拼接過(guò)程.
- StringBuffer和StringBuilder
StringBuffer是線程安全的可變字符串,其內(nèi)部實(shí)現(xiàn)是可變數(shù)組.StringBuilder是jdk 1.5新增的,其功能和StringBuffer類似,但是非線程安全.因此,在沒(méi)有多線程問(wèn)題的前提下,使用StringBuilder會(huì)取得更好的性能.
你對(duì)String對(duì)象的intern()熟悉么?
intern()方法會(huì)首先從常量池中查找是否存在該常量值,如果常量池中不存在則現(xiàn)在常量池中創(chuàng)建,如果已經(jīng)存在則直接返回.
比如 :
String s1=”aa”;
String s2=s1.intern();
System.out.print(s1==s2);//返回true
參考資料:
Java 7 源碼學(xué)習(xí)系列(一)——String
Java7為什么要修改substring的實(shí)現(xiàn)
JDK源碼分析之String篇