最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java String的深入理解

 更新時間:2017年09月19日 15:33:43   投稿:lqh  
這篇文章主要介紹了java String的深入理解的相關(guān)資料,希望通過本文大家能理解String的用法,需要的朋友可以參考下

java String的深入理解

一、Java內(nèi)存模型 

按照官方的說法:Java 虛擬機(jī)具有一個堆,堆是運(yùn)行時數(shù)據(jù)區(qū)域,所有類實(shí)例和數(shù)組的內(nèi)存均從此處分配。    

JVM主要管理兩種類型內(nèi)存:堆和非堆,堆內(nèi)存(Heap Memory)是在 Java 虛擬機(jī)啟動時創(chuàng)建,非堆內(nèi)存(Non-heap Memory)是在JVM堆之外的內(nèi)存。

簡單來說,非堆包含方法區(qū)、JVM內(nèi)部處理或優(yōu)化所需的內(nèi)存(如 JITCompiler,Just-in-time Compiler,即時編譯后的代碼緩存)、每個類結(jié)構(gòu)(如運(yùn)行時常數(shù)池、字段和方法數(shù)據(jù))以及方法和構(gòu)造方法的代碼。     

Java的堆是一個運(yùn)行時數(shù)據(jù)區(qū),類的(對象從中分配空間。這些對象通過new、newarray、 anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。

堆是由垃圾回收來負(fù)責(zé)的,堆的優(yōu)勢是可以動態(tài)地分配內(nèi)存大小,生存期也不必事先告訴編譯器,因?yàn)樗窃谶\(yùn)行時動態(tài)分配內(nèi)存的,Java的垃圾收集器會自動收走這些不再使用的數(shù)據(jù)。但缺點(diǎn)是,由于要在運(yùn)行時動態(tài)分配內(nèi)存,存取速度較慢。   

棧的優(yōu)勢是,存取速度比堆要快,僅次于寄存器,棧數(shù)據(jù)可以共享。但缺點(diǎn)是,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量數(shù)據(jù)(int, short, long, byte, float, double, boolean, char)和對象句柄(引用)。     

虛擬機(jī)必須為每個被裝載的類型維護(hù)一個常量池。常量池就是該類型所用到常量的一個有序集合,包括直接常量(string,integer和 floating point常量)和對其他類型,字段和方法的符號引用。   

對于String常量,它的值是在常量池中的。而JVM中的常量池在內(nèi)存當(dāng)中是以表的形式存在的, 對于String類型,有一張固定長度的CONSTANT_String_info表用來存儲文字字符串值,注意:該表只存儲文字字符串值,不存儲符號引用。說到這里,對常量池中的字符串值的存儲位置應(yīng)該有一個比較明了的理解了。在程序執(zhí)行的時候,常量池會儲存在Method Area,而不是堆中。常量池中保存著很多String對象; 并且可以被共享使用,因此它提高了效率 

二、案例解析

