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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

字符串内存内部

發(fā)布時(shí)間:2023/12/3 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 字符串内存内部 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
本文基于我對(duì)StackOverflow的回答 。 我正在嘗試解釋String類如何存儲(chǔ)文本,內(nèi)部存儲(chǔ)和常量池如何工作。

這里要理解的要點(diǎn)是String Java對(duì)象與其內(nèi)容– private value字段下的char[]之間的區(qū)別。 String基本上是char[]數(shù)組的包裝器,將其封裝并使其無法修改,因此String可以保持不變。 另外, String類還記住該數(shù)組的實(shí)際部分(請(qǐng)參閱下文)。 這一切都意味著您可以擁有兩個(gè)指向相同char[]不同String對(duì)象(相當(dāng)輕量)。

我會(huì)告訴你一些例子,連同hashCode()的每個(gè)String和hashCode()內(nèi)部的char[] value字段(我將其稱之為文本字符串從區(qū)分)。 最后,我將顯示javap -c -verbose輸出以及測(cè)試類的常量池。 請(qǐng)不要將類常量池與字符串文字池混淆。 它們并不完全相同。 另請(qǐng)參見了解常量池的javap輸出 。

先決條件

為了進(jìn)行測(cè)試,我創(chuàng)建了一個(gè)實(shí)用程序方法來破壞String封裝:

private int showInternalCharArrayHashCode(String s) {final Field value = String.class.getDeclaredField("value");value.setAccessible(true);return value.get(s).hashCode(); }

它將打印char[] value hashCode() ,有效地幫助我們了解此特定String是否指向相同的char[]文本。

一個(gè)類中的兩個(gè)字符串文字

讓我們從最簡(jiǎn)單的示例開始。

Java代碼

String one = "abc"; String two = "abc";

順便說一句,如果您只寫"ab" + "c" ,則Java編譯器將在編譯時(shí)執(zhí)行串聯(lián),并且生成的代碼將完全相同。 僅當(dāng)在編譯時(shí)知道所有字符串時(shí),此方法才有效。

類常量池
每個(gè)類都有自己的常量池 -常量值列表,如果它們?cè)谠创a中多次出現(xiàn),則可以重用。 它包括常見的字符串,數(shù)字,方法名稱等。 這是上面示例中常量池的內(nèi)容:

const #2 = String #38; // abc //... const #38 = Asciz abc;

需要注意的重要事項(xiàng)是String常量對(duì)象( #2 )和字符串指向的Unicode編碼文本"abc" ( #38 )之間的區(qū)別。

字節(jié)碼
這是生成的字節(jié)碼。 請(qǐng)注意, one引用和two引用都分配有指向"abc"字符串的相同#2常量:

ldc #2; //String abc astore_1 //one ldc #2; //String abc astore_2 //two

輸出量
對(duì)于每個(gè)示例,我將打印以下值:

System.out.println("one.value: " + showInternalCharArrayHashCode(one)); System.out.println("two.value: " + showInternalCharArrayHashCode(two)); System.out.println("one" + System.identityHashCode(one)); System.out.println("two" + System.identityHashCode(two));

這兩對(duì)相等并不奇怪:

one.value: 23583040 two.value: 23583040 one: 8918249 two: 8918249

這意味著不僅兩個(gè)對(duì)象都指向相同的char[] (下面的相同文本),所以equals()測(cè)試將通過。 但更重要的是, one和two是完全相同的引用! 因此, one == two也是正確的。 顯然,如果one和two指向同一個(gè)對(duì)象,則one.value和two.value必須相等。

文字和new String() ?

Java代碼
現(xiàn)在,我們都在等待該示例–一個(gè)字符串文字和一個(gè)使用相同文字的新String 。 這將如何運(yùn)作?

String one = "abc"; String two = new String("abc");

在源代碼中兩次使用了"abc"常量這一事實(shí)應(yīng)該給您一些提示……

類常量池與上面相同。

字節(jié)碼

ldc #2; //String abc astore_1 //onenew #3; //class java/lang/String dup ldc #2; //String abc invokespecial #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V astore_2 //two

仔細(xì)地看! 第一個(gè)對(duì)象的創(chuàng)建方法與上面相同,不足為奇。 它只需要從常量池中常量引用已經(jīng)創(chuàng)建的String ( #2 )。 但是,第二個(gè)對(duì)象是通過常規(guī)構(gòu)造函數(shù)調(diào)用創(chuàng)建的。 但! 第一個(gè)String作為參數(shù)傳遞。 可以將其反編譯為:

String two = new String(one);

輸出量
輸出有點(diǎn)令人驚訝。 第二對(duì)表示對(duì)String對(duì)象的引用是可以理解的-我們創(chuàng)建了兩個(gè)String對(duì)象-一個(gè)在常量池中為我們創(chuàng)建,第二個(gè)是為two手動(dòng)創(chuàng)建的。 但是,為什么第一對(duì)建議兩個(gè)String對(duì)象都指向同一個(gè)char[] value數(shù)組呢?

one.value: 41771 two.value: 41771 one: 8388097 two: 16585653

當(dāng)您查看String(String)構(gòu)造函數(shù)的工作原理時(shí),這一點(diǎn)變得很清楚(此處已大大簡(jiǎn)化):

public String(String original) {this.offset = original.offset;this.count = original.count;this.value = original.value; }

看到? 在基于現(xiàn)有對(duì)象創(chuàng)建新的String對(duì)象時(shí),它會(huì)重用 char[] value 。 String是不可變的,不需要復(fù)制已知永遠(yuǎn)不會(huì)修改的數(shù)據(jù)結(jié)構(gòu)。 而且,由于new String(someString)創(chuàng)建了現(xiàn)有字符串的精確副本,并且字符串是不可變的,因此顯然沒有理由同時(shí)存在兩者。
我認(rèn)為這是一些誤解的線索:即使您有兩個(gè)String對(duì)象,它們?nèi)钥赡苤赶蛳嗤膬?nèi)容。 如您所見, String對(duì)象本身很小。

