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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java 字符串第一个字符_深入Java源码剖析之字符串常量

發布時間:2024/7/19 java 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 字符串第一个字符_深入Java源码剖析之字符串常量 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

字符串在Java生產開發中的使用頻率是非常高的,可見,字符串對于我們而言非常關鍵。那么從C語言過來的同學會發現,在C中是沒有String類型的,那么C語言要想實現字符串就必須使用char數組,通過一個個的字符來組拼成字符串。

Java中是如何實現字符串的

那其實在Java中,關于字符串的實現,其實用的也是char數組,這可以從源碼中得到體現。

/** * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * * @param original * A {@code String} */ public String(String original) { this.value = original.value; this.hash = original.hash; }復制代碼

這是String類的構造方法,而這個value實際上就是char數組。

/** The value is used for character storage. */ private final char value[];復制代碼

字符串在內存中的保存方式

我們都知道如何去創建一個字符串,那么, 字符串在內存中的保存方式是怎樣的呢?在內存中有一個區域叫做常量池,而當我們以這樣的方式去創建字符串:

String s1 = "abc";String s2 = "abc";復制代碼

這個字符串就一定會被保存到常量池中。而Java虛擬機如果發現常量池中已經存在需要創建的字符串中,它就不會重復創建,而是指向那個字符串即可。

String s1 = "abc";String s2 = "abc";System.out.println(s1 == s2);復制代碼

所以上述代碼段的執行結果一定是true。但是如果使用new關鍵字區創建字符串,過程就不太一樣了。比如下面的聲明:

String s3 = new String("abc");String s4 = new String("abc");復制代碼

過程是這樣的:首先將abc保存在常量池中,此時并沒有引用,然后new關鍵字會去創建一個字符串對象,就會在堆內存中創建abc,然后s3變量指向abc。當執行第二句聲明時,因為常量池中已經存在abc,所以不會重復創建,而new關鍵字又會去堆內存開辟空間存放abc,然后s4變量指向abc。

String s3 = new String("abc");String s4 = new String("abc");System.out.println(s3 == s4);復制代碼

所以上述代碼段的執行結果一定是false。

字符串駐留

當相同的字符串常量被多次創建時,注意是使用雙引號(" ")顯式聲明時,字符串常量對象會被保存在常量池中,且只會創建一個對象,這就是字符串駐留,這個名詞的產生就是為了提升性能。簡單提一下,字符串中有一個方法叫做intern();那么這個方法有什么作用呢? 該方法會去常量池中尋找當前調用該方法的字符串常量,若找到,則直接返回該字符串對象,若沒有,則將當前字符串放入常量池并返回,總之該方法一定會返回字符串。

String s3 = new String("abc");String s4 = new String("abc");System.out.println(s3.intern() == s4.intern());復制代碼

所以上述代碼段的執行結果一定是true,因為字符串駐留只允許常量池中一個相同字符串的存在。

JVM內存結構

剛才一直在說常量池,那么常量池具體在哪呢?這就要來研究一下JVM的內存結構。JVM分為堆、棧、方法區,棧又分為本地方法棧和Java棧。

在Java7之前常量池就放在方法區里,而從Java7開始,常量池被移到了堆。這樣說過于抽象,我們可以通過代碼來感受這一過程。

String s1 = new String("hello") + new String("world");String s2 = "helloworld";System.out.println(s1 == s2);復制代碼

上述程序段的執行結果一定是false。因為s1變量在堆中,而s2變量在常量池中,兩者肯定不相同。那么看下面這段代碼,猜猜看結果是什么?

String s1 = new String("hello") + new String("world");System.out.println(s1.intern() == s1);復制代碼

按照剛才的分析,intern()返回的一定是常量池里的字符串,而s1變量在堆中,它們肯定是不一樣的,但運行結果竟然是true。那是不是就能解釋常量池在堆中,所以它們指向的是同一個對象呢?其實還不完全是,我們可以繼續看一段代碼。

String s1 = new String("hello") + new String("world");System.out.println(s1.intern() == s1);String s2 = new String("hello") + new String("world"); System.out.println(s2.intern() == s2);復制代碼

