程序往往在其代码中包含大量字符串文字。在 Java 中,为了提高效率,这些常量被收集在称为字符串表的东西中。例如,如果您"Name: "
在十个不同的地方使用字符串,JVM(通常)只有一个该字符串的实例,并且在所有十个使用它的地方,引用都指向那个实例。这样可以节省内存。
这种优化是可能的,因为String
它是不可变的。如果可以更改字符串,则将其更改一个位置将意味着它在其他九个位置也将更改。这就是为什么任何更改字符串的操作都会返回一个新实例的原因。这就是为什么如果你这样做:
String s = "boink";
s.toUpperCase();
System.out.println(s);
它打印boink
,不是BOINK
。
现在还有一个棘手的问题:多个实例java.lang.String
可能指向char[]
它们的字符数据的相同底层,换句话说,它们可能是相同的不同视图,char[]
只使用数组的一个切片。再次,优化效率。该substring()
方法是发生这种情况的一种情况。
s1 = "Fred47";
//String s1: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=0, length=6
// ^........................^
s2 = s1.substring(2, 5);
//String s2: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=2, length=3
// ^.........^
// the two strings are sharing the same char[]!
在您的 SCJP 问题中,所有这些都归结为:
- 该字符串
"Fred"
取自字符串表。
- 该字符串
"47"
取自字符串表。
- 该字符串
"Fred47"
是在方法调用期间创建的。//1
- 该字符串
"ed4"
是在方法调用期间创建的,与//2共享相同的后备数组"Fred47"
- 该字符串
"ED4"
是在方法调用期间创建的。//3
s.toString()
不会创建一个新的,它只是返回this
。
所有这一切的一个有趣的边缘案例:考虑如果你有一个非常长的字符串会发生什么,例如,从互联网上获取的网页,假设它的长度char[]
是 2 兆字节。如果你接受这个,你会得到一个看起来只有四个字符长substring(0, 4)
的新字符串,但它仍然共享这 2 兆字节的支持数据。这在现实世界中并不常见,但它可能会浪费大量内存!在您遇到此问题的(极少数)情况下,您可以使用新的小型支持数组创建一个字符串。new String(hugeString.substring(0, 4))
最后,可以在运行时通过调用intern()
它来强制将一个字符串放入字符串表中。在这种情况下的基本规则:不要这样做。扩展规则:除非您使用内存分析器确定它是有用的优化,否则不要这样做。