Java 字符串文字池是对字符串对象的引用集合,还是对象的集合

Java 字符串文字池是对字符串对象的引用集合,还是对象的集合,第1张

Java 字符串文字池是对字符串对象的引用集合,还是对象的集合

我认为这里要了解的主要点是在私有字段下

StringJava
对象及其内容之间的区别。基本上是数组的包装器,将其封装并使其无法修改,因此可以保持不变。另外,类记住此阵列的部分被实际使用(见下文)。这一切都意味着你可以拥有两个指向同一个对象(轻量级)。
char[]valueStringchar[]StringStringStringchar[]

我会告诉你一些例子,连同

hashCode()
每一个
String
hashCode()
内部的
char[] value
领域(我将其称之为文本从字符串相区别)。最后,我将显示
javap -c -verbose
输出以及测试类的常量池。请不要将类常量池与字符串文字池混淆。它们并不完全相同。另请参见了解常量池的
javap
输出。

先决条件

为了进行测试,我创建了一种破坏String封装的实用程序方法:

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

这将打印

hashCode()
char[] value
,有效地帮助我们理解这是否特定
String
指向相同的
char[]
文字或没有。

一个类中的两个字符串文字
让我们从最简单的示例开始。

Java代码

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

顺便说一句,如果你只是编写

"ab" + "c"
,Java编译器将在编译时执行串联,并且生成的代码将完全相同。仅当在编译时知道所有字符串时,此方法才有效。

类常量池
每个类都有自己的常量池 -如果常量值在源代码中多次出现,则可以重用这些常量值的列表。它包括常见的字符串,数字,方法名称等。

这是上面示例中常量池的内容。

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

需要注意的重要事项是字符串所指向的String常量对象(#2)和Unipre编码文本”abc”(#38)之间的区别。

字节码
这是生成的字节码。请注意,两个one和two引用均分配有#2指向”abc”字符串的相同常量:

ldc #2; //String abcastore_1    //oneldc #2; //String abcastore_2    //two

输出量

对于每个示例,我将打印以下值:

System.out.println(showInternalCharArrayHashCode(one));System.out.println(showInternalCharArrayHashCode(two));System.out.println(System.identityHashCode(one));System.out.println(System.identityHashCode(two));

这两对相等并不奇怪:

235830402358304089182498918249

这意味着不仅两个对象都指向相同char[](下面的相同文本),所以equals()测试将通过。但更多,one并且two是完全相同的参考!因此one == two也为true。显然,如果one和two指向相同的对象,则one.value并且two.value必须相等。

文字和

new String()

Java代码

现在我们都在等待该示例-一个字符串文字和一个String使用相同文字的新文字。这将如何运作?

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

“abc”在源代码中两次使用常量这一事实应该给你一些提示…

类常量池
同上。

字节码

ldc #2; //String abcastore_1    //onenew #3; //class java/lang/Stringdupldc #2; //String abcinvokespecial   #4; //Method java/lang/String."<init>":(Ljava/lang/String;)Vastore_2    //two

仔细看!第一个对象的创建方法与上面相同,不足为奇。它只需要从常量池中对已经创建的String(#2)进行常量引用。但是,第二个对象是通过常规构造函数调用创建的。但!第一个String作为参数传递。可以将其反编译为:

String two = new String(one);

输出量
输出有点令人惊讶。第二对代表String对象的引用是可以理解的-我们创建了两个String对象-一个是在常量池中为我们创建的,第二个是为手动创建的two。但是,为什么在地球上第一对暗示两个String对象都指向同一个char[] value数组呢?

4177141771838809716585653

当你查看String(String)构造函数的工作原理时,这一点变得很清楚(此处已大大简化了):

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

看到?在String基于现有对象创建新对象时,它会重用 char[] value。Strings是不可变的,因此无需复制已知永远不会修改的数据结构。

我认为这就是你问题的线索:即使你有两个String对象,它们可能仍指向相同的内容。如你所见,String对象本身很小。

运行时修改和

intern()

Java代码

假设你最初使用了两个不同的字符串,但是在进行一些修改之后,它们都是相同的:

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

Java编译器(至少是我的)不够聪明,无法在编译时执行此类 *** 作,请看一下:

类常量池

突然我们以指向两个不同常量文本的两个常量字符串结尾:

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

字节码

ldc #2; //String abcastore_1    //oneldc #3; //String ?abciconst_1invokevirtual   #4; //Method String.substring:(I)Ljava/lang/String;astore_2    //two

拳头弦是照常构造的。第二个是通过首先加载常量”?abc”字符串然后调用substring(1)它来创建的。

输出量
这里不足为奇-我们有两个不同的字符串,指向char[]内存中的两个不同的文本:

273798477615385838809716585653

好吧,文字并没有什么不同,

equals()
方法仍然会产生效果true。我们有两个相同文本的不必要副本。

现在我们应该进行两次练习。首先,尝试运行:

two = two.intern();

在打印哈希码之前。不仅双方one并two指向相同的文字,但它们是相同的参考!

11108810111088101518444915184449

这意味着

one.equals(two)
one == two
测试都将通过。我们还节省了一些内存,因为”abc”文本在内存中仅出现一次(第二个副本将被垃圾回收)。

第二个练习略有不同,请查看以下内容:

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

显然,one和two是两个不同的对象,指向两个不同的文本。但是输出如何表明它们都指向同一个char[]数组?!

2358304023583040111088108918249

我将答案留给你。它会教你如何substring()工作,这种方法的优点是什么,以及何时会导致大麻烦。



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5432117.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-11
下一篇 2022-12-11

发表评论

登录后才能评论

评论列表(0条)

保存