日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

【Java基本功】一文读懂String及其包装类的实现原理

發(fā)布時(shí)間:2025/3/18 java 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java基本功】一文读懂String及其包装类的实现原理 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.



String作為Java中最常用的引用類型,相對(duì)來(lái)說(shuō)基本上都比較熟悉,無(wú)論在平時(shí)的編碼過(guò)程中還是在筆試面試中,String都很受到青睞,然而,在使用String過(guò)程中,又有較多需要注意的細(xì)節(jié)之處。


String的連接

@Testpublic?void?contact?()?{????//1連接方式????String?s1?=?"a";????String?s2?=?"a";????String?s3?=?"a"?+?s2;????String?s4?=?"a"?+?"a";????String?s5?=?s1?+?s2;????//表達(dá)式只有常量時(shí),編譯期完成計(jì)算????//表達(dá)式有變量時(shí),運(yùn)行期才計(jì)算,所以地址不一樣????System.out.println(s3?==?s4);?//f????System.out.println(s3?==?s5);?//f????System.out.println(s4?==?"aa");?//t?}

String類型的intern

public?void?intern?()?{????//2:string的intern使用????//s1是基本類型,比較值。s2是string實(shí)例,比較實(shí)例地址????//字符串類型用equals方法比較時(shí)只會(huì)比較值????String?s1?=?"a";????String?s2?=?new?String("a");????//調(diào)用intern時(shí),如果s2中的字符不在常量池,則加入常量池并返回常量的引用????String?s3?=?s2.intern();????System.out.println(s1?==?s2);????System.out.println(s1?==?s3);}

String類型的equals

//字符串的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;//????????}

StringBuffer和Stringbuilder

底層是繼承父類的可變字符數(shù)組value

/**?*?The?value?is?used?for?character?storage.?*/char[]?value;初始化容量為16?/**?*?Constructs?a?string?builder?with?no?characters?in?it?and?an?*?initial?capacity?of?16?characters.?*/public?StringBuilder()?{????super(16);}這兩個(gè)類的append方法都是來(lái)自父類AbstractStringBuilder的方法?public?AbstractStringBuilder?append(String?str)?{????if?(str?==?null)????????return?appendNull();????int?len?=?str.length();????ensureCapacityInternal(count?+?len);????str.getChars(0,?len,?value,?count);????count?+=?len;????return?this;}@Overridepublic?StringBuilder?append(String?str)?{????super.append(str);????return?this;}?@Overridepublic?synchronized?StringBuffer?append(String?str)?{????toStringCache?=?null;????super.append(str);????return?this;}

append

Stringbuffer在大部分涉及字符串修改的操作上加了synchronized關(guān)鍵字來(lái)保證線程安全,效率較低。?String類型在使用?+?運(yùn)算符例如?String?a?=?"a"?a?=?a?+?a;時(shí),實(shí)際上先把a(bǔ)封裝成stringbuilder,調(diào)用append方法后再用tostring返回,所以當(dāng)大量使用字符串加法時(shí),會(huì)大量地生成stringbuilder實(shí)例,這是十分浪費(fèi)的,這種時(shí)候應(yīng)該用stringbuilder來(lái)代替string。

擴(kuò)容

#注意在append方法中調(diào)用到了一個(gè)函數(shù)?ensureCapacityInternal(count?+?len);該方法是計(jì)算append之后的空間是否足夠,不足的話需要進(jìn)行擴(kuò)容?public?void?ensureCapacity(int?minimumCapacity)?{????if?(minimumCapacity?>?0)????????ensureCapacityInternal(minimumCapacity);}private?void?ensureCapacityInternal(int?minimumCapacity)?{????//?overflow-conscious?code????if?(minimumCapacity?-?value.length?>?0)?{????????value?=?Arrays.copyOf(value,????????????????newCapacity(minimumCapacity));????}}如果新字符串長(zhǎng)度大于value數(shù)組長(zhǎng)度則進(jìn)行擴(kuò)容?擴(kuò)容后的長(zhǎng)度一般為原來(lái)的兩倍?+?2;?假如擴(kuò)容后的長(zhǎng)度超過(guò)了jvm支持的最大數(shù)組長(zhǎng)度MAX_ARRAY_SIZE。?考慮兩種情況?如果新的字符串長(zhǎng)度超過(guò)int最大值,則拋出異常,否則直接使用數(shù)組最大長(zhǎng)度作為新數(shù)組的長(zhǎng)度。?private?int?hugeCapacity(int?minCapacity)?{????if?(Integer.MAX_VALUE?-?minCapacity?<?0)?{?//?overflow????????throw?new?OutOfMemoryError();????}????return?(minCapacity?>?MAX_ARRAY_SIZE)??????????minCapacity?:?MAX_ARRAY_SIZE;}

刪除

這兩個(gè)類型的刪除操作:?都是調(diào)用父類的delete方法進(jìn)行刪除?public?AbstractStringBuilder?delete(int?start,?int?end)?{????if?(start?<?0)????????throw?new?StringIndexOutOfBoundsException(start);????if?(end?>?count)????????end?=?count;????if?(start?>?end)????????throw?new?StringIndexOutOfBoundsException();????int?len?=?end?-?start;????if?(len?>?0)?{????????System.arraycopy(value,?start+len,?value,?start,?count-end);????????count?-=?len;????}????return?this;}事實(shí)上是將剩余的字符重新拷貝到字符數(shù)組value。

這里用到了system.arraycopy來(lái)拷貝數(shù)組,速度是比較快的

system.arraycopy方法

轉(zhuǎn)自知乎:?在主流高性能的JVM上(HotSpot?VM系、IBM?J9?VM系、JRockit系等等),可以認(rèn)為System.arraycopy()在拷貝數(shù)組時(shí)是可靠高效的——如果發(fā)現(xiàn)不夠高效的情況,請(qǐng)報(bào)告performance?bug,肯定很快就會(huì)得到改進(jìn)。?java.lang.System.arraycopy()方法在Java代碼里聲明為一個(gè)native方法。所以最na?ve的實(shí)現(xiàn)方式就是通過(guò)JNI調(diào)用JVM里的native代碼來(lái)實(shí)現(xiàn)。

String的不可變性

關(guān)于String的不可變性,這里轉(zhuǎn)一個(gè)不錯(cuò)的回答

什么是不可變?

String不可變很簡(jiǎn)單,如下圖,給一個(gè)已有字符串"abcd"第二次賦值成"abcedl",不是在原內(nèi)存地址上修改數(shù)據(jù),而是重新指向一個(gè)新對(duì)象,新地址。

String為什么不可變?

翻開(kāi)JDK源碼,java.lang.String類起手前三行,是這樣寫(xiě)的:

public?final?class?String?implements?java.io.Serializable,?Comparable<String>,?CharSequence?{?????/**?String本質(zhì)是個(gè)char數(shù)組.?而且用final關(guān)鍵字修飾.*/?????private?final?char?value[];??...??...?}

首先String類是用final關(guān)鍵字修飾,這說(shuō)明String不可繼承。再看下面,String類的主力成員字段value是個(gè)char[]數(shù)組,而且是用final修飾的。

final修飾的字段創(chuàng)建以后就不可改變。 有的人以為故事就這樣完了,其實(shí)沒(méi)有。因?yàn)殡m然value是不可變,也只是value這個(gè)引用地址不可變。擋不住Array數(shù)組是可變的事實(shí)。

Array的數(shù)據(jù)結(jié)構(gòu)看下圖。

也就是說(shuō)Array變量只是stack上的一個(gè)引用,數(shù)組的本體結(jié)構(gòu)在heap堆。

String類里的value用final修飾,只是說(shuō)stack里的這個(gè)叫value的引用地址不可變。沒(méi)有說(shuō)堆里array本身數(shù)據(jù)不可變。看下面這個(gè)例子,

final?int[]?value={1,2,3}?;int[]?another={4,5,6};?value=another;????//編譯器報(bào)錯(cuò),final不可變?value用final修飾,編譯器不允許我把value指向堆區(qū)另一個(gè)地址。但如果我直接對(duì)數(shù)組元素動(dòng)手,分分鐘搞定。??final?int[]?value={1,2,3};?value[2]=100;??//這時(shí)候數(shù)組里已經(jīng)是{1,2,100}???所以String是不可變,關(guān)鍵是因?yàn)镾UN公司的工程師。?在后面所有String的方法里很小心的沒(méi)有去動(dòng)Array里的元素,沒(méi)有暴露內(nèi)部成員字段。?private?final?char?value[]這一句里,private的私有訪問(wèn)權(quán)限的作用都比f(wàn)inal大。而且設(shè)計(jì)師還很小心地把整個(gè)String設(shè)成final禁止繼承,避免被其他人繼承后破壞。所以String是不可變的關(guān)鍵都在底層的實(shí)現(xiàn),而不是一個(gè)final。考驗(yàn)的是工程師構(gòu)造數(shù)據(jù)類型,封裝數(shù)據(jù)的功力。

不可變有什么好處?

這個(gè)最簡(jiǎn)單地原因,就是為了安全??聪旅孢@個(gè)場(chǎng)景(有評(píng)論反應(yīng)例子不夠清楚,現(xiàn)在完整地寫(xiě)出來(lái)),一個(gè)函數(shù)appendStr( )在不可變的String參數(shù)后面加上一段“bbb”后返回。appendSb( )負(fù)責(zé)在可變的StringBuilder后面加“bbb”。

總結(jié)以下String的不可變性。

1 首先f(wàn)inal修飾的類只保證不能被繼承,并且該類的對(duì)象在堆內(nèi)存中的地址不會(huì)被改變。

2 但是持有String對(duì)象的引用本身是可以改變的,比如他可以指向其他的對(duì)象。

3 final修飾的char數(shù)組保證了char數(shù)組的引用不可變。但是可以通過(guò)char[0] = 'a'來(lái)修改值。不過(guò)String內(nèi)部并不提供方法來(lái)完成這一操作,所以String的不可變也是基于代碼封裝和訪問(wèn)控制的。

舉個(gè)例子

final?class?Fi?{????int?a;????final?int?b?=?0;????Integer?s;?}final?char[]a?=?{'a'};final?int[]b?=?{1};@Testpublic?void?final修飾類()?{????//引用沒(méi)有被final修飾,所以是可變的。????//final只修飾了Fi類型,即Fi實(shí)例化的對(duì)象在堆中內(nèi)存地址是不可變的。????//雖然內(nèi)存地址不可變,但是可以對(duì)內(nèi)部的數(shù)據(jù)做改變。????Fi?f?=?new?Fi();????f.a?=?1;????System.out.println(f);????f.a?=?2;????System.out.println(f);????//改變實(shí)例中的值并不改變內(nèi)存地址。?????Fi?ff?=?f;????//讓引用指向新的Fi對(duì)象,原來(lái)的f對(duì)象由新的引用ff持有。????//引用的指向改變也不會(huì)改變?cè)瓉?lái)對(duì)象的地址????f?=?new?Fi();????System.out.println(f);????System.out.println(ff);}這里的對(duì)f.a的修改可以理解為char[0]?=?'a'這樣的操作。只改變數(shù)據(jù)值,不改變內(nèi)存值。


要理解String里的intern方法,就要注意基本數(shù)據(jù)類型的拆箱裝箱,以及對(duì)常量池的理解。


常量池和自動(dòng)拆箱裝箱

自動(dòng)拆箱和裝箱的原理其實(shí)與常量池有關(guān)。3.1存在棧中:public?void(int?a){int?i?=?1;int?j?=?1;}方法中的i?存在虛擬機(jī)棧的局部變量表里,i是一個(gè)引用,j也是一個(gè)引用,它們都指向局部變量表里的整型值?1.int?a是傳值引用,所以a也會(huì)存在局部變量表。?3.2存在堆里:class?A{int?i?=?1;A?a?=?new?A();}i是類的成員變量。類實(shí)例化的對(duì)象存在堆中,所以成員變量也存在堆中,引用a存的是對(duì)象的地址,引用i存的是值,這個(gè)值1也會(huì)存在堆中??梢岳斫鉃橐胕指向了這個(gè)值1。也可以理解為i就是1.?3.3包裝類對(duì)象怎么存其實(shí)我們說(shuō)的常量池也可以叫對(duì)象池。比如String?a=?new?String("a").intern()時(shí)會(huì)先在常量池找是否有“a"對(duì)象如果有的話直接返回“a"對(duì)象在常量池的地址,即讓引用a指向常量”a"對(duì)象的內(nèi)存地址。public?native?String?intern();Integer也是同理。

下圖是Integer類型在常量池中查找同值對(duì)象的方法。

public?static?Integer?valueOf(int?i)?{????if?(i?>=?IntegerCache.low?&&?i?<=?IntegerCache.high)????????return?IntegerCache.cache[i?+?(-IntegerCache.low)];????return?new?Integer(i);}private?static?class?IntegerCache?{????static?final?int?low?=?-128;????static?final?int?high;????static?final?Integer?cache[];?????static?{????????//?high?value?may?be?configured?by?property????????int?h?=?127;????????String?integerCacheHighPropValue?=????????????sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");????????if?(integerCacheHighPropValue?!=?null)?{????????????try?{????????????????int?i?=?parseInt(integerCacheHighPropValue);????????????????i?=?Math.max(i,?127);????????????????//?Maximum?array?size?is?Integer.MAX_VALUE????????????????h?=?Math.min(i,?Integer.MAX_VALUE?-?(-low)?-1);????????????}?catch(?NumberFormatException?nfe)?{????????????????//?If?the?property?cannot?be?parsed?into?an?int,?ignore?it.????????????}????????}????????high?=?h;?????????cache?=?new?Integer[(high?-?low)?+?1];????????int?j?=?low;????????for(int?k?=?0;?k?<?cache.length;?k++)????????????cache[k]?=?new?Integer(j++);?????????//?range?[-128,?127]?must?be?interned?(JLS7?5.1.7)????????assert?IntegerCache.high?>=?127;????}?????private?IntegerCache()?{}}

所以基本數(shù)據(jù)類型的包裝類型可以在常量池查找對(duì)應(yīng)值的對(duì)象,找不到就會(huì)自動(dòng)在常量池創(chuàng)建該值的對(duì)象。

而String類型可以通過(guò)intern來(lái)完成這個(gè)操作。

JDK1.7后,常量池被放入到堆空間中,這導(dǎo)致intern()函數(shù)的功能不同,具體怎么個(gè)不同法,且看看下面代碼,這個(gè)例子是網(wǎng)上流傳較廣的一個(gè)例子,分析圖也是直接粘貼過(guò)來(lái)的,這里我會(huì)用自己的理解去解釋這個(gè)例子:

[java]?view?plain?copyString?s?=?new?String("1");??s.intern();??String?s2?=?"1";??System.out.println(s?==?s2);???String?s3?=?new?String("1")?+?new?String("1");??s3.intern();??String?s4?=?"11";??System.out.println(s3?==?s4);??輸出結(jié)果為:?[java]?view?plain?copyJDK1.6以及以下:false?false??JDK1.7以及以上:false?true

JDK1.6查找到常量池存在相同值的對(duì)象時(shí)會(huì)直接返回該對(duì)象的地址。

JDK 1.7后,intern方法還是會(huì)先去查詢常量池中是否有已經(jīng)存在,如果存在,則返回常量池中的引用,這一點(diǎn)與之前沒(méi)有區(qū)別,區(qū)別在于,如果在常量池找不到對(duì)應(yīng)的字符串,則不會(huì)再將字符串拷貝到常量池,而只是在常量池中生成一個(gè)對(duì)原字符串的引用。

那么其他字符串在常量池找值時(shí)就會(huì)返回另一個(gè)堆中對(duì)象的地址。



轉(zhuǎn)載于:https://blog.51cto.com/14006572/2341808

總結(jié)

以上是生活随笔為你收集整理的【Java基本功】一文读懂String及其包装类的实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。