先来看看下面关于String
的真实面试题,看看你废不废?
①
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str1 == str2); // true or false?
System.out.println(str1.equals(str2)); // true or false?
System.out.println(str1 == str3); // true or false?
System.out.println(str1.equals(str3)); // true or false?
System.out.println(str3 == str4); // true or false?
如果要正确回答上面几个面试题,不深入理解String
类型的内存原理是不行的,本篇的唯一目的就是言简意赅、深入浅出的梳理String
类型的底层原理。
一,字符串存储的内存原理
1,字面量声明字符串对象
String name = "Java";
如上,以字面量声明的字符串String
对象存储在堆内存中,而其地址则保存在字符串常量池中(从JDK 7开始,字符串常量池被移到了堆中)。
结合上图,对于代码String name = "Java";
,创建的字符串对象的内存分布如下:
- ①
String
是引用类型,所以变量name
存储的是字符串对象的地址值0xefd
,指向常量池某一条记录,如箭头① - ②常量池是HashTable,存储的是字符串对象的地址,如箭头②
- ③String内部使用一个名为
value
的byte
数组存储字符串的,显然变量value
是引用类型,所以其值也是地址,指向一个byte
数组对象,如上图箭头③
那么问题来了,两个字面量相等的变量的内存分布是怎样的呢?
String name = "Java";
String name2 = "Java";
当创建一个String时,如果该字符串已经存在于常量池中,则直接引用该字符串;否则,会在常量池中创建一个新的字符串实例,并返回对该实例的引用。
所以,如上图所示,因为这两个变量都是以字面量声明的,且字符串值都是“Java”
,所以变量name
和name2
会指向同一个地址。
2,构造函数声明字符串对象
String name = new String("Java");
如上,当以构造函数声明字符串对象时,与字面量最大的不同是,创建的字符串对象不会保存在字符串常量池中,字符串String
对象存储在堆内存中,其内存分布如下图所示:
类似的问题,创建两个值一样的对象,其内存是怎么分布的呢?
String name = new String("Java");
String name2 = new String("Java");
对于上面代码,如下图所示,会创建两个完全不同的字符串对象。
从上面可以看出,用字面量声明对象,字符串相同时,可以复用字符串对象,有更高的内存利用率。所以,最佳实践是使用字面量声明字符串对象。
二,面试题分析
对于文章开头的面试题:
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str1 == str2); // true or false?
System.out.println(str1.equals(str2)); // true or false?
System.out.println(str1 == str3); // true or false?
System.out.println(str1.equals(str3)); // true or false?
System.out.println(str3 == str4); // true or false?
①
对象str1和str2是通过字面量创建的,这两个变量在字符串常量池的作用下,指向同一个字符串对象,所以:
System.out.println(str1 == str2);
System.out.println(str1.equals(str2));
结果都是true
:
这里需要知道运算符 ==
和 函数equals
的区别:
- 运算符
==
比较的是两个对象在内存中地址,即栈中变量存储的值 - 函数
equals
会把内存中的字符串值取出来比较是否相同,如上例,比较的是字符串"Java"和"Java"是否相同
②
变量str3
是通过构造函数创建的,所以不会通过常量池复用已经创建的对象,所以两个变量指向不同地址的对象,用运算符==
比较的结果是false
;但两个对象存储的字符串内容是相同的,用equals
比较的结果是true
:
System.out.println(str1 == str3);
System.out.println(str1.equals(str3));
③
System.out.println(str3 == str4);
System.out.println(str3.equals(str4));
因为对象str3和str4都是通过构造函数创建的,根据前面的分析,会创建两个不同的对象,所以,用运算符==
比较的结果是false
;但两个对象存储的字符串内容是相同的,用equals
比较的结果是true
: