查看所有目錄
String類是我們?nèi)粘i_發(fā)中使用最頻繁的類之一神帅,曾今有人說String類用的好壞能評判你是否是一個合格的java程序員。
基礎(chǔ)知識:
String對象的存放位置:大家都知道java中的對象大都是存放在堆中的伟桅,但是String對象是一個特例,它被存放在常量池中。
當創(chuàng)建一個字面量String對象時,首先會去檢查常量池中這個對象的存在與否省撑。
java本地方法:一個本地方法就是一個java調(diào)用非java代碼的接口。該方法是非java實現(xiàn)俯在,由C或C++語言實現(xiàn)竟秫。形式是:
修飾符 native 返回值類型 本地方法名(); 如public native String intern();
在我們看java源碼時如果追溯到了本地方法,在java層面上就到頭了跷乐,如果需要更深層次的了解本地方法的實現(xiàn)肥败,就需要下載openjdk源碼然后看它是如何實現(xiàn)的了。有興趣的同學(xué)可以看如何查看java本地方法這篇文章愕提。
String類:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {}
可以看到String類是final修飾的不能被繼承拙吉,同時它實現(xiàn)了Serializable接口可以序列化和反序列化,實現(xiàn)了Comparable支持字符串的比較揪荣,實現(xiàn)了CharSequence接口說明它是一個字符序列。
成員變量:
private final char value[];//存儲字符串
private int hash; //字符串的hash code 默認是0
private static final long serialVersionUID = -6849794470754667710L;//序列化id
String對象的字符串實際是維護在一個字符數(shù)組中的往史。操作字符串實際上就是操作這個字符數(shù)組,而且這個數(shù)組也是final修飾的不能夠被改變仗颈。
構(gòu)造方法:
public String() {
this.value = "".value;
}
無參構(gòu)造方法,值為空串椎例“ぞ觯基本不用。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
參數(shù)String對象參數(shù)來構(gòu)造String對象订歪,該構(gòu)造函數(shù)經(jīng)常被用來做面試題脖祈。問new String("abc");共創(chuàng)建了幾個對象。答案是兩個刷晋,字面量"abc"創(chuàng)建一個對象放在常量池中盖高,new String()又創(chuàng)建一個對象放在堆中。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
通過整個char數(shù)組參數(shù)來構(gòu)造String對象眼虱,實際將參數(shù)char數(shù)組值復(fù)制給String對象的char數(shù)組喻奥。
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
截取入?yún)?shù)組的一部分來構(gòu)造String對象,具體哪一部分由offset和count決定捏悬,其中做了些參數(shù)檢查撞蚕,傳入非法參數(shù)會報數(shù)組越界異常StringIndexOutOfBoundsException
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
通過byte數(shù)組構(gòu)造String對象,將入?yún)yte數(shù)組中指定內(nèi)容过牙,用指定charsetName的字符集轉(zhuǎn)換后構(gòu)造String對象甥厦。
其中StringCoding.decode(charsetName, bytes, offset, length)是根據(jù)指定編碼對byte數(shù)組進行解碼纺铭,解碼返回char數(shù)組。
checkBounds(bytes, offset, length)是對參數(shù)進行檢查(源碼如下)刀疙,該方法是私有的只能在String類中調(diào)用舶赔。
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
該構(gòu)造方法與上述構(gòu)造方法類似,只不過這里的字符集是用Charset指定的庙洼,上述是用String指定的顿痪。Charset與charsetName是java中表示字符集的兩種不同形式。它們之間相互轉(zhuǎn)換如下:
字符串轉(zhuǎn)Charset對象:Charset charset = Charset.forName("UTF-8");
Charset對象轉(zhuǎn)字符串:String s = charset.displayName();
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
這兩個方法同上述兩個方法類似油够,上述是轉(zhuǎn)換byte數(shù)組中的部分數(shù)據(jù)構(gòu)造String對象蚁袭,這里是轉(zhuǎn)換全部byte數(shù)組構(gòu)造String對象。通過轉(zhuǎn)換byte數(shù)組構(gòu)造String對象在工作中還是挺常用的石咬。
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
通過byte數(shù)組揩悄,不指定字符集構(gòu)造String對象。實際要在 StringCoding.decode(bytes, offset, length);解碼byte數(shù)組的時候會構(gòu)造默認的字符編碼鬼悠,默認的也就是系統(tǒng)默認的可能過GBK删性,可能過UTF-8,也可能是其它』牢眩可通過-Dfile.encoding=UTF-8進行修改系統(tǒng)默認編碼蹬挺。
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
通過StringBuffer構(gòu)造String對象,StringBuffer內(nèi)部也是維護了一個char數(shù)組它掂,這里將StringBuffer數(shù)組中的內(nèi)容復(fù)制給String對象中的數(shù)組巴帮。而且StringBuffer是線程安全的,所以這里也加了synchronized塊保證線程安全虐秋。
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
通過StringBuilder構(gòu)造String對象榕茧,原理同StringBuffer一樣,只不過StringBuilder是線程不安全的客给,所在這里沒有加synchronized塊用押。基礎(chǔ)面試中面試官經(jīng)常詢問StringBuffer與StringBuilder的區(qū)別靶剑,有興趣的同學(xué)可以搜一下蜻拨。
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
乍一看不知道這個構(gòu)造函數(shù)是用來干嘛的,仔細分析就知道這個函數(shù)大有作用桩引。首先它同String(char[] value)函數(shù)相比多了個參數(shù)share官觅,雖然在方法本身沒有用到share,目前是只支持true阐污,注釋也說了不支持false休涤。這個方法定義成這樣應(yīng)該是為了同String(char[] value)進行區(qū)分。否則沒辦法構(gòu)成方法重載。再來看下這個方法的作用功氨。它是直接將參數(shù)的地址傳給了String對象序苏,這樣要比直接使用String(char[] value)的效率要高,因為String(char[] value)是逐一拷貝捷凄。有人會問這樣Stirng對象和參數(shù)傳過來的char[] value共享同一個數(shù)組忱详,不就破壞了字符串的不可變性。設(shè)計都也考慮到了跺涤,所以它設(shè)置了保護用protected修飾而沒有公開出去匈睁。所以從安全性角度考慮,他也是安全的桶错。在java中也有很多地方用到了這種性能好的航唆、節(jié)約內(nèi)存的、安全的構(gòu)造函數(shù)院刁。如replace糯钙、concat、valueOf等方法退腥。
其它方法
length:
public int length() {
return value.length;
}
獲取字符串的長度任岸,實際上就是返回內(nèi)部數(shù)組的長度。因為char數(shù)組被final修飾是不可變的狡刘,只要構(gòu)造完成char數(shù)組中的內(nèi)容長度都不會改變享潜,所以這里可以直接返回數(shù)組的長度。
isEmpty:
public boolean isEmpty() {
return value.length == 0;
}
判斷字符串是否為空嗅蔬。同理如果char的長度為0則表示字符串空米碰。
charAt:
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
獲取字符串中指定索引位置的字符。先判斷索引是否合法购城,不能小于0或是大于字符串長度。然后直接返回數(shù)組對應(yīng)位置的字符虐译。
codePointAt:
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
同charAt類似瘪板,獲取字符串指定索引位置的字符的代碼點。也就是將對應(yīng)位置的字符轉(zhuǎn)換成UniCode漆诽。
codePointBefore:
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);
}
獲取指定索引位置前一個位置的字符的代碼點侮攀。
codePointCount:
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);
}
獲取字符串代碼點個數(shù),是實際上的字符個數(shù)厢拭。length()方法返回的是使用的是UTF-16編碼的字符代碼單元數(shù)量兰英,不一定是實際上我們認為的字符個數(shù)。如 String str = “/uD835/uDD6B”供鸠,那么機器會識別它是2個代碼單元代理的1個代碼點"Z"畦贸,故而,length的結(jié)果是代碼單元數(shù)量2,而codePointCount()的結(jié)果是代碼點數(shù)量1薄坏。
getChars(char dst[], int dstBegin):
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
復(fù)制字符串中數(shù)組內(nèi)容到指定字符數(shù)組指定位置中趋厉。該方法并沒有范圍檢查,方法僅供內(nèi)部使用不對外公開胶坠。
getChars(int srcBegin, int srcEnd, char dst[], int dstBegin):
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);
}
獲取字符串中指定位置的字符到目標字符數(shù)組中君账。該方法為公有的,做了范圍檢查沈善∠缡可以看到對外提供的方法還是要嚴謹一些。在工作中也是一樣闻牡,內(nèi)部使用的可以稍微寬松一些净赴,對外提供的需要做嚴格的限制。
getBytes:
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
獲取字符串對應(yīng)的字節(jié)數(shù)組澈侠。將字符串編碼成byte數(shù)組并返回劫侧,其中前兩個方法是調(diào)用者指定字符集,后一個方法使用系統(tǒng)默認的字符集哨啃。
equals:
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;
}
重寫了Object類的equals方法烧栋,這里是比較兩個字符器的內(nèi)容是否完全相等。先判斷長度是否相等拳球,長度不相等字符串必然不相等审姓。然后再逐一比較每個對應(yīng)位置的字符是否相等,如果全部相等則返回true祝峻,否則返回false魔吐。
contentEquals(StringBuffer sb) :
public boolean contentEquals(StringBuffer sb) {
return SequencontentEquals((Charce)sb);
}
比較字符串與StringBuffer對象的內(nèi)容是否相等,調(diào)用了contentEquals(CharSequence cs) 方法莱找,該方法實現(xiàn)如下酬姆。
contentEquals(CharSequence cs) :
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;
}
比較字符串內(nèi)容與字符序列內(nèi)容是否相等。首先判斷是否為AbstractStringBuilder類型奥溺,AbstractStringBuilder有兩種實現(xiàn)方式辞色,StringBuffer(線程安全的)和StringBuilder(線程不安全的),如果是則判斷是否為StringBuffer類型浮定,此類判斷需要加鎖以保證線程安全相满,它們兩個都調(diào)用了nonSyncContentEquals方法進行判斷(見下)。其次判斷是否為String類型,因為String類也實現(xiàn)了CharSequence接口桦卒,如果是則調(diào)用String類的equals方法立美。最后如果是其它字符序列,則逐一比較字符數(shù)組中每個位置的字符是否相等方灾。
nonSyncContentEquals:
private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
char v1[] = value;
char v2[] = sb.getValue();
int n = v1.length;
if (n != sb.length()) {
return false;
}
for (int i = 0; i < n; i++) {
if (v1[i] != v2[i]) {
return false;
}
}
return true;
}
該方法為私有的供String類內(nèi)部使用建蹄,也使用了相同的邏輯,先判字符數(shù)組長度是否相等,再逐一進行比較躲撰。其實不論是String的equals方法针贬,contentEquals(CharSequence cs) 方法還是nonSyncContentEquals方法里面的比較邏輯都差不多,是否可以考慮將這類似的邏輯抽取出來單獨成立個方法呢拢蛋。
equalsIgnoreCase:
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
從方法名也可以看出桦他,該方法是忽略大小寫比較字符串內(nèi)容是否相同。先判斷兩個對象地址是否一樣谆棱,地址一樣內(nèi)容自然也一樣快压。再判斷長度,如果長度一樣再調(diào)用regionMatches方法進行比較(見后)垃瞧。這里用了&&邏輯運算符的斷路原理蔫劣,如果前一個判斷為假,后面的判斷就沒意義了个从。
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;
}
該方法是實現(xiàn)Comparable接口的方法脉幢,用于對字符串進行比較大小。邏輯是對兩個字符串中的數(shù)組進行逐位比較大小嗦锐,從第一位開始比較嫌松,大的字符串就大,如果相同就繼續(xù)向下比較奕污,直到比較出大小為止萎羔。這里取了兩個字符串中長度較小的作為循環(huán)次數(shù)。從源碼也可以看出字符串比較并不是我們表面上認為的先進行長度比較碳默,長度不一樣再進行每個位置的比較贾陷。
內(nèi)部內(nèi)
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
該內(nèi)部內(nèi)實際上就是String類定義的一個內(nèi)部比較器,私有的僅供內(nèi)部使用嘱根,用于進行忽略大小寫比較字符串是否相等髓废。CaseInsensitiveComparator(大小寫不敏感比較器),只看名字不看其具體實現(xiàn)也能大致看出來其作用该抒,可見起一個好的名字是多么的重要慌洪。比較邏輯是對每位字符逐一進行比較,如果不等則將字符轉(zhuǎn)換為對應(yīng)的大寫字符再進行比較柔逼,如果還不等再轉(zhuǎn)換為對應(yīng)的小寫進行比較,最后返回兩個字符的大小即為整個字符串的大小割岛。這里先轉(zhuǎn)換為大寫愉适,再轉(zhuǎn)換為小寫的目的是不是所有的字符都是用英文字母進行表示的,比如漢字等癣漆。
compareToIgnoreCase:
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
忽略大小寫比較兩個字符串的大小维咸,用到了上述內(nèi)部內(nèi)的比較邏輯。
regionMatches(int toffset, String other, int ooffset,int len):
public boolean regionMatches(int toffset, String other, int ooffset,
int len) {
char ta[] = value;
int to = toffset;
char pa[] = other.value;
int po = ooffset;
// Note: toffset, ooffset, or len might be near -1>>>1.
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false;
}
while (len-- > 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
區(qū)域比較,比較兩個字符串指定區(qū)域指定長度的內(nèi)容是否相等癌蓖。從指定區(qū)域瞬哼,開始逐一比較指定長度字符數(shù)組內(nèi)容是否相等。
regionMatches(boolean ignoreCase, int toffset,String other, int ooffset, int len) :
public boolean regionMatches(boolean ignoreCase, int toffset,
String other, int ooffset, int len) {
char ta[] = value;
int to = toffset;
char pa[] = other.value;
int po = ooffset;
// Note: toffset, ooffset, or len might be near -1>>>1.
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false;
}
while (len-- > 0) {
char c1 = ta[to++];
char c2 = pa[po++];
if (c1 == c2) {
continue;
}
if (ignoreCase) {
char u1 = Character.toUpperCase(c1);
char u2 = Character.toUpperCase(c2);
if (u1 == u2) {
continue;
}
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
continue;
}
}
return false;
}
return true;
}
該方法同上述方法類似租副,只是加了一個參數(shù)ignoreCase坐慰,是否忽略大小寫,如果忽略大小寫則還要將字符轉(zhuǎn)換成對應(yīng)的大寫用僧,小寫進行比較结胀。
startsWith(String prefix, int toffset):
public boolean startsWith(String prefix, int toffset) {
char ta[] = value;
int to = toffset;
char pa[] = prefix.value;
int po = 0;
int pc = prefix.value.length;
// Note: toffset might be near -1>>>1.
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
判斷字符串的子串是否以指定字符開始捅厂。如"HelloWorld"就是以指定前綴"Hello"開頭譬正。邏輯是先創(chuàng)建字符串與指定前綴的副本(目的是為了保護字符串和避免其它線程對字符串進行修改導(dǎo)致比較出錯)积暖,再從指定位置判斷指定長度监右,也就是前綴長度字符串內(nèi)容是否相等庶灿。
startsWith(String prefix):
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
同上述方法饲鄙,只不過該方法的指定位置是從字符串的第0個位置開始勺届。
endsWith:
public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}
判斷字符串是否以指定字符結(jié)尾廊散。調(diào)用了startsWith的判斷邏輯歹垫。
hashCode:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
重寫Object的hashCode方法剥汤。java中的hashCode有兩個作用。一:Object的hashCode返回對象的內(nèi)存地址县钥。二:對象重寫的hashCode配合基于散列的集合一起正常運行秀姐,這樣的散列集合包括HashSet、HashMap以及HashTable等若贮。對于大量的元素比較時直接比較equals效率低下省有,可先判斷hashCode再判斷equals,因為不同的對象可能返回相同的hashCode(如"Aa"和"BB"的hashCode就一樣),所以比較時有時需要再比較equals谴麦。hashCode只是起輔助作用蠢沿。為了使字符串計算出來的hashCode盡可能的少重復(fù),即降低哈希算法的沖突率匾效,設(shè)計者選擇了31這個乘數(shù)舷蟀。選31有兩個好處。1:31是一個不大不小的質(zhì)數(shù)面哼,是作為 hashCode 乘子的優(yōu)選質(zhì)數(shù)之一野宜,其它的像37、41魔策、43也是不錯的乘數(shù)選擇匈子。2:31可以被 JVM 優(yōu)化,31 * i = (i << 5) - i闯袒。計算hashCode的原理也很簡單虎敦,即用原h(huán)ashCode乘以31再加上char數(shù)組的每位值游岳。
indexOf(int ch):
public int indexOf(int ch) {
return indexOf(ch, 0);
}
獲取指定字符在字符串中第一次出現(xiàn)的索引位置。具體調(diào)用了indexOf(ch, 0) (見后)其徙。
indexOf(int ch, int fromIndex):
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1.
return -1;
}
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
for (int i = fromIndex; i < max; i++) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return indexOfSupplementary(ch, fromIndex);
}
}
獲取指定字符在字符串中指定位置后第一次出現(xiàn)的索引位置胚迫。邏輯是:先對開始索引位置fromIndex進行檢查,如果小于0則取0唾那,如果大于數(shù)組長度則待查找的結(jié)果不存在访锻,返回-1.如果fromIndex合法,再判斷待查找的字符是否是在兩個字節(jié)以內(nèi)通贞。ch < Character.MIN_SUPPLEMENTARY_CODE_POINT 這個條件非常重要朗若,是分界BmpCode的界限,Character.MIN_SUPPLEMENTARY_CODE_POINT這個數(shù)代表十進制中62355,剛好是2個字節(jié)昌罩。如果在兩個字節(jié)內(nèi)則遍歷字符數(shù)組找到即返回所引哭懈。待查找字符超過兩個字節(jié),則使用indexOfSupplementary(int ch, int fromIndex)方法進行查找茎用。該方法是拆分字符的高低位進行比較,int類型在java中占4個字節(jié)遣总,如果不是BmpCode代碼(2字節(jié)以內(nèi))點是ValidCodePoint(2字節(jié)到四字節(jié)),代碼點是有高兩位和低兩位轨功,這種類型的int轉(zhuǎn)化為字符時分開來處理旭斥,作為兩個字符。源碼如下古涧。
private int indexOfSupplementary(int ch, int fromIndex) {
if (Character.isValidCodePoint(ch)) {
final char[] value = this.value;
final char hi = Character.highSurrogate(ch);
final char lo = Character.lowSurrogate(ch);
final int max = value.length - 1;
for (int i = fromIndex; i < max; i++) {
if (value[i] == hi && value[i + 1] == lo) {
return i;
}
}
}
return -1;
}
lastIndexOf:
public int lastIndexOf(int ch) {
return lastIndexOf(ch, value.length - 1);
}
public int lastIndexOf(int ch, int fromIndex) {
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
int i = Math.min(fromIndex, value.length - 1);
for (; i >= 0; i--) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return lastIndexOfSupplementary(ch, fromIndex);
}
}
從后住前查找指定字符在字符串中第一次出現(xiàn)的位置垂券。原理同indexOf方法類似,這里就不贅述了羡滑。
public int indexOf(String str) {
return indexOf(str, 0);
}
public int indexOf(String str, int fromIndex) {
return indexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}
查找指定字符串在原字符串中第一次出現(xiàn)的索引位置菇爪。具體實現(xiàn)是調(diào)用了indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,int fromIndex)方法。實現(xiàn)如下柒昏。
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
char first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);
if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
該方法是保護的凳宙,只能在包內(nèi)調(diào)用。邏輯是(假設(shè)是從第0個位置開始找职祷,從其它位置開始邏輯類似):
1.遍歷當前字符串氏涩,找到當前字符串中和參數(shù)str字符串第一個字符相同的字符的位置記為i。
2.然后逐一比較接下來的每個字符是否相等有梆,如果相等則返回是尖,不等進行3
3.從原字符串第i個位置后找與str第一個字符相等的位置,再比較接下來的每個字符是否相等泥耀。
如此循環(huán)直到找到饺汹,或原字符串遍歷完成結(jié)束方法。
lastIndexOf(String str):
public int lastIndexOf(String str) {
return lastIndexOf(str, value.length);
}
從后往前查找字符串str在原字符串中第一次出現(xiàn)的索引位置爆袍。該系列方法同indexOf(String str)系列方法邏輯類似首繁。只不過查找順序是從后往前。
substring(int beginIndex):
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);
}
截取字符串的子串陨囊。截取從指定位置beginIndex開始(包含這個位置)弦疮,到字符串結(jié)束之間的字符串內(nèi)容。如果beginIndex=0則返回原串蜘醋,否則創(chuàng)建一個新的字符串返回胁塞。
substring(int beginIndex, int endIndex):
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);
}
截取原字符串的子串,同上個方法類似压语,上一個方法的結(jié)束位置是原串的結(jié)尾啸罢,這個方法是指定結(jié)束位置endIndex(不包含這個位置)。需要注意的是這個方法是包含頭不包含尾胎食。
subSequence:
public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}
截取指定區(qū)間內(nèi)的字符序列扰才。調(diào)用了substring方法,因為String本身就是一個CharSequence厕怜,所以這里可以直接返回衩匣。
concat:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
連接兩個字符串。先創(chuàng)建了一個新的字符數(shù)組復(fù)制了兩個字符串中的內(nèi)容粥航。然后通過String(char[] value, boolean share)創(chuàng)建結(jié)果字符串琅捏。注意這里用的是直接復(fù)制引用的方式而不是復(fù)制數(shù)組中字符的內(nèi)容來創(chuàng)建字符串,這可以提高效率递雀,前面寫字符串的構(gòu)造方法時也提到過柄延。創(chuàng)建的新的字符串,對原來的兩個字符串的內(nèi)容沒有影響缀程。
replace(char oldChar, char newChar):
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;
}
將字符串中所有的舊字符oldChar搜吧,替換為新的字符newChar。邏輯是:先找到字符串中第一次出現(xiàn)oldChar字符的位置i杠输。將之前的字符數(shù)組復(fù)制給新數(shù)組buf赎败,然后從i后將字符數(shù)組中的內(nèi)容復(fù)制給buf,只不過如果字符為oldCha則替換為newChar.然后再通過buf創(chuàng)建新的字符串返回蠢甲。
matches:
public boolean matches(String regex) {
return Pattern.matches(regex, this);
}
查找字符串是否包含指定正則規(guī)則的字符串僵刮。關(guān)于正則在java也是一個很有用知識點,有興趣的同學(xué)可以查一下鹦牛。
contains:
public boolean contains(CharSequence s) {
return indexOf(s.toString()) > -1;
}
判斷字符串中是否包含指定的字符序列搞糕。實際是調(diào)用indexOf方法,查找序列在字符串中的位置來判斷的曼追,如果不包含則查找的索引為-1.
replaceFirst:
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
替換第一個正則匹配項窍仰。需要注意一點,如果需要替換的內(nèi)容中包含反斜杠\需要用) in the replacement
replaceAll
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
替換所有的正則匹配項针史。同理新替換的內(nèi)容中包含反斜杠\需要用$代替。
split(String regex, int limit) :
public String[] split(String regex, int limit) {
char ch = 0;
if (((regex.value.length == 1 && //判斷參數(shù)長度是否為1
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) || //判斷參數(shù)不在特殊符號".$|()[{^?*+\\"中
(regex.length() == 2 && //判斷參數(shù)長度是否為2
regex.charAt(0) == '\\' && \\第一位為轉(zhuǎn)義符"\\"
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && //第二位不是0-9之間 '0'轉(zhuǎn)換為int為48 '9'轉(zhuǎn)換為int為57
((ch-'a')|('z'-ch)) < 0 && //判斷不在 a-z之間
((ch-'A')|('Z'-ch)) < 0)) && //判斷不在A-Z之間
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE)) //判斷分隔符不在特殊符號中
{
int off = 0;//當前索引
int next = 0;//下一個分割符出現(xiàn)的索引
boolean limited = limit > 0;//只分割前l(fā)imit份還是全部分割,limit=0代表全部分割
ArrayList<String> list = new ArrayList<>();//創(chuàng)建一個集合碟狞,用于存放切割好的子串
while ((next = indexOf(ch, off)) != -1) {//判斷是否包含下個分隔符啄枕,如果有則進入循環(huán)
if (!limited || list.size() < limit - 1) {//判斷是全部分割或當前分割次數(shù)小于總分割次數(shù)
list.add(substring(off, next));//切割當前索引到下一個分隔符之間的字符串并添加到list中
off = next + 1; //繼續(xù)切割下一下子串
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));//切割當前索引到字符串結(jié)尾的子字符串并添加到list
off = value.length;//將當前索引置為字符串長度
break;//結(jié)束循環(huán)
}
}
// If no match was found, return this
if (off == 0) //如果找不到分隔符則返回只有本字符串的數(shù)組
return new String[]{this};
// Add remaining segment
if (!limited || list.size() < limit)//如果是全部分割,或者沒有達到分割數(shù)族沃,則追加最后一項
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];//創(chuàng)建對應(yīng)長度數(shù)組频祝,因為返回結(jié)果為字符串數(shù)組
return list.subList(0, resultSize).toArray(result);//集合轉(zhuǎn)數(shù)組并返回
}
return Pattern.compile(regex).split(this, limit);//其它情況用正則的切割規(guī)則去切割
}
根據(jù)指定規(guī)則切割原字符串。如 "abc,def,ghi".split(",")則返回包含"abc","def","ghi"三個字符串元素的字符串數(shù)組脆淹。源碼分析直接寫在源碼中常空。
split:
public String[] split(String regex) {
return split(regex, 0);
}
根據(jù)指定規(guī)則切割字符串,切割全部子串盖溺。
join:
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// Number of elements not likely worth Arrays.stream overhead.
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
public static String join(CharSequence delimiter,
Iterable<? extends CharSequence> elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
join方法是jdk1.8之后新加的方法漓糙,作用是將字符序列數(shù)組,或是字符序列集合通過分割符delimiter連接成一個字符串烘嘱。提供這兩個實現(xiàn)原理差不多兼蜈,第一個方法使用的可變參數(shù),第二個方法使用的可迭代參數(shù)拙友,這樣設(shè)計主要是為了讓方法更好用为狸,參數(shù)可以是一個數(shù)組也可以是一個集合。再來看下原理遗契。
通過遍歷數(shù)組和集合將數(shù)組元素或集合元素添加到StringBuilder辐棒,添加前會先加入一個分割符delimiter,然后將StringBuilder中的內(nèi)容返回,具體如下:
//1.StringJoiner的add方法,使用了方法調(diào)用鏈的方式牍蜂,返回對象本身漾根,可重復(fù)使用add方法。
public StringJoiner add(CharSequence newElement) {
prepareBuilder().append(newElement);//調(diào)用prepareBuilder()包含之前添加的元素和新加入一個分割符鲫竞,然后再append添加新的元素
return this;
}
//2.StringJoiner的prepareBuilder方法,內(nèi)部維護了一個StringBuilder
private StringBuilder prepareBuilder() {
if (value != null) {
value.append(delimiter);//每次調(diào)用這個方法時會,往StringBuilder中添加分割符delimiter
} else {
value = new StringBuilder().append(prefix);//第一次調(diào)用時創(chuàng)建StringBuilder對象
}
return value;//返回StringBuilder對象辐怕,以便下次調(diào)用的時候操作的是同一個StringBuilder
}
大小寫轉(zhuǎn)換函數(shù):
public String toLowerCase() {
return toLowerCase(Locale.getDefault());
}
public String toUpperCase() {
return toUpperCase(Locale.getDefault());
}
從名字可以看出這兩個函數(shù)是對字符串進行大小寫轉(zhuǎn)換的,需要注意的是只是針對英文字母[a-z][A-Z]轉(zhuǎn)換有效从绘,其它字符轉(zhuǎn)換無效寄疏。
trim:
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
去掉字符串兩端的空白字符,空白字符包括僵井,空格陕截、tab、回車符批什。邏輯:
1.從左到右循環(huán)字符數(shù)組农曲,若字符為空字符,則繼續(xù)循環(huán)驻债,直到第一個不為空的字符記錄其位置st乳规。
2.從右到左循環(huán)字符數(shù)組形葬,若字符為空字符,則繼續(xù)循環(huán)暮的,直到第一個不為空的字符記錄其位置len荷并。
3.截取字符串中從st到len位置的子串。
toString:
public String toString() {
return this;
}
返回字符串對象的字符串形式青扔,實際上就是返回他本身。
toCharArray:
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
將字符串轉(zhuǎn)換為字符數(shù)組返回翩伪。將字符串中維護的字符數(shù)組復(fù)制一份返回微猖。這里有兩點需要注意的地方:
1.這里不能直接返回內(nèi)部字符數(shù)組value,如果直接返回 value缘屹,返回的數(shù)組(假設(shè)為chArray)與value指向同一個地址凛剥,一旦你修改了 chArray數(shù)組的內(nèi)容,value所指向的內(nèi)容也隨之改變轻姿,這樣破壞了String的不變性犁珠。
2.源碼中有一行注釋:Cannot use Arrays.copyOf because of class initialization order issues(由于類初始化順序問題,無法使用ARARY.COSTOFF)互亮,這里我猜測是這樣的犁享,字符串比Arrays先初始化完成,但是在JDK中存在其它對象使用了toCharArray方法豹休,而這個對象比String對象初始化晚炊昆,但比Arrays對象初始化早,導(dǎo)致使用時Arrays未初始化完成從而報錯威根。故這里有了這個注釋凤巨,而使用 System.arraycopy則不會存在這樣的問題,因為這個方法是本地方法洛搀。
format
public static String format(String format, Object... args) {
return new Formatter().format(format, args).toString();
}
public static String format(Locale l, String format, Object... args) {
return new Formatter(l).format(format, args).toString();
}
用于創(chuàng)建格式化的字符串以及連接多個字符串對象敢茁。熟悉C語言的同學(xué)應(yīng)該記得C語言的sprintf()方法,兩者有類似之處留美。這里給出了兩種重載形式彰檬,第一種使用本地語言環(huán)境,第二種使用指定的語言環(huán)境谎砾。
如:String.format("Hi,%s:%s.%s", "z3","l4","w5");返回Hi:z3,l4,w5
valueOf系列:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[]) {
return new String(data);
}
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
public static String copyValueOf(char data[]) {
return new String(data);
}
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(char c) {
char data[] = {c};
return new String(data, true);
}
public static String valueOf(int i) {
return Integer.toString(i);
}
public static String valueOf(long l) {
return Long.toString(l);
}
public static String valueOf(float f) {
return Float.toString(f);
}
public static String valueOf(double d) {
return Double.toString(d);
}
valueOf系列僧叉,將傳入的參數(shù),轉(zhuǎn)換成各自對應(yīng)的字符串對象棺榔。需要注意兩點:
1.對于對象如果是null則返回字符串"null".
2.對于boolean類型真返回"true",假返回"false"
intern:
public native String intern();
最后String還有一個intern方法瓶堕,這個方法是本地方法,無方法體症歇。
jdk1.7之前intern方法執(zhí)行后如果在常量池找不到對應(yīng)的字符串郎笆,則會將字符串拷貝到常量池谭梗,然后返回常量池中的引用。
jdk1.7之后intern方法執(zhí)行后如果在常量池找不到對應(yīng)的字符串宛蚓,則會在常量池中生成一個對原字符串的引用激捏。
原來在常量池中找不到時,復(fù)制一個副本放到常量池凄吏,1.7后則是將在堆上的地址引用復(fù)制到常量池远舅。
實際上在常量池中有一個對象StringTable,可以看作是一個HashSet痕钢。使用StringTable來維護所有存活的字符串的一個對象图柏。
使用String的intern方法可以節(jié)省內(nèi)存。在某些情況下可以使用 intern() 方法任连,它能夠使內(nèi)存中的不同字符串都只有一個實例對象蚤吹。
看下面的例子:
public void testIntern() {
String str2 = new String("hello") + new String("world");
str2.intern();// 使用intern方法后str2與str1指向同一個對象,否則它們指向兩個不同的對象随抠。這樣就能達到節(jié)省內(nèi)存的效果裁着。
String str1 = "helloworld";
System.out.println(str2 == str1);
}