两个内容相同的字符串是否会被存储在相同的内存位置?

25

这是我在面试中遇到的一个问题。

我定义了两个字符串,如下:

String s1="Java";
String s2="Java";

我的问题是这两个引用是否指向同一内存位置。通常情况下,当我们创建相同的字符串(没有使用new关键字),内容是否只被存储在内存中一次,所有具有相同内容的String对象都只是指向同一个位置,而不会多次存储字符串"Java"? s1和s2的哈希码是相同的。但哈希码是否直接依赖于对象的内存位置?


请不要打我,我在这个问题上标记了“C#”和“语言无关”的标签,因为我想知道其他平台和语言是否有任何差异。 - Gennady Vanin Геннадий Ванин
1
这种知识是区分高效程序员和只会回答无用问题的高效求职者的关键。 - Gennady Vanin Геннадий Ванин
可能是重复的问题:如何在Java中比较字符串? - Peter O.
2
@GennadyVanin--Novosibirsk,创建其他语言的类似问题不是更好吗?目前答案似乎全部都是针对Java的。 - Brian Rasmussen
@BrianRasmussen,谢谢,我已经做了。真是浪费时间!再有几个这样的问题,我就要放弃我的项目deadline,去学习和回答这些问题了。 - Gennady Vanin Геннадий Ванин
9个回答

27
将相同字符串合并的过程称为“interning”,许多语言编译器已经进行了多年,但并非总是如此。特别是由@GennadyVanin--Novosibirsk扩展的问题的答案取决于语言和编译器实现。对于Java,所有常量字符串都被联接,这是Java Language Specification所要求的。但这仅适用于常量字符串表达式,并且只有在同时编译它们时才会联接。如果您有两个Java字符串在时间和空间上足够分离(例如编译到单独的JAR文件中),它们将不是同一个对象。同样,动态创建的Java字符串(例如各种toString()方法的输出)除非该方法通过String.intern()明确请求,否则不会被联接。是的,所有使用联接字符串的地方都将共享相同的内存位置 - 这是为什么首先要联接字符串的重要原因之一。

至于其他语言,这是一个更大的问题,但通过这些答案中的所有信息,我相信你可以在网上研究它。简而言之,没有普遍一致的意见认为应该如何做。


@Ross Patterson非常感谢!!这解释了概念,是我疑惑的完美答案。 - Reshma Ratheesh
@Ross Patterson,感谢提及。但我猜测这不是由于时间和空间的亲和性,而是由于相同的JVM执行(因为JVM可以被重新实例化)。 - Gennady Vanin Геннадий Ванин
2
无论来自哪个类或JAR文件,所有在编译时已经池化的字符串都将被类加载器在运行时池化。 - user207421
如果你有两个Java字符串在时间和空间上足够分离(例如,编译成单独的JAR文件),它们将不是同一个对象。正如@EJP所指出的那样,这是错误和完全无意义的(在这种情况下,时间和空间到底意味着什么?)。请修正它。下面Gennady的答案描述了所有正确的规则。 - Oleg
如果这两个字符串不是同一个引用,Map<String, String>是否能正常工作? - user5730329

9
String s1="Java";
String s2="Java";
My question is whether these two references point to the same memory location  

愚蠢的引用Java语言规范§3.10.5

字符串字面量是对String类实例的引用(§4.3.1, §4.3.3)。

此外,字符串字面量总是引用同一个String类实例。这是因为字符串字面量 - 或者更一般地说,常量表达式的值为字符串(§15.28) - 被“内部化”以共享唯一实例,使用String.intern方法。

请阅读代码示例的注释:

此示例说明了六个要点:

  • 在同一包(§7)中的同一类(§8)中的文字字符串表示对同一个String对象(§4.3.1)的引用。

  • 在同一包中的不同类中的文字字符串表示对同一个String对象的引用。

  • 在不同包中的不同类中的文字字符串也表示对同一个String对象的引用。

  • 由常量表达式(§15.28)计算的字符串在编译时计算,然后被视为字面值。

  • 在运行时通过连接计算的字符串是新创建的,因此是不同的。

  • 显式地将计算字符串放入池中的结果是与具有相同内容的任何现有文字字符串相同的字符串。


7

当编译器优化您的字符串字面值时,它会看到s1和s2具有相同的值,因此您只需要一个字符串对象。这是安全的,因为Java中的字符串是不可变的。

String s1="Java";
String s2="Java";
System.out.println(s1== s2);

这个例子的结果是true,因为s1s2指向同一个对象。