public static void main(String[] args) { 
    /** 
     * 情景一:字符串池 
     * JAVA虛擬機(jī)(JVM)中存在著一個字符串池,其中保存著很多String對象; 
     * 并且可以被共享使用,因此它提高了效率。 
     * 由于String類是final的,它的值一經(jīng)創(chuàng)建就不可改變。 
     * 字符串池由String類維護(hù),我們可以調(diào)用intern()方法來訪問字符串池。 
     */ 
    String s1 = "abc";   
    //↑ 在字符串池創(chuàng)建了一個對象 
    String s2 = "abc";   
    //↑ 字符串pool已經(jīng)存在對象“abc”(共享),所以創(chuàng)建0個對象,累計(jì)創(chuàng)建一個對象 
    System.out.println("s1 == s2 : "+(s1==s2));  
    //↑ true 指向同一個對象, 
    System.out.println("s1.equals(s2) : " + (s1.equals(s2)));  
    //↑ true 值相等 
    //↑------------------------------------------------------over 
    /** 
     * 情景二:關(guān)于new String("") 
     * 
     */ 
    String s3 = new String("abc"); 
    //↑ 創(chuàng)建了兩個對象,一個存放在字符串池中,一個存在與堆區(qū)中; 
    //↑ 還有一個對象引用s3存放在棧中 
    String s4 = new String("abc"); 
    //↑ 字符串池中已經(jīng)存在“abc”對象,所以只在堆中創(chuàng)建了一個對象 
    System.out.println("s3 == s4 : "+(s3==s4)); 
    //↑false  s3和s4棧區(qū)的地址不同,指向堆區(qū)的不同地址; 
    System.out.println("s3.equals(s4) : "+(s3.equals(s4))); 
    //↑true s3和s4的值相同 
    System.out.println("s1 == s3 : "+(s1==s3)); 
    //↑false 存放的地區(qū)多不同,一個棧區(qū),一個堆區(qū) 
    System.out.println("s1.equals(s3) : "+(s1.equals(s3))); 
    //↑true 值相同 
    //↑------------------------------------------------------over 
    /** 
     * 情景三: 
     * 由于常量的值在編譯的時候就被確定(優(yōu)化)了。 
     * 在這里,"ab"和"cd"都是常量,因此變量str3的值在編譯時就可以確定。 
     * 這行代碼編譯后的效果等同于: String str3 = "abcd"; 
     */ 
    String str1 = "ab" + "cd"; //1個對象 
    String str11 = "abcd";  
    System.out.println("str1 = str11 : "+ (str1 == str11)); 
    //↑------------------------------------------------------over 
    /** 
     * 情景四: 
     * 局部變量str2,str3存儲的是存儲兩個拘留字符串對象(intern字符串對象)的地址。 
     * 
     * 第三行代碼原理(str2+str3): 
     * 運(yùn)行期JVM首先會在堆中創(chuàng)建一個StringBuilder類, 
     * 同時用str2指向的拘留字符串對象完成初始化, 
     * 然后調(diào)用append方法完成對str3所指向的拘留字符串的合并, 
     * 接著調(diào)用StringBuilder的toString()方法在堆中創(chuàng)建一個String對象, 
     * 最后將剛生成的String對象的堆地址存放在局部變量str3中。 
     * 
     * 而str5存儲的是字符串池中"abcd"所對應(yīng)的拘留字符串對象的地址。 
     * str4與str5地址當(dāng)然不一樣了。 
     * 
     * 內(nèi)存中實(shí)際上有五個字符串對象: 
     *    三個拘留字符串對象、一個String對象和一個StringBuilder對象。 
     */ 
    String str2 = "ab"; //1個對象 
    String str3 = "cd"; //1個對象                     
    String str4 = str2+str3;                    
    String str5 = "abcd";  
    System.out.println("str4 = str5 : " + (str4==str5)); // false 
    //↑------------------------------------------------------over 
    /** 
     * 情景五: 
     * JAVA編譯器對string + 基本類型/常量 是當(dāng)成常量表達(dá)式直接求值來優(yōu)化的。 
     * 運(yùn)行期的兩個string相加,會產(chǎn)生新的對象的,存儲在堆(heap)中 
     */ 
    String str6 = "b"; 
    String str7 = "a" + str6; 
    String str67 = "ab"; 
    System.out.println("str7 = str67 : "+ (str7 == str67)); 
    //↑str6為變量,在運(yùn)行期才會被解析。 
    final String str8 = "b"; 
    String str9 = "a" + str8; 
    String str89 = "ab"; 
    System.out.println("str9 = str89 : "+ (str9 == str89)); 
    //↑str8為常量變量,編譯期會被優(yōu)化 
    //↑------------------------------------------------------over 
  }

總結(jié):

1.String類初始化后是不可變的(immutable)

這一說又要說很多,大家只要知道String的實(shí)例一旦生成就不會再改變了,比如說:String str=”kv”+”ill”+” “+”ans”; 就是有4個字符串常量,首先”kv”和”ill”生成了”kvill”存在內(nèi)存中,然后”kvill”又和” ” 生成 “kvill “存在內(nèi)存中,最后又和生成了”kvill ans”;并把這個字符串的地址賦給了str,就是因?yàn)镾tring的”不可變”產(chǎn)生了很多臨時變量,這也就是為什么建議用StringBuffer的原 因了,因?yàn)镾tringBuffer是可改變的。

   下面是一些String相關(guān)的常見問題:

String中的final用法和理解 
final StringBuffer a = new StringBuffer(“111”); 
final StringBuffer b = new StringBuffer(“222”); 
a=b;//此句編譯不通過 final StringBuffer a = new StringBuffer(“111”); 
a.append(“222”);// 編譯通過