運(yùn)行時(shí)修改和intern() ?

Java代碼
假設(shè)您最初使用了兩個(gè)不同的字符串,但是在進(jìn)行一些修改之后,它們都是相同的:

String one = "abc"; String two = "?abc".substring(1); //also two = "abc"

Java編譯器(至少是我的)不夠聰明,無法在編譯時(shí)執(zhí)行此類操作,請(qǐng)看一下:

類常量池
突然我們以指向兩個(gè)不同常量文本的兩個(gè)常量字符串結(jié)尾:

const #2 = String #44; // abc const #3 = String #45; // ?abc const #44 = Asciz abc; const #45 = Asciz ?abc;

字節(jié)碼

ldc #2; //String abc astore_1 //oneldc #3; //String ?abc iconst_1 invokevirtual #4; //Method String.substring:(I)Ljava/lang/String; astore_2 //two

拳頭弦照常構(gòu)造。 通過首先加載常量"?abc"字符串,然后在其上調(diào)用substring(1)來創(chuàng)建第二個(gè)。

輸出量

這里不足為奇–我們有兩個(gè)不同的字符串,指向內(nèi)存中兩個(gè)不同的char[]文本:

one.value: 27379847 two.value: 7615385 one: 8388097 two: 16585653

好吧,文本并沒有真正的不同 , equals()方法仍然會(huì)產(chǎn)生true 。 我們有兩個(gè)不必要的相同文本副本。
現(xiàn)在我們應(yīng)該進(jìn)行兩次練習(xí)。 首先,嘗試運(yùn)行:

two = two.intern();

在打印哈希碼之前。 one和two不僅指向同一文本,而且它們是相同的參考!

one.value: 11108810 two.value: 11108810 one: 15184449 two: 15184449

這意味著one.equals(two)和one == two測(cè)試都將通過。 我們還節(jié)省了一些內(nèi)存,因?yàn)?#34;abc"文本在內(nèi)存中僅出現(xiàn)一次(第二個(gè)副本將被垃圾回收)。
第二個(gè)練習(xí)略有不同,請(qǐng)查看以下內(nèi)容:

String one = "abc"; String two = "abc".substring(1);

顯然one和two是兩個(gè)不同的對(duì)象,指向兩個(gè)不同的文本。 但是輸出如何表明它們都指向同一個(gè)char[]數(shù)組?!

one.value: 11108810 two.value: 8918249 one: 23583040 two: 23583040

我將答案留給你。 它會(huì)教您substring()工作原理,這種方法的優(yōu)點(diǎn)是什么以及何時(shí)會(huì)導(dǎo)致大麻煩 。

得到教訓(xùn)

  • String對(duì)象本身相當(dāng)便宜。 它指向的文本占用了大部分內(nèi)存
  • String只是char[]的薄包裝,以保持不變性
  • new String("abc")作為內(nèi)部文本表示被重用是不是真的那么貴。 但是還是要避免這樣的構(gòu)造。
  • 從編譯時(shí)已知的常量值連接String時(shí),連接由編譯器而不是由JVM完成
  • substring()有點(diǎn)棘手,但最重要的是,就使用的內(nèi)存和運(yùn)行時(shí)間而言,它都很便宜(在兩種情況下均保持不變)

參考:來自Java和社區(qū)博客的JCG合作伙伴 Tomasz Nurkiewicz的字符串內(nèi)存內(nèi)部結(jié)構(gòu) 。


翻譯自: https://www.javacodegeeks.com/2012/07/string-memory-internals.html

總結(jié)

以上是生活随笔為你收集整理的字符串内存内部的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

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