国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - java String的深入理解

java String的深入理解

2021-01-06 10:54CSDN Java教程

這篇文章主要介紹了java String的深入理解的相關(guān)資料,希望通過本文大家能理解String的用法,需要的朋友可以參考下

java String的深入理解

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

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

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

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

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

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

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

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

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

二、案例解析

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public static void main(String[] args) {
    /**
     * 情景一:字符串池
     * JAVA虛擬機(jī)(JVM)中存在著一個(gè)字符串池,其中保存著很多String對(duì)象;
     * 并且可以被共享使用,因此它提高了效率。
     * 由于String類是final的,它的值一經(jīng)創(chuàng)建就不可改變。
     * 字符串池由String類維護(hù),我們可以調(diào)用intern()方法來訪問字符串池。
     */
    String s1 = "abc";  
    //↑ 在字符串池創(chuàng)建了一個(gè)對(duì)象
    String s2 = "abc";  
    //↑ 字符串pool已經(jīng)存在對(duì)象“abc”(共享),所以創(chuàng)建0個(gè)對(duì)象,累計(jì)創(chuàng)建一個(gè)對(duì)象
    System.out.println("s1 == s2 : "+(s1==s2)); 
    //↑ true 指向同一個(gè)對(duì)象,
    System.out.println("s1.equals(s2) : " + (s1.equals(s2))); 
    //↑ true 值相等
    //↑------------------------------------------------------over
    /**
     * 情景二:關(guān)于new String("")
     *
     */
    String s3 = new String("abc");
    //↑ 創(chuàng)建了兩個(gè)對(duì)象,一個(gè)存放在字符串池中,一個(gè)存在與堆區(qū)中;
    //↑ 還有一個(gè)對(duì)象引用s3存放在棧中
    String s4 = new String("abc");
    //↑ 字符串池中已經(jīng)存在“abc”對(duì)象,所以只在堆中創(chuàng)建了一個(gè)對(duì)象
    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ū)多不同,一個(gè)棧區(qū),一個(gè)堆區(qū)
    System.out.println("s1.equals(s3) : "+(s1.equals(s3)));
    //↑true 值相同
    //↑------------------------------------------------------over
    /**
     * 情景三:
     * 由于常量的值在編譯的時(shí)候就被確定(優(yōu)化)了。
     * 在這里,"ab"和"cd"都是常量,因此變量str3的值在編譯時(shí)就可以確定。
     * 這行代碼編譯后的效果等同于: String str3 = "abcd";
     */
    String str1 = "ab" + "cd"; //1個(gè)對(duì)象
    String str11 = "abcd"
    System.out.println("str1 = str11 : "+ (str1 == str11));
    //↑------------------------------------------------------over
    /**
     * 情景四:
     * 局部變量str2,str3存儲(chǔ)的是存儲(chǔ)兩個(gè)拘留字符串對(duì)象(intern字符串對(duì)象)的地址。
     *
     * 第三行代碼原理(str2+str3):
     * 運(yùn)行期JVM首先會(huì)在堆中創(chuàng)建一個(gè)StringBuilder類,
     * 同時(shí)用str2指向的拘留字符串對(duì)象完成初始化,
     * 然后調(diào)用append方法完成對(duì)str3所指向的拘留字符串的合并,
     * 接著調(diào)用StringBuilder的toString()方法在堆中創(chuàng)建一個(gè)String對(duì)象,
     * 最后將剛生成的String對(duì)象的堆地址存放在局部變量str3中。
     *
     * 而str5存儲(chǔ)的是字符串池中"abcd"所對(duì)應(yīng)的拘留字符串對(duì)象的地址。
     * str4與str5地址當(dāng)然不一樣了。
     *
     * 內(nèi)存中實(shí)際上有五個(gè)字符串對(duì)象:
     *    三個(gè)拘留字符串對(duì)象、一個(gè)String對(duì)象和一個(gè)StringBuilder對(duì)象。
     */
    String str2 = "ab"; //1個(gè)對(duì)象
    String str3 = "cd"; //1個(gè)對(duì)象                    
    String str4 = str2+str3;                   
    String str5 = "abcd"
    System.out.println("str4 = str5 : " + (str4==str5)); // false
    //↑------------------------------------------------------over
    /**
     * 情景五:
     * JAVA編譯器對(duì)string + 基本類型/常量 是當(dāng)成常量表達(dá)式直接求值來優(yōu)化的。
     * 運(yùn)行期的兩個(gè)string相加,會(huì)產(chǎn)生新的對(duì)象的,存儲(chǔ)在堆(heap)中
     */
    String str6 = "b";
    String str7 = "a" + str6;
    String str67 = "ab";
    System.out.println("str7 = str67 : "+ (str7 == str67));
    //↑str6為變量,在運(yùn)行期才會(huì)被解析。
    final String str8 = "b";
    String str9 = "a" + str8;
    String str89 = "ab";
    System.out.println("str9 = str89 : "+ (str9 == str89));
    //↑str8為常量變量,編譯期會(huì)被優(yōu)化
    //↑------------------------------------------------------over
  }