字符串池是一种机制,所有已定义的字符串都存储在某个“池”中,在创建新的字符串对象之前,编译器会检查是否已经定义了这样的字符串。


1
错误。您可以使用StringBuilder创建String实例,如果您创建的字符串已经在其他地方作为字面量使用,则它不会是相同的字符串,也不会在相同的内存位置上。这就是为什么所有的String比较都必须使用.equals进行。String是一个引用类型。 - TheBlastOne
@TheBlastOne 你可以手动将字符串进行内部化:String#intern() - maba
1
@AchintyaJha 是的,“在Java中,字符串是不可变的,所以它是安全的。”虽然字符串在Java中是不可变的,但这并不意味着在所有情况下使用“==”比较它们是安全的。 - TheBlastOne
1
@TheBlastOne 这个问题和答案都是关于字符串字面量的。而你的评论则不是。 - user207421
@EJP,你的评论也不是 :D 如果你愿意的话,我可以删除这些评论。 - TheBlastOne
显示剩余3条评论

3

示例。

第一个示例

String s1 = "FirstString";
String s2 = "FirstString";

 if(s1 == s2) {
   //This condition matched true because java don't make separate object for these two string. Both strings point to same reference.
 }

第二个例子
String s1= "FirstString";
String s2 = new String("FirstString");

if(s1.equals(s2)) {
  //This condition true because same content.
}

if(s1 == s2) {
  //This condition will be false because in this java allocate separate reference for both of them
}

结论:Java 如何检查字符串是否存在。如果我们使用 new 创建了第二个字符串对象并且内容不同,则会创建一个新的对象并分配不同的引用,如果我们没有使用 new 创建对象并且内容相同,则会将相同的引用赋值给原先包含的第一个字符串。


2
你的结论是不正确的。如果你使用 new,无论内容如何,你总会得到一个新的字符串对象。 - user207421

0

当你拥有

String str1 = new String("BlaBla");  //In the heap!
String str2 = new String("BlaBla");  //In the heap!

如果你通过new操作符(和构造函数)显式地创建了一个String对象,那么你将会有每个对象指向一个不同的存储位置。

但是如果你有:

String str1 = "BlaBla";        
String str2 = "BlaBla";

那么你就有了隐式构造。如果两个字符串字面值具有相同的值,则它们共享相同的存储空间,这是因为Java保留了相同字符串的存储!(具有相同值的字符串)


0
String s1="Java";
String s2="Java"; 

它们指向同一内存位置吗?
最初我说“不是”,但在上面的情况中,请参见下面提到的StringPool答案,实际上是的。
“当我们创建相同的字符串(没有new关键字)时,内容只会被存储一次,并且所有具有相同内容的String对象只是引用同一位置。”
……在问题{{link1:“Java Strings and StringPool”}}中看到详细的答案。
“s1和s2的哈希码相同。但哈希码是否直接依赖于对象的内存位置?”
不,哈希码取决于String的内容。

你的“不”似乎与Java语言规范§3.10.5相矛盾:“字符串字面量是对String类(§4.3.1,§4.3.3)实例的引用。此外,字符串文字始终引用String类的同一实例。这是因为字符串字面量 - 或更一般地说,是常量表达式(§15.28)的值的字符串 - 被内部化以共享唯一实例,使用String.intern方法。”请检查那里的示例。 - Gennady Vanin Геннадий Ванин
我同意,我对“内存位置”有所误解。关于StringPool的链接答案对我来说是新闻! - Vorsprung
是的,s1和s2引用变量都指向在字符串池中放置的同一个字符串对象。 - AmitG
@AmitG 我同意,答案已修改。 - Vorsprung

0
补充其他人的观点: new关键字总是强制创建一个新对象。 如果你像下面这样声明:
String s1 = "some";
String s2 = "some";

然后使用字符串池机制,引用s1和s2将指向具有值“some”的相同字符串对象。


0
String s1="Java";
String s2="Java";

两者指向同一对象。欲了解更多详细信息请点击此处


即使它们在多个(并行、同时)不同的程序、进程中,也可以这样做吗? - Gennady Vanin Геннадий Ванин
是的。如果它们在多个不同的程序中(并行、同时),则进程将为相同的JVM共享字符串,而不是为不同的JVM。 - AmitG

-2

是的,Andrew Hare在这个链接https://dev59.com/ZXE95IYBdhLWcg3wCJbk#2486195上回答了。

基本上,字符串池允许运行时通过在池中保留不可变字符串来节省内存,以便应用程序的各个区域可以重用常见字符串的实例,而不是创建多个实例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接