1.String、StringBuilder以及StringBuffer区别
(1).String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的
例子:
public static void main(String[] args) {
String a = "123";
System.out.println("String:"+a.hashCode());
a += "456";
System.out.println("String:"+a.hashCode());
}
如图可看到同样一个对象,String类型重新赋值过后的hashcode不一样,证明他们不是同一个对象。java中对String类型操作是一个不断创建新对象并回收旧对象的过程,所以这个过程很慢,所以设计到字符串拼接,更改的操作都是使用StringBuilder或者StringBuffer。
(2).StringBuilder和StringBuffer的区别:
StringBuilder线程不安全,StringBuffer线程安全
StringBuffer之所以线程安全加了synchronized关键字
所以3者运行速度StringBuilder >StringBuffer >String(因为加了synchronized关键字StringBuffer速度会稍慢一些
额外补充:
public static void main(String[] args) {
String str1 = "hello world";
String str2 = "hello world";
String str3 = "hello world";
String str4 = new String("hello world");
String str5 = new String("hello world");
String str6 = new String("hello world");
System.out.println(str1==str4);
System.out.println(str1==str2);
System.out.println(str4==str5);
}
第一个false的原因str1一个是字符串常量,它处于字符串常量池,在讲之前先了解下字符串常量池在哪里。
首先了解一下
堆
存储的是对象,每个对象都包含一个与之对应的class
JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定
栈
每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象)
每个栈中的数据(原始类型和对象引用)都是私有的
方法区
静态区,跟堆一样,被所有的线程共享
方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量
字符串常量池则存在于方法区
这张图可以很好的理解
所以str1它是整个程序中永远唯一的元素和str4 是两个不同的对象
第二个为true的原因str1和str2不是通过new出来的对象所以它存在与常量池中,常量池中如果是相同的字符串,他都会返回同一个对象。
第三个为false的原因str4和str5都是new出来的对象存在于堆中,堆中一个new代表一个唯一对象,所以他两是不同的对象
面试题:
String str4 = new String(“abc”) 创建多少个对象?
1.在常量池中查找是否有“abc”对象
(1).有则返回对应的引用实例
(2).没有则创建对应的实例对象
2.在堆中 new 一个 String(“abc”) 对象
3.将对象地址赋值给str4,创建一个引用
所以,常量池中没有“abc”字面量则创建两个对象,否则创建一个对象,以及创建一个引用
面试题:
String a = "hello2";
String b = "hello" + 2;
System.out.println((a == b));
输出结果为:true。原因很简单,“hello”+2在编译期间就已经被优化成"hello2",因此在运行期间,变量a和变量b指向的是同一个对象。
面试题:
String a = "hello2";
String b = "hello";
String c = b + 2;
System.out.println((a == c));
输出结果为:false。由于有符号引用的存在,所以 String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象。
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替 比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
面试题:
String a = "hello2";
final String b = "hello";
String c = b + 2;
System.out.println((a == c));
输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = “hello” + 2;
面试题:
public class Main {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String c = b + 2;
System.out.println((a == c));
}
public static String getHello() {
return "hello";
}
}
输出结果为false。这里面虽然将b用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此a和c指向的不是同一个对象。
借鉴博客:https://www.cnblogs.com/dolphin0520/p/3778589.html