String 对象内存分配(常量池和堆)
- 内存分配策略
- 创建对象的方式
- 代码栗子
- 字符串常量重载 "+"
- 字符串引用重载 "+"
- 代码栗子
内存分配策略
String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中
最近学习看到这么个说法,有点懵,于是面向浏览器编程梳理了一波。
创建对象的方式
String s =""
与 String s = new String("")
两种方式都可以创建字符串对象,它们有什么不同吗?
-
String s = "abc"
方式创建的对象,存储在字符串常量池中,在创建字符串对象之前,会先在常量池中检查是否存在abc
对象。如果存在,则直接返回常量池中abc
对象的引用,不存在则创建该对象,并将该对象的引用返回给对象s
。 -
String s = new String("abc")
这种方式,实际上abc
本身就是字符串常量池中的一个对象,在运行new String()
时,把字符串常量池中的字符串abc
复制到堆中,因此该方式不仅会在常量池中,还会在堆中创建abc
字符串对象。 最后把java
堆中对象的引用返回给s
。
代码栗子
可通过这个栗子验证一下上述方式
@Test
public void test(){
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2); // true
System.out.println(s3 == s4); // false
System.out.println(s1 == s3); // false
}
字符串常量重载 “+”
"a" + "bc"
是两个字符串常量的拼接,当一个字符串由多个字符串常量连接而成时,那么它也是字符串常量。字符串常量的 “+” 号连接,在程序编译期,java 虚拟机就将常量字符串的 “+” 连接优化为连接后的值。
最终只会在字符串常量池中创建一个 abc
对象,并没有创建临时字符串对象 a
和 bc
,减轻了垃圾收集器的压力。
字符串引用重载 “+”
java 虚拟机对于有字符串引用存在的字符串连接,即 s2 + "bc"
在被编译器执行的时候,会自动引入 StringBuilder
对象,调用其 append()
方法,最终调用 toString()
方法返回其在 堆中对象
(思考:为什么是堆中呢(下面会讲))的引用。
代码栗子
@Test
public void test() {
String s1 = "abc";
String s2 = "a";
System.out.println(s1 == ("a" + "bc")); // true
System.out.println(s1 == (s2 + "bc")); // false
}
反编译后的部分字节码:
因此 s2 + "bc"
就等同于下面过程。
s2 + "bc" = new StringBuilder().append(s2).append("bc").toString();
为什么说 s2 + "bc"
返回的是堆中对象的引用,而不是字符串常量池呢?
我们到 StringBuilder
源码中查看一下 toString()。
/**
* StringBuilder 类的 toSTring() 方法
*/
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
上面讲了 new String("")
这种方式创建的字符串对象被放到了堆内存中,这里的 toString 其实是 copy s2 + "bc"
到一个堆中对象。