阅读了SCJP Tip Line的作者Corey McGlone在javaranch网站上的文章后,我都感到困惑。从字面上看是Strings,由Kathy Sierra(javaranch的联合创始人)和Bert Bates共同编写的《 SCJP Java 6程序员指南》。
我将尝试引用Corey先生和Kathy Sierra女士对String Literal Pool的引用。
1.根据科里·麦格隆先生的说法:
字符串文字池是指向字符串对象的引用的集合。
String s = "Hello";
(假设”Hello”堆上没有名为“ Hello”
的对象),将在堆上创建一个String对象,并将对该对象的引用放置在String Literal Pool(常量表)中
String a = new String("Bye")
;(假设堆上没有名为“ Bye” new
的对象,操作员将要求JVM在堆上创建一个对象。
现在,在本文中对”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."
在这种情况下,对两个String文字的引用仍然放在常量表(String Literal Pool)
中,但是当你使用关键字时”new,”JVM必须在运行时创建一个新的String对象,而不是使用常量表中的对象。
这是解释它的图。
这是否意味着String Literal Pool
也具有对此对象的引用?
这是Corey McGlone的文章链接
http://www.javaranch.com/journal/200409/Journal200409.jsp#a1
2.根据SCJP书中的Kathy Sierra和Bert Bates的说法:
为了使Java的内存使用效率更高,JVM预留了一个特殊的内存区域,称为“字符串常量池”,当编译器遇到String Literal时,它将检查该池以查看是否已经存在相同的String。如果没有,它将创建一个新的字符串文字对象。
String s = "abc";
//创建一个String对象和一个引用变量。
很好,但随后我对以下声明感到困惑:
String s = new String("abc")
//创建两个对象和一个引用变量。它在书中说:....普通(非池)内存中的新String对象,“ s”将引用它…而池中将放置一个附加的文字“ abc”。
本书中的上述几行与Corey McGlone的文章中的那行相冲突。
如果字符串文字池是Corey McGlone提到的对String对象的引用的集合,那么为什么要将文字对象“ abc”放在池中(如书中所述)?
字符串文字池位于何处?
请清除此疑问,尽管在编写代码时并不太重要,但是从内存管理的角度来看这非常重要,这就是我要清除此基础的原因。
我认为这里要了解的主要点是在私有字段下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)和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也为true。显然,如果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)构造函数的工作原理时,这一点变得很清楚(此处已大大简化了):
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; // 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”文本在内存中仅出现一次(第二个副本将被垃圾回收)。
第二个练习略有不同,请查看以下内容:
String one = "abc";
String two = "abc".substring(1);
显然,one和two是两个不同的对象,指向两个不同的文本。但是输出如何表明它们都指向同一个char[]数组?!
23583040
23583040
11108810
8918249
我将答案留给你。它会教你如何substring()工作,这种方法的优点是什么,以及何时会导致大麻烦。
问题内容: 这是以前有关Java中的String初始化的一些问题的后续问题。 在用Java进行了一些小测试之后,我面临以下问题: 为什么我可以执行此语句 当str2一个String对象初始化为,但我不能调用方法toString()上str2?那么Java如何将空字符串对象和字符串文字串联起来? 顺便说一句,我还尝试将一个初始化为null和字符串文字的Integer连接起来,”a_literal_s
我是Mapstruct的新手。我有一个Word对象,它包含一个字符串值和一组它自己,我想把它映射到WordDTO,它包含一个值和一组字符串值。我不知道怎么做。正如我在注释中所说,mapstruct不能映射两个对象是有道理的,但如果它有帮助,我将错误放在下面: 我为映射实现了这个接口: 谢谢你的帮助。
问题内容: 谁能从此链接http://www.javatpoint.com/corejava-interview- questions-3 解释问题87和89 87)在下面的代码中将创建多少个对象? 答案是:只有一个物体 89)在下面的代码中将创建多少个对象? 答案是:两个对象,一个在字符串常量池中,另一个在非池(堆)中。 问题答案: 尽管是用Java编写的类,但它是一种与JVM有特殊关系的特殊类
问题内容: Douglas Crockford喜欢说在javascript原型面向对象语言中不需要。 他解释说,添加这些代码只是为了使来自基于类(即“ 经典 ”)的面向对象的编程语言的人们感到某种程度的舒适: JavaScript中,我们很难雅 JavaScript是一种原型语言,但是它有一个运算符,试图使它看起来像古典语言。这往往会使程序员感到困惑,从而导致一些有问题的编程模式。 您无需在Jav
问题内容: 之间有什么区别 和 问题答案: 当你使用字符串文字时,可以将字符串嵌入,但是当你使用时,你将获得一个新的字符串对象。 在此示例中,两个字符串文字均引用相同的对象: 在这里,创建了2个不同的对象,它们具有不同的引用: 通常,应尽可能使用字符串文字表示法。它更易于阅读,并且为编译器提供了优化代码的机会。
问题内容: 在Android API中http://developer.android.com/guide/topics/data/data- storage.html#pref 它说: 共享首选项允许您保存和检索原始数据类型的持久键-值对。您可以使用SharedPreferences保存任何原始数据:布尔值,浮点数,整数,长型和字符串。 字符串是原始数据类型还是对象? 问题答案: 就编程语言而言