可見,final只對引用的”值”(即內(nèi)存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會導(dǎo)致編譯期錯誤。至于它所指向的對象的變化,final是不負(fù)責(zé)的。

2.代碼中的字符串常量在編譯的過程中收集并放在class文件的常量區(qū)中,如”123”、”123”+”456”等,含有變量的表達(dá)式不會收錄,如”123”+a。

3.JVM在加載類的時候,根據(jù)常量區(qū)中的字符串生成常量池,每個字符序列如”123”會生成一個實(shí)例放在常量池里,這個實(shí)例是不在堆里的,也不會被GC,這個實(shí)例的value屬性從源碼的構(gòu)造函數(shù)看應(yīng)該是用new創(chuàng)建數(shù)組置入123的,所以按我的理解此時value存放的字符數(shù)組地址是在堆里,如果有誤的話歡迎大家指正。

4.使用String不一定創(chuàng)建對象

在執(zhí)行到雙引號包含字符串的語句時,如String a = “123”,JVM會先到常量池里查找,如果有的話返回常量池里的這個實(shí)例的引用,否則的話創(chuàng)建一個新實(shí)例并置入常量池里。如果是 String a = “123” + b (假設(shè)b是”456”),前半部分”123”還是走常量池的路線,但是這個+操作符其實(shí)是轉(zhuǎn)換成[SringBuffer].Appad()來實(shí)現(xiàn)的,所以最終a得到是一個新的實(shí)例引用,而且a的value存放的是一個新申請的字符數(shù)組內(nèi)存空間的地址(存放著”123456”),而此時”123456”在常量池中是未必存在的。

要注意: 我們在使用諸如String str = “abc”;的格式定義類時,總是想當(dāng)然地認(rèn)為,創(chuàng)建了String類的對象str。擔(dān)心陷阱!對象可能并沒有被創(chuàng)建!而可能只是指向一個先前已經(jīng)創(chuàng)建的對象。只有通過new()方法才能保證每次都創(chuàng)建一個新的對象

5.使用new String,一定創(chuàng)建對象

在執(zhí)行String a = new String(“123”)的時候,首先走常量池的路線取到一個實(shí)例的引用,然后在堆上創(chuàng)建一個新的String實(shí)例,走以下構(gòu)造函數(shù)給value屬性賦值,然后把實(shí)例引用賦值給a:

public String(String original) {
  int size = original.count;
  char[] originalValue = original.value;
  char[] v;
   if (originalValue.length > size) {
     // The array representing the String is bigger than the new
     // String itself. Perhaps this constructor is being called
     // in order to trim the baggage, so make a copy of the array.
      int off = original.offset;
      v = Arrays.copyOfRange(originalValue, off, off+size);
   } else {
     // The array representing the String is the same
     // size as the String, so no point in making a copy.
    v = originalValue;
   }
  this.offset = 0;
  this.count = size;
  this.value = v;
  }

從中我們可以看到,雖然是新創(chuàng)建了一個String的實(shí)例,但是value是等于常量池中的實(shí)例的value,即是說沒有new一個新的字符數(shù)組來存放”123”。

如果是String a = new String(“123”+b)的情況,首先看回第4點(diǎn),”123”+b得到一個實(shí)例后,再按上面的構(gòu)造函數(shù)執(zhí)行。

6.String.intern()

String對象的實(shí)例調(diào)用intern方法后,可以讓JVM檢查常量池,如果沒有實(shí)例的value屬性對應(yīng)的字符串序列比如”123”(注意是檢查字符串序列而不是檢查實(shí)例本身),就將本實(shí)例放入常量池,如果有當(dāng)前實(shí)例的value屬性對應(yīng)的字符串序列”123”在常量池中存在,則返回常量池中”123”對應(yīng)的實(shí)例的引用而不是當(dāng)前實(shí)例的引用,即使當(dāng)前實(shí)例的value也是”123”。

public native String intern();

存在于.class文件中的常量池,在運(yùn)行期被JVM裝載,并且可以擴(kuò)充。String的 intern()方法就是擴(kuò)充常量池的 一個方法;當(dāng)一個String實(shí)例str調(diào)用intern()方法時,Java 查找常量池中 是否有相同Unicode的字符串常量,如果有,則返回其的引用,如果沒有,則在常 量池中增加一個Unicode等于str的字符串并返回它的引用;看示例就清楚了

/**
 * Java學(xué)習(xí)交流QQ群:589809992 我們一起學(xué)Java!
 */
public static void main(String[] args) {
    String s0 = "kvill"; 
    String s1 = new String("kvill"); 
    String s2 = new String("kvill"); 
    System.out.println( s0 == s1 ); //false
    System.out.println( "**********" ); 
    s1.intern(); //雖然執(zhí)行了s1.intern(),但它的返回值沒有賦給s1
    s2 = s2.intern(); //把常量池中"kvill"的引用賦給s2 
    System.out.println( s0 == s1); //flase
    System.out.println( s0 == s1.intern() ); //true//說明s1.intern()返回的是常量池中"kvill"的引用
    System.out.println( s0 == s2 ); //true
  }

最后我再破除一個錯誤的理解:有人說,“使用 String.intern() 方法則可以將一個 String 類的保存到一個全局 String 表中 ,如果具有相同值的 Unicode 字符串已經(jīng)在這個表中,那么該方法返回表中已有字符串的地址,如果在表中沒有相同值的字符串,則將自己的地址注冊到表中”如果我把他說的這個全局的 String 表理解為常量池的話,他的最后一句話,”如果在表中沒有相同值的字符串,則將自己的地址注冊到表中”是錯的:

public static void main(String[] args) {    
    String s1 = new String("kvill"); 
    String s2 = s1.intern(); 
    System.out.println( s1 == s1.intern() ); //false
    System.out.println( s1 + " " + s2 ); //kvill kvill
    System.out.println( s2 == s1.intern() ); //true
  }

在這個類中我們沒有聲名一個”kvill”常量,所以常量池中一開始是沒有”kvill”的,當(dāng)我們調(diào)用s1.intern()后就在常量池中新添加了一 個”kvill”常量,原來的不在常量池中的”kvill”仍然存在,也就不是“將自己的地址注冊到常量池中”了。

   s1==s1.intern() 為false說明原來的”kvill”仍然存在;s2現(xiàn)在為常量池中”kvill”的地址,所以有s2==s1.intern()為true。

StringBuffer與StringBuilder的區(qū)別,它們的應(yīng)用場景是什么?

jdk的實(shí)現(xiàn)中StringBuffer與StringBuilder都繼承自AbstractStringBuilder,對于多線程的安全與非安全看到StringBuffer中方法前面的一堆synchronized就大概了解了。

這里隨便講講AbstractStringBuilder的實(shí)現(xiàn)原理:我們知道使用StringBuffer等無非就是為了提高java中字符串連接的效率,因?yàn)橹苯邮褂?進(jìn)行字符串連接的話,jvm會創(chuàng)建多個String對象,因此造成一定的開銷。AbstractStringBuilder中采用一個char數(shù)組來保存需要append的字符串,char數(shù)組有一個初始大小,當(dāng)append的字符串長度超過當(dāng)前char數(shù)組容量時,則對char數(shù)組進(jìn)行動態(tài)擴(kuò)展,也即重新申請一段更大的內(nèi)存空間,然后將當(dāng)前char數(shù)組拷貝到新的位置,因?yàn)橹匦路峙鋬?nèi)存并拷貝的開銷比較大,所以每次重新申請內(nèi)存空間都是采用申請大于當(dāng)前需要的內(nèi)存空間的方式,這里是2倍,

