Q1:
你知道String底层是怎么编译的吗?
你知道String s1="abc" 与 String s2 = new String("abc") 的区别吗?
1.String 编译底层原理
String a = "a" + "b" +"c";
String b = "abc";
a == b 会是true还是false呢?
下面直接贴代码试试看:
public static void main(String[] args) {
String a = "a" + "b" +"c";
String b = "abc";
System.out.println( a == b);
}
/**
* 结果
true
* */
结果及原理分析
看控制台打印结果:true,说明 a 和 b 引用地址相同。
那么问题来了:为啥引用地址是同一个呢?
在JVM里,考虑到垃圾回收(Garbage Collection)的方便,将 heap 划分为三部分:
young generation (新生代)
tenured generation(old generation)(旧生代)
permanent generation( permgen )(永久代)
而这里要说的就是永久代。
在通过 a = "abc" 这样的方式去创建字符串变量的时候,
在编译期间的时候 jvm会做一次编译优化,把字符串常量放进永久代里面的常量池里面,
而后面一样的字符串常量都会去这个常量池里面查找是否存在这个常量。
如果存在的话就直接引用,
如果不存在则创建一个并且放进这个常量池里面。
那编译期优化是怎么优化的呢?
如 String a = "a" + "b" +"c";
jvm编译的时候会 会直接编译成 String a = "abc";
这个时候会创建一个字符串并且放在常量池里面;
当定义 String b = "abc"时因为在常量池里面已经存在 "abc" 这个常量,
b 会直接引用 这个常量的地址,所以a==b等于 true;
因此得出:两种拼接方式引用同一个地址。
2.String s1="abc" 与 String s2 = new String("abc") 的地址引用相同吗?
(前者下面称呼 s1 对象,后者下面称呼 s2 对象)
也许你没想过这个问题,没关系,下面直接贴代码看看结果:
public static void main(String[] args) {
String s1="abc";
String s2=new String("abc");
System.out.println("是否相等:" + (s1 == s2));
}
/**
* 结果
是否相等:false
* */
结果及原理分析
String str = "abc "的内部工作。Java内部将其分为以下几个步骤:
(1)先定义一个名为str的对String类的对象引用变量:String str;
(2)在栈中查找有没有存放值为 "abc "的地址,
如果没有,则开辟一个存放字面值为 "abc "的地址,接着创建一个新的String类的对象o,
并将o 的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。
如果已经有了值为 "abc "的地址,则查找对象o,并返回o的地址。
(3)将str指向对象o的地址。
注:这一切都发生在栈中。
而new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。
因此得出:s1 对象 和 s2 对象的引用地址不同
3.比较 s1 对象 与 s2 对象 的执行效率
前面探讨了他们的引用地址不同,那么各自执行效率是否有差别?
同样,贴代码看看:
/**
* 本次测试时间单位采用了 纳秒
*/
public static void main(String[] args) {
long start = System.nanoTime();
for(int i = 0 ; i < 100000 ; i ++){
String s1 = "abc";
}
long end = System.nanoTime();
System.out.println("直接赋值方式耗时:" + (end - start) + " nano ");
start = System.nanoTime();
for(int i = 0 ; i < 100000 ; i ++){
String s1 = new String("abc");
}
end = System.nanoTime();
System.out.println("实例化方式耗时:" + (end - start) + " nano ");
}
/**
* 结果
s1 对象方式耗时: 866486 nano
s2 对象方式耗时:5415777 nano
* */
结果及原理分析
从控制台看,s1 对象方式执行更快。
那么问题来了:为啥s1更快呢?这里发生了什么?
使用String str = "abc ";的方式,可以在一定程度上提高程序的运行速度,
因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。
而对于String str = new String( "abc "),则一概在堆中创建新对象,
而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。
这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。
因此得出:s1 对象效率更高。
总结
1、String编译时会先从常量池中找,找到了就直接引用,没找到就创建一个并且放进这个常量池里面。
2、s1 对象 和 s2 对象的引用地址不同。s1 的引用地址是JVM的栈(stack)地址,s2的引用地址是JVM的堆(heap)地址。
3、使用String s1="abc" 方式的运行效率更高于 new String()。(如果常量池已存在"abc",s1对象就不会新增,直接引用地址,而s2对象就一定会新增对象)