方案往往含有大量的字符串文字在他们的代码。在Java中,为了提高效率,这些常量被收集在一个称为字符串表的东西中。例如,如果在十个不同的地方使用字符串"Name: "
,则JVM(通常)只有该String的一个实例,并且在使用该字符串的所有十个位置中,引用都指向该实例。这节省了内存。
这种优化是可能的,因为String
是不可变的。如果可以更改字符串,将其更改为一个地方意味着它会改变其他九个字符串。这就是为什么任何改变String的操作都会返回一个新的实例。这就是为什么如果你这样做:
String s = "boink";
s.toUpperCase();
System.out.println(s);
它打印boink
,不BOINK
。
现在有一个更靠谱一点:在相同的基础char[]
他们的字符数据java.lang.String
可能点的多个实例,换句话说,也可以是不同的同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"
在方法调用期间创建,共享相同的背衬阵列"Fred47"
// 2
- 方法调用过程中创建
"ED4"
的字符串。 // 3
s.toString()
不会创建一个新的,它只会返回this
。
之一的这一切有趣的边缘情况:考虑,如果你有一个很长的字符串会发生什么,例如,从网上取一个网页,让我们假设char[]
的长度为2兆字节。如果你拿这substring(0, 4)
,你会得到一个新的字符串,看起来像它只有四个字符长,但它仍然共享这两兆字节的支持数据。这在现实世界中并不常见,但它可能会造成巨大的内存浪费!在(罕见)情况下,如果遇到此问题,可以使用new String(hugeString.substring(0, 4))
创建一个带有新的小型后备阵列的字符串。
最后,可以在运行时通过调用intern()
来强制字符串进入字符串表。这种情况下的基本规则:不要这样做。扩展规则:除非您使用内存分析器来确定它是一种有用的优化,否则不要这样做。
我想象中的编译器将内联前两个语句和删除最后一个(冗余)方法调用。这给你留下了“Fred47”,“ed4”,“ED4”。 –
@Jared's'不是*编译时常量表达式*,因此不会发生。 (编译代码并使用'javap -c',甚至'strings'。) –
可能重复的[Java - 多少个字符串对象?](http://stackoverflow.com/questions/17898236/java-how-many -string-objects) –