在阅读了 SCJP Tip Line 的作者 Corey McGlone 在 javaranch 网站上的文章后,我感到很困惑。命名字符串,字面意思和
Kathy Sierra(javaranch 的联合创始人)和 Bert Bates 编写的 SCJP Java 6 程序员指南。
我将尝试引用 Corey 先生和 Kathy Sierra 女士所引用的有关字符串文字池的内容。
1. 根据 Corey McGlone 先生的说法:
String s = "Hello";
(假设 Heap 上没有名为“Hello”的对象),将创建一个 String 对象
"Hello"
在堆上,并将在字符串文字池(常量表)中放置对此对象的引用String a = new String("Bye");
(假设 Heap 上没有名为“Bye”的对象,new
运算符将强制 JVM 在 Heap 上创建一个对象。现在解释
"new"
用于创建字符串及其引用的运算符在本文中有点困惑,因此我将代码和文章本身的解释如下。
public class ImmutableStrings
{
public static void main(String[] args)
{
String one = "someString";
String two = new String("someString");
System.out.println(one.equals(two));
System.out.println(one == two);
}
}
在这种情况下,由于关键字
"new."
,我们实际上最终得到了稍微不同的行为。在这种情况下,对两个字符串字面量的引用仍被放入常量表(字符串字面量池)中,
但是,当您遇到关键字
"new,"
时JVM 有义务在运行时创建一个新的 String 对象,而不是使用常量表中的那个。
这是解释它的图表..
那么这是否意味着 String Literal Pool 也有对这个 Object 的引用?
这是 Corey McGlone 文章的链接
http://www.javaranch.com/journal/200409/Journal200409.jsp#a1
2. 根据 SCJP 书中 Kathy Sierra 和 Bert Bates 的说法:
遇到字符串文字,它会检查池以查看是否已存在相同的字符串。如果不是,那么它会创建一个
新的字符串文字对象。
String s = "abc";
//创建一个 String 对象和一个引用变量....没关系,但后来我被这句话搞糊涂了:
String s = new String("abc")
//创建两个对象和一个引用变量。它在书中说....一个新的 String 对象 in normal(non-pool) memory ,并且“s”将引用它...而
额外的文字“abc”将被放置在池中。
书中的上述几行与 Corey McGlone 的文章中的那一句相冲突。
请清除这个疑问,虽然在编写代码时不会有太大影响,但从内存管理方面来说非常重要,并且
这就是我想清除这个基金的原因。
最佳答案
我认为这里要理解的要点是 String
之间的区别。 Java 对象及其内容 - char[]
下 private value
field . String
基本上是围绕 char[]
的包装器数组,封装它并使其无法修改,所以 String
可以保持不变。还有 String
class 记住实际使用了这个数组的哪些部分(见下文)。这一切都意味着您可以拥有两个不同的 String
对象(相当轻量级)指向同一个 char[]
.
我将向您展示一些示例,以及 hashCode()
每个String
和 hashCode()
内部char[] value
字段(我将其称为文本以将其与字符串区分开来)。最后我会展示javap -c -verbose
输出,以及我的测试类的常量池。请不要将类常量池与字符串文字池混淆。它们并不完全相同。另见 Understanding javap's output for the Constant Pool .
先决条件
出于测试目的,我创建了这样一个实用方法,它会中断 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编译器会在编译时进行拼接,生成的代码完全一样。这仅在编译时已知所有字符串时才有效。类常量池
每个类(class)都有自己的constant pool - 一个常量值列表,如果它们在源代码中出现多次,则可以重用。它包括常见的字符串、数字、方法名称等。
下面是我们上面例子中常量池的内容。
const #2 = String #38; // abc
//...
const #38 = Asciz abc;
需要注意的重要一点是
String
之间的区别常量对象 ( #2
) 和 Unicode 编码文本 "abc"
( #38
) 字符串指向的。字节码
这是生成的字节码。请注意,
one
和 two
引用分配了相同的 #2
常量指向 "abc"
字符串:ldc #2; //String abc
astore_1 //one
ldc #2; //String abc
astore_2 //two
输出
对于每个示例,我正在打印以下值:
System.out.println(showInternalCharArrayHashCode(one));
System.out.println(showInternalCharArrayHashCode(two));
System.out.println(System.identityHashCode(one));
System.out.println(System.identityHashCode(two));
两对相等也就不足为奇了:
23583040
23583040
8918249
8918249
这意味着不仅两个对象都指向相同的
char[]
(下面相同的文字)所以 equals()
测试将通过。但更重要的是,one
和 two
是完全相同的引用!所以one == two
也是如此。显然如果 one
和 two
指向同一个对象然后 one.value
和 two.value
必须相等。文字和
new String()
Java代码
现在我们都在等待的例子 - 一个字符串文字和一个新的
String
使用相同的文字。这将如何运作?String one = "abc";
String two = new String("abc");
事实
"abc"
源代码中使用了两次常量应该给你一些提示......类常量池
和上面一样。
字节码
ldc #2; //String abc
astore_1 //one
new #3; //class java/lang/String
dup
ldc #2; //String abc
invokespecial #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
astore_2 //two
仔细看!第一个对象的创建方式与上述相同,这并不奇怪。它只需要一个对已经创建的常量引用
String
( #2
) 来自常量池。然而,第二个对象是通过正常的构造函数调用创建的。但!第一String
作为参数传递。这可以反编译为:String two = new String(one);
输出
输出有点令人惊讶。第二对,代表对
String
的引用对象是可以理解的 - 我们创建了两个 String
对象 - 一个是在常量池中为我们创建的,第二个是为 two
手动创建的.但是为什么,第一对实际上表明两者都String
对象指向同一个 char[] value
大批?!41771
41771
8388097
16585653
当您查看如何
String(String)
constructor works 时,您就清楚了。 (这里大大简化):public String(String original) {
this.offset = original.offset;
this.count = original.count;
this.value = original.value;
}
看?创建新时
String
基于现有对象,它 重复使用 char[] value
. String
s 是不可变的,不需要复制已知永远不会修改的数据结构。我认为这是您问题的线索:即使您有两个
String
对象,它们可能仍然指向相同的内容。正如你所看到的 String
对象本身很小。运行时修改和
intern()
Java代码
假设您最初使用了两个不同的字符串,但经过一些修改后它们都相同:
String one = "abc";
String two = "?abc".substring(1); //also two = "abc"
Java 编译器(至少是我的)不够聪明,无法在编译时执行此类操作,请看:
类常量池
突然间,我们得到了两个指向两个不同常量文本的常量字符串:
const #2 = String #44; // abc
const #3 = String #45; // ?abc
const #44 = Asciz abc;
const #45 = Asciz ?abc;
字节码
ldc #2; //String abc
astore_1 //one
ldc #3; //String ?abc
iconst_1
invokevirtual #4; //Method String.substring:(I)Ljava/lang/String;
astore_2 //two
拳头字符串像往常一样构造。第二个是通过首先加载常量
"?abc"
创建的。字符串,然后调用 substring(1)
在上面。输出
毫不奇怪 - 我们有两个不同的字符串,指向两个不同的
char[]
内存中的文字:27379847
7615385
8388097
16585653
嗯,文本并没有什么不同,
equals()
方法仍然会产生 true
.我们有两个不必要的相同文本的副本。现在我们应该进行两个练习。首先,尝试运行:
two = two.intern();
在打印哈希码之前。不仅两者
one
和 two
指向相同的文本,但它们是相同的引用!11108810
11108810
15184449
15184449
这意味着
one.equals(two)
和 one == two
测试将通过。我们也节省了一些内存,因为 "abc"
text 在内存中只出现一次(第二个副本将被垃圾收集)。第二个练习略有不同,看看这个:
String one = "abc";
String two = "abc".substring(1);
显然
one
和 two
是两个不同的对象,指向两个不同的文本。但是为什么输出表明它们都指向相同的 char[]
大批?!?23583040
23583040
11108810
8918249
我会把答案留给你。它会教你怎么做
substring()
有效,这种方法的优点是什么以及何时可以使用 lead to big troubles .
关于java - 字符串文字池是对字符串对象的引用的集合,还是对象的集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11700320/