總結(jié):

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

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

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

?
1
2
3
4
5
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只對(duì)引用的”值”(即內(nèi)存地址)有效,它迫使引用只能指向初始指向的那個(gè)對(duì)象,改變它的指向會(huì)導(dǎo)致編譯期錯(cuò)誤。至于它所指向的對(duì)象的變化,final是不負(fù)責(zé)的。

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

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

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

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

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)建了一個(gè)String的實(shí)例,但是value是等于常量池中的實(shí)例的value,即是說沒有new一個(gè)新的字符數(shù)組來存放”123”。

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

6.String.intern()

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

?
1
public native String intern();

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 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
  }

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

?
1
2
3
4
5
6
7
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
  }

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

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

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

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

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

StringBuffer 始于 JDK 1.0

StringBuilder 始于 JDK 1.5

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 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):

java String的深入理解

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

讓我們直接看到13,13~17是new了一個(gè)StringBuffer對(duì)象并調(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中。  

看到這里可能有人會(huì)說,“既然JVM內(nèi)部采用了StringBuffer來連接字符串了,那么我們自己就不用用StringBuffer,直接用”+“就行了吧!“。是么?當(dāng)然不是了。俗話說”存在既有它的理由”,讓我們繼續(xù)看后面的循環(huán)對(duì)應(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語(yǔ)句退出函數(shù);否則進(jìn)入循環(huán),也即47~66的字節(jié)碼。這里我們只需看47到51就知道為什么我們要在代碼中自己使用StringBuffer來處理字符串的連接了,因?yàn)槊看螆?zhí)行“+”操作時(shí)jvm都要new一個(gè)StringBuffer對(duì)象來處理字符串的連接,這在涉及很多的字符串連接操作時(shí)開銷會(huì)很大。

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

原文鏈接:http://geek.csdn.net/news/detail/236747

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 欧美日韩电影一区二区三区 | 在线观看一区 | 激情欧美一区二区三区中文字幕 | 免费观看全黄做爰大片国产 | 午夜精品在线观看 | 亚洲欧美另类久久久精品2019 | 欧洲一级毛片 | 九九热在线视频 | 山岸逢花在线观看无删减 | 日韩精品免费一区二区夜夜嗨 | 午夜视频在线观看网站 | 香蕉久久夜色精品国产使用方法 | av手机在线播放 | 日韩av在线中文字幕 | 欧美成人免费在线 | 天天澡天天狠天天天做 | 午夜成人免费视频 | 欧美黄色一区 | 欧美日韩一区二区视频在线观看 | 中文亚洲字幕 | 成人性大片免费观看网站 | 亚洲欧美日韩在线一区二区三区 | 欧美日韩在线播放 | 欧美视频成人 | 免费看一区二区三区 | 久久久久久久久久久免费视频 | 91在线麻豆 | 在线日韩视频 | 国产精品1区2区在线观看 | 欧美色综合天天久久综合精品 | 久久久精 | 日韩不卡 | 亚洲国产精品久久久久秋霞蜜臀 | 国产精品久久嫩一区二区免费 | 亚洲国产二区 | 九九精品在线 | 99精品国产一区二区青青牛奶 | 精品久久久久久久人人人人传媒 | 91精品国产乱码久久久久久 | 亚洲欧美在线观看视频 | 成人午夜精品一区二区三区 |