文章結(jié)構(gòu):1.眾說紛紛的equals和==;2.字符串家族的基本知識以及字符串家族的源碼解讀套鹅;3.字符串家族的坑以及使用推薦站蝠;
一、眾說紛紛的equals和==:
總述:1. ==對于基本類型是比較其值卓鹿,對于引用類型是比較地址菱魔,地址也可以是一個基本類型的值,因此可認為就是比較值的吟孙。2.equals只能用于對象的比較豌习,是所有類的一個基本方法存谎。如果用equals來比較基本類型的變量是有語法錯誤的,equals只是比較對象的內(nèi)容肥隆。
(博主神經(jīng)的牛角尖:假設兩個對象用==比較既荚,兩個基本數(shù)據(jù)類型也用==比較,栋艳,它們是基于什么判斷的恰聘,博主想了好久,想不通吸占,以后有機會看編譯原理的時候再出一篇)晴叨。
例子解析(輸出什么?矾屯?當然要大家親自去編譯查看啦兼蕊,這才有好效果嘛。)
public class CompareTest {
public static void main(String[] args){
int t1=30;
int t2=90;
int t3=120;
int t4=120;
Boolean result1=(t1==t2); //驗證不同值的比較是否相等
Boolean result2=((t1+t2)==t3); //驗證基本數(shù)據(jù)類型只要數(shù)值相等即相等
Boolean result3=(t3==t4); //驗證基本數(shù)據(jù)類型直接相等即相等
System.out.println("--【t1==t2】"+result1+"-----【(t1+t2)=t3】"+result2+"-----【t3=t4】"+result3);
//另外博主驗證過了件蚕,只要在Integer緩存大兴锛肌(-128-127)以內(nèi),只要數(shù)值相等排作,還是相等的牵啦。覺得大家應該動手試下這個就不貼太多出來了。
Integer s1 = Integer.valueOf(t1); //把基本數(shù)據(jù)類型傳遞給Integer包裝類構(gòu)建成對象
Integer s2 = Integer.valueOf(t2);
Integer s3 = Integer.valueOf(t3);
Integer s4 = Integer.valueOf(t4);
Integer s5 = Integer.valueOf(130);
Integer s6 = Integer.valueOf(130);
Boolean b1 = ((s1+s2)==s3); //驗證只要數(shù)值相等妄痪,還是相等的哈雏。即使它是一個Integer對象相加
Boolean b2 = s3.equals(s3); //驗證使用equals對象比較,因為還是在緩存區(qū)域以內(nèi)衫生,所以當然相等啦裳瘪。
Boolean b3 = (s3==s4); //驗證了Integer對象的緩存-128~127以內(nèi),值相等即可相等啦罪针。但是只要超出緩存區(qū)域盹愚,就不相等了。
//以下就思考地址與對象的比較關(guān)系啦
Boolean b4 = (s5==s6); //驗證啦超出緩存的對象啦站故,當然不相等啦。而且這個是對象地址的比較
Boolean b5 = s5.equals(s6); //驗證比較兩個擁有相同屬性值的對象比較的話讨惩,就相等了嘛对竣。只是比較對象的值踪危,也就是對象的內(nèi)容而已。
System.out.println("---【(s1+s2)==s3】"+b1+"-----【s3.equals(s3)】"+b2+"-----【s3==s4】"+b3+"-----【s5==s6】"+b4+"-----【s5.equals(s6)】"+b5);
}
}
補充:
“==”比較的是值--變量(棧)內(nèi)存中存放的對象的(堆)內(nèi)存地址
equals用于比較兩個對象的值是否相同--不是比地址岂津,是比較內(nèi)容。
【特別注意】Object類中的equals方法和“==”是一樣的悦即,沒有區(qū)別吮成,而String類橱乱,Integer類等等一些類,是重寫了equals方法粱甫,才使得equals和“==不同”(這個可參看我上一篇博客基本數(shù)據(jù)類及其包裝類詳解)泳叠,所以,當自己創(chuàng)建類時茶宵,自動繼承了Object的equals方法危纫,要想實現(xiàn)不同的等于比較,必須重寫equals方法乌庶。
另外种蝶,"=="比"equal"運行速度快,因為"=="只是比較引用。
二瞒大、字符串家族的基本知識:
(1)String:是不可變類螃征,即一旦String對象被創(chuàng)建,包含這個對象的字符序列是不可改變的透敌。
我們結(jié)合源碼來看下重要的部分代碼
//String類由final修飾盯滚,故String不能被繼承。并且被標記了可序列化
//CharSequence 是字符串協(xié)議接口
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
//看到源碼也這么說了拙泽,用于字符存儲淌山,并且它是一個final修飾的屬性,所以String一旦創(chuàng)建即不可被修改顾瞻。因此所有對String字符串的修改(如追加字符串泼疑,刪除部分字符串,截取字符串)都不是在原來的對象基礎上修改荷荤,而是新建一個String對象修改并返回退渗,這會造成原對象被廢棄,浪費資源且性能較差(特別是追加字符串和刪除部分字符串)蕴纳,若遇到字符串將被頻繁修改的情況会油,建議不要使用String,改用StringBuffer或StringBuilder。
private final char value[];
/**String類型的hash值**/
private int hash; // Default to 0
//數(shù)了下7個構(gòu)造器古毛,分析些常用和基本類似的吧翻翩。我們在生成一個String對象的時候必須對該對象的offset、count稻薇、value三個屬性進行賦值嫂冻,這樣我們才能獲得一個完成的String類型。
//這個就是直接賦值給字符存儲的數(shù)組嘛塞椎。默認value為一個長度為0的數(shù)組桨仿,所以為null。
public String() {
this.value = new char[0];
}
//初始化新創(chuàng)建的字符串對象案狠,它代表相同的字符序列的參數(shù).新創(chuàng)建的字符串是參數(shù)字符串的副本(就是增刪改string的時候的string對象)服傍,此構(gòu)造函數(shù)的使用是因為字符串是不可變的钱雷。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//就是可以傳入一個字符數(shù)組嘛。而且還是這個問題吹零,string不可變罩抗,你一創(chuàng)建就被copy了。隨后修改的字符對象已經(jīng)不是原來的對象了瘪校。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
//跟上面的構(gòu)造器差不多澄暮,多點規(guī)范
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);//起始值下標小于0,拋異常
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);//取值長度小于0阱扬,拋異常
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);//起始值下標加長度大于數(shù)組長度泣懊,拋異常
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
//在構(gòu)造對象時,傳入了下標以及長度
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
//構(gòu)造對象后:(步驟一)精確計算String所需要的長度麻惶。自動計算string所需長度馍刮。
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
continue;
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
//構(gòu)造對象后:(步驟二)分配和填充字符對象。提取每一位的字符窃蹋,并將其放入String字符串卡啰。
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);
}
this.value = v;
}
//可以看到StringBuffer 和StringBuilder 傳進來也可成為String對象。但是三者并沒繼承關(guān)系警没。
public String(StringBuffer buffer) {
//嘿嘿匈辱,神奇的一個線程關(guān)鍵字synchronized。一會有解析
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
//很普通的方法啦
public int length() {
return value.length;
}
public boolean isEmpty() {
return value.length == 0;
}
//用于取得字符串下標為i的字符
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
//返回指定索引處的字符(Unicode代碼點)杀迹。該索引引用char值(Unicode代碼單元)亡脸,其范圍從 0 到 length() - 1。就是返回一個Unicode值树酪。
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
//上面解析過浅碾,Integer,String這些類都是重寫了equals才有不同的效果续语。比較字符串的值是否相同 垂谢。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;//若比較的兩個對象引用地址值相同,則為同一個對象疮茄,值當然相同
}
if (anObject instanceof String) {//String與另一個對象能比較的前提是滥朱,對方也屬于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) {//將兩個String對象的值放入數(shù)組中,遍歷比較懂版,全部相同才表示相同
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//如果是直接String比較,很快的嘛躏率。
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
//比較兩個字符串字典躯畴。什么意思呢民鼓?比較是基于字符串中的每個字符的Unicode值,就是遍歷去比較兩個字符串的每個字符Unicode值大小蓬抄。其結(jié)果是負的整數(shù)丰嘉,如果此String對象字典前面的參數(shù)字符串;其結(jié)果是一個正整數(shù)嚷缭,如果此String對象字典如下的參數(shù)字符串饮亏;結(jié)果是零,如果兩個字符串相等阅爽,CompareTo返回0時路幸,equal(Object)方法將返回true。
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;
}
//判斷一個字符串是否以prefix字符串開頭付翁,toffset是相同的長度
public boolean startsWith (String prefix,int toffset){
char ta[] = value;
int to = offset + toffset;
char pa[] = prefix.value;
int po = prefix.offset;
int pc = prefix.count;
// Note: toffset might be near -1>>>1.
if ((toffset < 0) || (toffset > count - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
//連接兩個字符串
public String concat (String str){
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);//哈哈哈简肴,看到了吧,都是新建一個String對象百侧,然后返回砰识。
}
//String與基本類型的包裝類轉(zhuǎn)換源碼啦。都是靜態(tài)方法
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
//這個比較有意思佣渴,用了一個非public的構(gòu)造器辫狼,不過還是直接賦值給value屬性去存儲而已。但前提是傳入一個字節(jié)數(shù)組辛润,而不是字節(jié)
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);
}
//String截取方法膨处,傳入截取開始的下標
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);//當傳入的開始下標符合且不為0時,新建一個String,注意這個String的值并沒有變化频蛔,只是改變了偏移量
//傳入開始index以及結(jié)束下標去截取
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);//與上面的方法類似灵迫,沒有改變String的value屬性,只是而是改變了偏移量和count長度晦溪。
}
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;//替代的是整個value中的oldChar瀑粥,而不是從偏移量開始替代
int i = -1;
char[] val = value;
while (++i < len) {//先遍歷數(shù)組中是否有原字母,沒有就無需替換三圆,高效的設計
if (val[i] == oldChar) {
break;
}
}
if (i < len) {//獲得需要替換的char的下標,此下表以前的char直接復制路媚,
此下標以后的char才開始一個一個比較围苫,若等于oldchar則替換剃盾,高效
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;
}
}
}
嗯闰歪,String常用的源碼基本解析完了。下面我們看些有趣的問題去思考饱亿。
下面就是java對String的深度優(yōu)化問題啦。很恐怖。
String對象的地址問題腐巢,異同問題以及java對string的優(yōu)化問題
public class StringTest {
public static void main(String[] args) {
String a="fuzhu"; //這樣的方式也是創(chuàng)建一個string對象!泞莉!這樣創(chuàng)建是放在常量池捺弦,new的方式是放在堆中寞钥。
String b="fuzhu";
String c=new String ("fuzhu");
String d=new String ("fuzhu");
System.out.println(a==b);//true,fuzhu被創(chuàng)建在String Pool中,a和b會指向同一個對象,
System.out.println(a==c);//a指向的fuzhu被創(chuàng)建在String Pool中窝剖,而c所指向的對象被創(chuàng)建在heap中狂魔,兩者為不同的對象淫痰,地址值不同
System.out.println(d==c);//c和d所指的對象都被創(chuàng)建在堆中最楷,但是兩個不同的對象,地址值不同
System.out.println("---------重要的優(yōu)化問題!W阉铩烈评!----------");
String e = "ab";
String f = "a" + "b";
System.out.println((e == f));//答案是什么?犯建?是true=补凇!因為什么适瓦?因為java對String有個字符串常量池竿开,恐怖優(yōu)化!下面有解析玻熙。
System.out.println("-----------驗證什么時候是常量表達式7癫省!--------");
String g = "a1";
String h = "a" + 1;
System.out.println((g == h)); //result = true
String i = "a" + true; //String i = "atrue";
String j = "atrue" ;
System.out.println((i == j)); //result = true
String k = "a" + 3.4; // String k = "a3.4";
String l = "a3.4" ;
System.out.println((k == l)); //result = true
System.out.println("---------驗證非常量表達式的情況----------");
String o = "ab";
String p = "b";
String q = "a" + p;
System.out.println((o == q)); //result = false
}
}
1. String s="a"是一種非常特殊的形式,和new 有本質(zhì)的區(qū)別嗦随。它是java中唯一不需要new 就可以產(chǎn)生對象的途徑列荔。以 String s="a";形式賦值在java中叫直接量,它是在常量池中而不是象new 一樣放在壓縮堆中.
2. String s="a"這種形式的字符串,在JVM內(nèi)部發(fā)生字符串拘留,即當聲明這樣的一個字符串后,JVM會在常量池中先查找有有沒有一個值為"a"的對象,如果有,就會把它賦給當前引用.即原來那個引用和現(xiàn)在這個引用指點向了同一對象,如果沒有,則在常量池中新創(chuàng)建一個"a",下一次如果有String s2 = "1";又會將s1指向"abcd"這個對象,即以這形式聲明的字符串,只要值相等,任何多個引用都指向同一對象.
3. 解析直接創(chuàng)建的java對string的優(yōu)化過程:編譯優(yōu)化+ 常量池。String b = "a" + "b";編譯器將這個"a" + "b"作為常量表達式枚尼,在編譯時進行優(yōu)化贴浙,直接取結(jié)果"ab"。
至于怎么驗證真的是直接仁鸹小悬而??當然只有看編譯的二進制代碼啦锭汛,我們可下載個ultraedit工具笨奠,去查看兩個一模一樣的類的十六進制。就可以看出來了唤殴。
4. 什么時候是常量表達式般婆,什么時候不是?朵逝?
肯定是的:(1)String + String(這的string指的是直接量)蔚袍;(2)string + 基本類型;
不是的情況:兩個變量啊配名。比如兩個string變量就不行了啤咽。
(2)StringBuffer:也代表字符串對象,與其余兩個基本類似渠脉。但StringBuffer是線程安全宇整,StringBuilder沒有線程安全功能,所以StringBuilder性能略高芋膘。
因為StringBuffer和StringBuilder都繼承了AbstractStringBuilder 鳞青,所以我們先看AbstractStringBuilder 霸饲。
AbstractStringBuilder 源碼:
//為抽象類,主要的屬性有兩個臂拓,一個為value厚脉,一個為count,value用于存放值胶惰,count用于管理該類的容量
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//用來存字符串
char[] value;
//用來計算存儲使用的字符數(shù)量
int count;
public int length() {//length方法返回的是count的值傻工,而不是value.length
return count;
}
//抽象類的構(gòu)造器沒啥必要看了吧
//這個才是返回容量,字符串總長度
public int capacity() {
return value.length;
}
//一層扣一層的封裝的擴容機制:
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0)//如果最小容量大于長度就要擴容了
expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;//自動擴容機制孵滞,每次擴容(value.length+1)*2
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;//若傳入的參數(shù)小于0精钮,則直接把容量設置到Integer的最
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;//若擴容后的容量還是小于傳入的參數(shù),則將傳入的參數(shù)設為容量
}
value = Arrays.copyOf(value, newCapacity);//當count小于value.length時剃斧,將value多余長度的值刪除,這時value.length的長度等于count
}
//用于保留value的值忽你,保留的長度為count的值幼东,只有當count的值小于value.length時才起作用科雳,
public void trimToSize() {
if (count < value.length) {
value = Arrays.copyOf(value, count);
}
}
public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
ensureCapacityInternal(newLength);//當傳入的值大于等于0時根蟹,需要擴容
if (count < newLength) { //當傳入值大于字符統(tǒng)計量
Arrays.fill(value, count, newLength, '\0');//為新擴容的元素賦值'\0'尿赚,為結(jié)束符
}
count = newLength;//排除那堆不合理參數(shù)干擾后就是那個真正的字符統(tǒng)計量了琴庵。
}
//append依賴的一個方法翰苫,用以添加一個字符串數(shù)組
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
if (srcBegin < 0)
throw new StringIndexOutOfBoundsException(srcBegin);
if ((srcEnd < 0) || (srcEnd > count))
throw new StringIndexOutOfBoundsException(srcEnd);
if (srcBegin > srcEnd)
throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);//用于添加字符串鹰晨,將value的值添加到dst[]中
}
//在對象后拼接字符串或其他對象判没,效率較高熊杨,可以觀察到,在拼接時并沒有創(chuàng)建新的對象土至,也沒有舍棄舊的對象购对,相對于String的機制,性能提升相當明顯
//根據(jù)傳入?yún)?shù)對應不同的方法
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();//若傳入的字符串長度為0陶因,則默認添加null這個字符串
int len = str.length();
ensureCapacityInternal(count + len);//擴容到這么大
str.getChars(0, len, value, count);//然后用getChar方法去添加字符串數(shù)組
count += len;//確定存儲了這么多的字符
return this;
}
//拼接StringBuffer 也是可以的
public AbstractStringBuilder append(StringBuffer sb) {
if (sb == null)
return appendNull();
int len = sb.length();
ensureCapacityInternal(count + len);
sb.getChars(0, len, value, count);
count += len;
return this;
}
//這意味只要是AbstractStringBuilder 就可以拼接(暗指builder吧)
AbstractStringBuilder append(AbstractStringBuilder asb) {
if (asb == null)
return appendNull();
int len = asb.length();
ensureCapacityInternal(count + len);//直接檢驗容量骡苞,有需要則執(zhí)行擴容
asb.getChars(0, len, value, count);//然后用getChar方法去添加字符串數(shù)組
count += len;//確定存儲了這么多的字符
return this;
}
//添加null這個字符串
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;
}
//同理char的字節(jié)。就不看了楷扬,我們看下Interger等一些基本類型包裝類解幽,其余都基本類似的。
public AbstractStringBuilder append(int i) {
if (i == Integer.MIN_VALUE) {
append("-2147483648");//Integer最小值為特例烘苹,特殊處理
return this;
}
int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
: Integer.stringSize(i);//判斷Integer的位數(shù)躲株,負數(shù)有負號,要多加一位
int spaceNeeded = count + appendedLength;//確定字符串大小
ensureCapacityInternal(spaceNeeded);//還是擴容
Integer.getChars(i, spaceNeeded, value);//還是添加镣衡,不過是用Integer的靜態(tài)方法
count = spaceNeeded;
return this;
}
//以刪除一部分的字符霜定,傳入開始下標以及截止下標
public AbstractStringBuilder delete(int start, int end) {
//驗證參數(shù)的有效性
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;//結(jié)束下標大于count時档悠,將count設為結(jié)束下標
if (start > end)
throw new StringIndexOutOfBoundsException();//開始下標就大于結(jié)束下標,當然異常
int len = end - start;
if (len > 0) {
//System的靜態(tài)方法來實現(xiàn)數(shù)組之間的復制望浩。src:源數(shù)組辖所; srcPos:源數(shù)組要復制的起始位置;dest:目的數(shù)組曾雕;destPos:目的數(shù)組放置的起始位置奴烙;length:復制的長度。注意:src and dest都必須是同類型或者可以進行轉(zhuǎn)換類型的數(shù)組.
//這個函數(shù)可以實現(xiàn)自己到自己復制剖张。解析:http://blog.csdn.net/kesalin/article/details/566354
System.arraycopy(value, start+len, value, start, count-end);//執(zhí)行刪除
count -= len;//重置count大小
}
return this;
}
//在對象中間插入字符串數(shù)組
public AbstractStringBuilder insert(int dstOffset, CharSequence s) {
//驗證參數(shù)有效性
if (s == null)
s = "null";
if (s instanceof String)//這個必須要String才準插入
return this.insert(dstOffset, (String)s);
return this.insert(dstOffset, s, 0, s.length());
}
//傳入的是插入開始下標切诀,以及要插入的字符串。其他插入方法差不多搔弄,就不多說了幅虑。
public AbstractStringBuilder insert(int offset, String str) {
//驗證參數(shù)有效性
if ((offset < 0) || (offset > length()))
throw new StringIndexOutOfBoundsException(offset);
if (str == null)
str = "null";
int len = str.length();
ensureCapacityInternal(count + len);//擴容
System.arraycopy(value, offset, value, offset + len, count - offset);//使用arraycopy方法去創(chuàng)建那么多長度先,就是先在value中建立起用于存放插入值的空位
str.getChars(value, offset);//向空位中插入str
count += len;//更新count值
return this;
}
//將對象本身的字符順序調(diào)轉(zhuǎn)后返回給原對象.顾犹。就是反轉(zhuǎn)字符串
public AbstractStringBuilder reverse() {
boolean hasSurrogates = false;
int n = count - 1;
//采用從中間向兩端遍歷倒庵,對換對稱位置上的字符
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
char cj = value[j];//兩個暫存變量
char ck = value[k];
value[j] = ck;//直接對應位置交換
value[k] = cj;
//驗證每個字符的編碼是否在范圍內(nèi)
if (Character.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
//直接反轉(zhuǎn)后,如果兩字符順序錯了炫刷,就需要重新調(diào)整擎宝。考慮到存在增補字符浑玛,需要成對校驗绍申,就是超出了字符的編碼范圍的話就會重新翻轉(zhuǎn)到原來那樣
reverseAllValidSurrogatePairs();
}
return this;
}
//reverse的依賴方法--重新調(diào)整字符順序
private void reverseAllValidSurrogatePairs() {
for (int i = 0; i < count - 1; i++) {
char c2 = value[i];
if (Character.isLowSurrogate(c2)) {
char c1 = value[i + 1];
if (Character.isHighSurrogate(c1)) {
value[i++] = c1;
value[i] = c2;
}
}
}
}
//返回一個新字符串,它是此字符串的一個子字符串顾彰。該子字符串從指定的 start索引處開始极阅,一直到索引 end- 1 處的字符。因此涨享,該子字符串的長度為 end-start筋搏。
public String substring(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
throw new StringIndexOutOfBoundsException(end);
if (start > end)
throw new StringIndexOutOfBoundsException(end - start);
return new String(value, start, end - start);
}
}
嗯,AbstractStringBuilder是Stringbuilder和StringBuffer的父類厕隧,兩者的共同抽象出來很多相似性質(zhì)嘛奔脐。
接下來我們看StringBuilder的關(guān)鍵源碼:
也代表字符串對象。但這個是線程不安全的吁讨。
//StringBuilder類由final修飾帖族,不能被繼承,并且繼承了AbstractStringBuilder類挡爵,并完成了toString方法竖般,同時使用了AbstractStringBuilder類中大量的方法。
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
/** 序列號茶鹃, 對象序列化和反序列化需要的唯一標識版本號涣雕,基本類中艰亮,只要接入Serializable接口都會分配一個id */
static final long serialVersionUID = 4383685877147921099L;
///默認構(gòu)造方法
public StringBuilder() {
//使用父類的構(gòu)造方法,默認初始化容量為capacity = 16 挣郭∑#基本所有jdk中實現(xiàn)類涉及初始化容量的大小都為16,加上一點擴容機制
super(16);
}
//帶一個參數(shù)的構(gòu)造方法兑障,可以指定初始化容量
public StringBuilder(int capacity) {
super(capacity);
}
//帶一個參數(shù)構(gòu)造方法侄非,與前一個不同,這是指定一個String來初始化
public StringBuffer(String str) {
// 這里可以注意下流译,指定String初始化StringBuffer的時候指定容量大小為String的長度加上16
super(str.length() + 16);
//然后追加到value中
append(str);
}
//其余的構(gòu)造方法就都類似的啦
//下面是字符串修改方法逞怨。基本都是用父類的方法的福澡,我們剛剛就分析過了叠赦,就不多分析了。但是要注意:修改的方法全部都為線程不安全革砸,是犧牲了安全用以實現(xiàn)性能除秀。若需要考慮線程的安全性,建議使用StringBuffer算利。一會會看到為啥Buffer是線程安全的册踩。
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public String toString() {
// Create a copy, don't share the array
//源碼所說,是new了一個String對象效拭,返回string出去暂吉,而不是直接給Builder出去。
return new String(value, 0, count);
}
//將對象序列化允耿,寫入了count和value借笙。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeInt(count);
s.writeObject(value);
}
//用于反序列化扒怖,將count和value屬性讀取出來
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
count = s.readInt();
value = (char[]) s.readObject();
}
}
(3)StringBuffer:代表一個字符序列可變的字符串较锡。這個stringbuffer提供了一系列的修改方法去改變字符串對象序列。一旦StringBuffer生成了最終想要的對象盗痒,調(diào)用toString方法將它轉(zhuǎn)換成一個String對象即可蚂蕴。
來觀份源碼,將會詳細地去對比StringBuilder去講解
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
//Builder沒有的功能(一):最后一次修改后的緩存值(字符數(shù)組保存)俯邓,只要修改了value骡楼,那么就會重置
private transient char[] toStringCache;
/** 序列號, 對象序列化和反序列化需要的唯一標識版本號 */
static final long serialVersionUID = 3388685877147921107L;
//構(gòu)造方法基本跟builder差不多的啦
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
//獲取字符串字符數(shù)量稽鞭,效率低鸟整,對象鎖,所以這樣才會線程安全
@Override
public synchronized int length() {
return count;
}
// 獲取容量朦蕴,效率低--對象鎖
@Override
public synchronized int capacity() {
return value.length;
}
//確保容量不小于minimumCapacity
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
// 當最小容量值(傳進來的參數(shù)值)大于value.length(這個其實就是容量)篮条,那么就直接擴容
expandCapacity(minimumCapacity);
}
}
//擴容弟头,在它父類講過了。
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
//將value數(shù)組中沒有存入元素的部分去掉(類似去空格),此時容量大小和size大小相等涉茧。也就是刪掉count后面的那堆空的東東
@Override
public synchronized void trimToSize() {
//用父類的赴恨。但線程安全
super.trimToSize();
}
//setLength講過了就省略了。注意它也是線程安全伴栓。擴充字符串容量到newLength伦连,并且用空格填充
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
//調(diào)用父類函數(shù)
super.setLength(newLength);
}
//根據(jù)指定索引獲取字符,效率慢,對象鎖.
@Override
public synchronized char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
/**
* 根據(jù)索引修改字符串中某個字符值
*/
@Override
public synchronized void setCharAt(int index, char ch) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
toStringCache = null;//清除緩存钳垮,只要修改了value惑淳,此值就會clear
value[index] = ch;
}
//修改操作跟父類的唯一區(qū)別就是線程安全了:就不多貼了
@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;
}
/**
* 不存在內(nèi)存泄漏,實現(xiàn)了線程安全
*/
@Override
public synchronized String substring(int start) {
return substring(start, count);
//結(jié)果是new String(value, start, end - start);弄了個新對象的扔枫。
// 沒有復用char[]
}
//有一套這樣的同步與不同步方法
/**
* 此方法不同步汛聚, 而且也沒有 toStringCache = null;
* 如果需要同步,那么需要將boolean b轉(zhuǎn)化為specific type特定類型(String)
*/
@Override
public StringBuffer insert(int offset, boolean b) {
super.insert(offset, b);
return this;
}
@Override
public synchronized StringBuffer insert(int offset, char c) {
toStringCache = null;
super.insert(offset, c);
return this;
}
//toStringCache之前都不知道這個字段的含義短荐,看到這里似乎看懂了倚舀,。是提高toString函數(shù)的效率忍宋,不用每次都是調(diào)用痕貌。也就是有做了一個緩存
// Arrays.copyOfRange。糠排。舵稠。但是字符串修改后這個值需要clear
//線程安全
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);//返回一個新的string對象過去
}
// 自定義序列化字段
//**transient 用于指定哪個字段不被默認序列化,如public transient int a;
//serialPersistentFields 用于指定哪些字段需要被默認序列化.如下:
private static final java.io.ObjectStreamField[] serialPersistentFields =
{
new java.io.ObjectStreamField("value", char[].class),
new java.io.ObjectStreamField("count", Integer.TYPE),
new java.io.ObjectStreamField("shared", Boolean.TYPE),
};
/**
* 序列化大到ObjectOutputStream入宦,寫入了count和value哺徊、shared
*/
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
java.io.ObjectOutputStream.PutField fields = s.putFields();
fields.put("value", value);
fields.put("count", count);
fields.put("shared", false);
s.writeFields();
}
/**
* 反序列化到對象,讀出count和value乾闰。
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
java.io.ObjectInputStream.GetField fields = s.readFields();
value = (char[])fields.get("value", null);
count = fields.get("count", 0);
}
}
三落追、字符串家族的坑以及使用推薦
1.效率問題:效率:StringBuilder>StringBuffer>String
public static void main(String[] args) {
long start = System.currentTimeMillis();
String str = null;
for (int i = 0; i < 20000; i++) {
str = str + i + ",";//因為是不斷弄出一個新的對象出來的
}
System.out.println("String耗時 "+ (System.currentTimeMillis() - start));
System.out.println("-------------------");
//buffer和builder都有自動擴容機制,不像string有兩大缺點:1.返回對象使用大量new操作涯肩,產(chǎn)生很多垃圾轿钠;2.雖然最終調(diào)用的是系統(tǒng)復制數(shù)組操作,但調(diào)用之前開銷非常大病苗,只能靠復制來解決拼接問題疗垛。
start = System.currentTimeMillis();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 20000; i++) {
buffer.append(i + ",");//線程安全所以慢一點,但前提這里是單線程
}
System.out.println("StringBuffer耗時 "+(System.currentTimeMillis() - start));
System.out.println("-------------------");
start = System.currentTimeMillis();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 20000; i++) {
builder.append(i + ",");//線程不安全硫朦,效率最高
}
System.out.println("StringBuilder耗時 "+(System.currentTimeMillis() - start));
}
/*
String耗時 2030
-------------------
StringBuffer耗時 5
-------------------
StringBuilder耗時 3
*/
2.線程安全與線程不安全問題:驗證:
/**
* Created by ${符柱成} on 2017/1/11.
*/
public class StringBuilderTest {
public static void main(String[] args) {
/*
* 聲明個字符串s贷腕,用下劃線和井號是因為兩個比較好區(qū)分。 分別實例化StringBuffer和StringBuilder兩個對象
*/
String s = "####____";
StringBuffer sf = new StringBuffer(s);
StringBuilder sd = new StringBuilder(s);
/*
* 對sf和sd各自實例化兩個反轉(zhuǎn)他們的類
*/
// ThreadString sfr1 = new ThreadString(sf);
// ThreadString sfr2 = new ThreadString(sf);
ThreadString sdr1 = new ThreadString(sd);
ThreadString sdr2 = new ThreadString(sd);
/*
* 啟動這四個線程,此時sf和sd各自有兩個線程在對他們進行字符串反轉(zhuǎn)操作
*/
// new Thread(sfr1).start();
// new Thread(sfr2).start();
new Thread(sdr1).start();
new Thread(sdr2).start();
}
}
class ThreadString implements Runnable {
/*
* 這個類用來完成字符串的反轉(zhuǎn)工作泽裳,使用了Runnable接口來實現(xiàn)多線程 times是用來表示循環(huán)多少次的
* 因為懶的再寫一個變量所以用了一個Object類型的s芽世,后面再轉(zhuǎn)化
*/
public Object s = null;
int times = 10;
/*
* 兩個構(gòu)造方法把s傳進來
*/
public ThreadString(StringBuffer s) {
this.s = s;
}
public ThreadString(StringBuilder s) {
this.s = s;
}
/*
* 復寫run方法實現(xiàn)多線程 在我的電腦上大概循環(huán)十幾次可以看到效果了
*/
public void run() {
for (int i = 0; i <= times; i++) {
//sleep一下讓輸出更加清晰
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (s instanceof StringBuffer) {
((StringBuffer) s).reverse();//直接調(diào)用翻轉(zhuǎn)的方法
System.out.println("BUFFER->" + s);
} else if (s instanceof StringBuilder) {
((StringBuilder) s).reverse();//直接調(diào)用翻轉(zhuǎn)的方法
System.out.println(" " + s + "<-Builder");
}
System.out.println(Thread.currentThread().getName());//輸出下線程名字更加清晰啦
System.out.println("-------------------");
}
}
}
/*
* 最后看一下控制臺的輸出會發(fā)現(xiàn)反轉(zhuǎn)后出現(xiàn)井號和下劃線交錯的都是StringBuilder的輸出
*/
/*
我們輸出的時候會發(fā)現(xiàn),Builder在一次線程操作中甚至可以這樣反轉(zhuǎn)诡壁,但是Buffer就可觀察到?jīng)]有這樣的搶占修改济瓢,是十分有序地修改。
-------------------
####____<-builder
____####<-builder
Thread-1
-------------------
*/
使用注意點總結(jié):
1. 如果要操作少量的數(shù)據(jù)用 = String
2. 單線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù) --StringBuilder
3. 多線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) -- StringBuffer
好了妹卿,深入Java基礎(二)——字符串家族講完了旺矾。本博客是經(jīng)過仔細研究其他的博客,結(jié)合源代碼注釋夺克,并在這里做出進一步拓展以及寫出自己的理解箕宙。另外,這個系列會逐步更新铺纽,也希望閱讀更多源碼柬帕,分享經(jīng)驗給大家。歡迎在下面指出錯誤狡门,共同學習O萸蕖!你的點贊是對我最好的支持F淞蟆凤跑!
這個系列的文章因為涉及很多源碼,所以會根據(jù)網(wǎng)友們的反應以及我的學習深入去補充叛复。希望大家一起來討論學習仔引。
轉(zhuǎn)載請注明:【JackFrost的博客】