StringBuffer 始于 JDK 1.0

StringBuilder 始于 JDK 1.5

從 JDK 1.5 開始,帶有字符串變量的連接操作(+),JVM 內(nèi)部采用的是
StringBuilder 來實(shí)現(xiàn)的,而之前這個操作是采用 StringBuffer 實(shí)現(xiàn)的。

我們通過一個簡單的程序來看其執(zhí)行的流程:

/**
 * Java學(xué)習(xí)交流QQ群:589809992 我們一起學(xué)Java!
 */
public class Buffer { 
   public static void main(String[] args) { 
      String s1 = "aaaaa"; 
      String s2 = "bbbbb"; 
      String r = null; 
      int i = 3694; 
      r = s1 + i + s2;  

      for(int j=0;i<10;j++){ 
        r+="23124"; 
      } 
   } 
}

使用命令javap -c Buffer查看其字節(jié)碼實(shí)現(xiàn):

將清單1和清單2對應(yīng)起來看,清單2的字節(jié)碼中l(wèi)dc指令即從常量池中加載“aaaaa”字符串到棧頂,istore_1將“aaaaa”存到變量1中,后面的一樣,sipush是將一個短整型常量值(-32768~32767)推送至棧頂,這里是常量“3694”。  

讓我們直接看到13,13~17是new了一個StringBuffer對象并調(diào)用其初始化方法,20 ~ 21則是先通過aload_1將變量1壓到棧頂,前面說過變量1放的就是字符串常量“aaaaa”,接著通過指令invokevirtual調(diào)用StringBuffer的append方法將“aaaaa”拼接起來,后續(xù)的24 ~ 30同理。最后在33調(diào)用StringBuffer的toString函數(shù)獲得String結(jié)果并通過astore存到變量3中。  