這段代碼的運行結果:

truefalse復制代碼

感覺很神奇,讓人猜不透,摸不著。別急,下面我們來一起分析一下。

通過這個圖來理解一下,首先第一行代碼會在常量池中創建hello和world兩個字符串,接著在堆中開辟了一個空間存放組合后的字符串helloworld,然后變量s1指向它。我們說intern()會返回常量池中的字符串,那么在常量池中沒有helloworld的情況下intern()方法會怎樣處理呢?其實它會將對堆中helloworld的引用放入常量池中,此時s1.intern()和s1都指向的是同一個對象,它們是相等的。但是s2在創建的過程中也會在堆中開辟一個空間存放helloworld,使變量s2指向它,而s2.intern()方法在執行的時候發現,helloworld的引用已經存在,所以直接返回,但此時返回的其實是s1變量的引用,那么s2.intern()與s2不相等相信大家能夠理解了。

String s1 = new String("hello") + new String("world");System.out.println(s1.intern() == s1);String s2 = new String("hello") + new String("world");System.out.println(s2.intern() == s1);復制代碼

那么這段程序的輸出結果你若是能立馬知曉,那么恭喜你,前面的知識點你已基本掌握。執行結果就是:

truetrue復制代碼

我們還可以通過一個極端的方法來判斷常量池的位置。

List list = new ArrayList();String str = "boom";for(int i = 0;i < Integer.MAX_VALUE;i++) { String temp = str + i; str = temp; list.add(temp.intern()); }復制代碼

通過編寫這一段程序能夠讓JVM去不停地將字符串變量存入常量池從而使其內存溢出,內存溢出后控制臺信息如下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOfRange(Arrays.java:2694) at java.lang.String.(String.java:203) at java.lang.StringBuilder.toString(StringBuilder.java:405) at com.itcast.test2.StringTest.main(StringTest.java:25)復制代碼

可以看到,控制臺信息提示堆內存溢出,這也可以得出常量池的位置是在堆內。這是Java7及其以后版本的輸出信息,當我們將版本切換為Java7之前的版本,同樣的代碼,輸出信息如下:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.util.Arrays.copyOfRange(Arrays.java:2694) at java.lang.String.(String.java:203) at java.lang.StringBuilder.toString(StringBuilder.java:405) at com.itcast.test2.StringTest.main(StringTest.java:25)復制代碼

PermGen space其實就是方法區, 那么其實在JVM中的堆,一般分為三大部分:新生代、老年代、永久代:這個PermGen space就是永久代,也就是方法區,叫法不同而已。

其它問題

繼續來探討一下關于字符串常量的一些其它問題。

String s1 = "hello" + "world";String s2 = "helloworld";System.out.println(s1 == s2); String temp = "hello";String s3 = temp + "world";String s4 = "helloworld";System.out.println(s3 == s4);復制代碼

那么,這兩個輸出的結果是什么呢?結果是:

truefalse復制代碼

第一個輸出為true不難理解,因為s1和s2指向的都是常量池中的helloworld字符串,那么s3和s4難道就不是嗎?它還真就不是這樣了。s3在創建過程中會將temp保存在堆內存中,所以s3和s4指向的對象不是同一個。我們可以通過反編譯來證實,將這段代碼的.class文件進行反編譯,結果如下:

String s1 = "helloworld";String s2 = "helloworld"; System.out.println(s1 == s2); String temp = "hello";String s3 = String.valueOf(temp) + "world";String s4 = "helloworld";System.out.println(s3 == s4);復制代碼

我們可以看到,s1和s2的創建過程其實是一模一樣的,其實,JVM為了優化速度,當它確定是兩個字符串常量進行拼接時,它會在編譯器就完成拼接,而并不會去創建對象處理,但是s3的創建要經過temp變量,因為JVM無法在編譯期就推測出temp,所以它要通過String對象來進行處理,將temp放入堆內存。所以,并不是說只有出現new關鍵字變量才會放入堆內存中。

希望這篇文章能夠使你更加深入地理解字符串常量。

總結

以上是生活随笔為你收集整理的java 字符串第一个字符_深入Java源码剖析之字符串常量的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。