1.构造String
一般,我们常用下面的两种方式来构造String。
1.
String str1 = "hello";
String str2 = "hello";
String str1 = new String("hello");
String str2 = new String("hello")
但是,构造String的方法不止这两种,其他方法在这里就不再赘述了。
看起来,这两种方法都构造了两个相同的字符串hello,但是,在底层,这两种方法是不同的。
2.从内存上看两种方法的区别
对于方法1代码,在内存上有:
也就是,通过这种方法,只构造了一个hello,在构造String str2 = “hello”;的前,已经有一个hello在堆上了,这时便不再开辟新的堆空间去构造hello,而是在栈上再创建一个str2引用,指向原有的这个hello。
对于方法2,从内存上看:
通过这样的方法,在堆上保存了两份hello,创建了两个引用str1,str2分别来指向这两个字符串。
3.如何验证2中的说法
我们知道,在Java中,对于String,==是比较两个引用是否指向同一个对象,equals()方法是比较字符串内容是否相同。
那么,对于方法1:
str1 == str2;
结果是True。从上面的内存图中可以知道,str1和str2指向的是同一个对象。
对于方法2:
str1 == str2;
结果是False。因为str1和str2指向的是两个不同的hello。
4.字符串常量池
对于上面的例子,方法1暂且称为直接赋值法。
String str1 = "hello" ;
String str2 = "hello" ;
String str3 = "hello" ;
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // true
对于这个代码,它的内存分布又是怎样的呢?
通过上面的介绍,能知道,此时堆中只有一份hello,栈中有三个引用指向这一个hello。
那么问题来了,为什么没有开辟新的空间呢?
在JVM底层实际上会自动维护一个对象池(字符串常量池)
如果采用了直接赋值法进行String类的对象实例化操作,那么该实例化对象将自动保存到这个对象池之中。
如果下次继续使用直接赋值声明String类对象,此时对象池之中如若有指定内容,将直接进行引用如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。
对于方法2:
如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 “hello” 是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉),也就是标红的那部分是垃圾空间,会被回收掉,显然,这种方法的开销比直接赋值法更大。
我们可以使用手动入池的方法让字符串对象加入到字符串常量池中。
String str1 = new String("hello").intern() ;
String str2 = "hello" ;
System.out.println(str1 == str2);//True
此时,在内存上表现为:
5.字符串不可变
字符串是一种不可变对象. 它的内容不可改变。
先看这样的一段代码:
String str = "hello" ; //1
str = str + " world" ; //2
str += "!!!" ; //3
System.out.println(str);
// 执行结果
hello world!!!
表面上看起来,字符串好像变了,其实并不是。看一下内存图:
这是第一行代码执行后的内存图。
这是第二行代码执行后的内存图。
这是第三行代码执行后的内存图。
+= 之后 str 打印的结果却是变了, 但是不是 String 对象本身发生改变, 而是 str 引用到了其他的对象.
6.StringBuilder和StringBuffer
String类是不可变类,StringBuilder和StringBuffer类是可变类。
StringBuffer和StringBuilder用法基本相同,不同的是,StringBuilder是线程不安全的,StringBuffer是线程安全的。这里以StringBuffer为例。
StringBuffer提供了append(),replaceAll(),replaceFirst(),insert()等方法可以改变字符串的内容。
还与String类不同的是,StringBuffer没有提供equals方法,在比较字符串内容时,需要使用toString()方法转换成String类型,再使用equals()方法。