看到這里可能有人會說,“既然JVM內(nèi)部采用了StringBuffer來連接字符串了,那么我們自己就不用用StringBuffer,直接用”+“就行了吧!“。是么?當(dāng)然不是了。俗話說”存在既有它的理由”,讓我們繼續(xù)看后面的循環(huán)對應(yīng)的字節(jié)碼。  

37~ 42都是進(jìn)入for循環(huán)前的一些準(zhǔn)備工作,37,38是將j置為1。44這里通過if_icmpge將j與10進(jìn)行比較,如果j大于10則直接跳轉(zhuǎn)到73,也即return語句退出函數(shù);否則進(jìn)入循環(huán),也即47~66的字節(jié)碼。這里我們只需看47到51就知道為什么我們要在代碼中自己使用StringBuffer來處理字符串的連接了,因?yàn)槊看螆?zhí)行“+”操作時jvm都要new一個StringBuffer對象來處理字符串的連接,這在涉及很多的字符串連接操作時開銷會很大。

如有疑問請留言或者到本站社區(qū)交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

相關(guān)文章

  • Java程序快速合并多個Word(docx)文檔

    Java程序快速合并多個Word(docx)文檔

    這篇文章主要為大家介紹了如何使用Java程序快速合并多個Word(docx)文檔實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • springboot集成redis存對象亂碼的問題及解決

    springboot集成redis存對象亂碼的問題及解決

    這篇文章主要介紹了springboot集成redis存對象亂碼的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Java?Object類equals方法

    Java?Object類equals方法

    這篇文章主要介紹了Java?Object類equals方法,Object類位于java.lang包中,java.lang包包含著Java最基礎(chǔ)和核心的類,在編譯時會自動導(dǎo)入,下文更多相關(guān)介紹,需要的小伙伴可以參考一下
    2022-04-04
  • 詳解JDK 5 Annotation 注解之@Target的用法介紹

    詳解JDK 5 Annotation 注解之@Target的用法介紹

    這篇文章主要介紹了詳解JDK 5 Annotation 注解之@Target的用法介紹,需要的朋友可以參考下
    2016-02-02
  • spring boot中使用http請求的示例代碼

    spring boot中使用http請求的示例代碼

    本篇文章主要介紹了spring boot中 使用http請求的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • Java如何分析算法的時間和空間復(fù)雜度

    Java如何分析算法的時間和空間復(fù)雜度

    這篇文章主要介紹了Java如何分析算法的時間和空間復(fù)雜度,在計(jì)算機(jī)科學(xué)中,計(jì)算復(fù)雜性解釋了算法的性能。文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下
    2022-06-06
  • java  Swing基礎(chǔ)教程之圖形化實(shí)例代碼

    java Swing基礎(chǔ)教程之圖形化實(shí)例代碼

    這篇文章主要介紹了java Swing基礎(chǔ)教程之圖形化實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • Spring?Boot?底層原理基礎(chǔ)深度解析

    Spring?Boot?底層原理基礎(chǔ)深度解析

    這篇文章主要介紹了Spring?Boot?底層原理基礎(chǔ),包括底層注解@Configuration,底層注解@Import及底層注解@Conditional的相關(guān)知識,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • Java集合之LinkedHashSet詳解

    Java集合之LinkedHashSet詳解

    這篇文章主要介紹了Java集合之LinkedHashSet詳解,LinkedHashSet是HashSet的子類,實(shí)現(xiàn)了Set接口,LinkedHashSet底層是一個LinkedHashMap,底層維護(hù)了一個數(shù)組+雙向鏈表,需要的朋友可以參考下
    2023-09-09
  • Java數(shù)據(jù)結(jié)構(gòu)之鏈表的增刪查改詳解

    Java數(shù)據(jù)結(jié)構(gòu)之鏈表的增刪查改詳解

    今天帶大家來學(xué)習(xí)Java鏈表的增刪改查的相關(guān)知識,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)Java的小伙伴們有很好的幫助,需要的朋友可以參考下
    2021-05-05

最新評論

汪清县| 武陟县| 盈江县| 南昌县| 晋中市| 沿河| 西宁市| 香格里拉县| 沿河| 青铜峡市| 贡山| 荆州市| 绥阳县| 凤冈县| 和平县| 南澳县| 随州市| 田阳县| 苍溪县| 景东| 会同县| 濮阳市| 西乌| 沙坪坝区| 云安县| 咸丰县| 神农架林区| 太湖县| 隆德县| 宁国市| 稻城县| 清水河县| 久治县| 荆门市| 三河市| 汕尾市| 临西县| 虞城县| 东辽县| 句容市| 沾化县|