我们得知道为何会出现String,StringBuilder,StringBuffer
(1)String 字符串常量 (每次都会创建对象)
(2)StringBuilder 字符串变量(非线程安全)
(3)StringBuffer 字符串变量(线程安全)
从上面可以看到String 因为每次创建的时候都会创建对象,太消耗内存了,所以就有了StringBuilder,但是StringDuilder是线程不安全的,所以就有了StringBuffer。
基本来说都是在性能上都是 StringBuilder > StringBuffer > String 的了
一、String
要知道,字符串就是一个字符数组,也相当于一个常量,因为一旦赋值就不改变,只能让变量指向另一个地址值。
1.String的内存机制
通过构造器new出来的字符串对象每一次new都会申请一个内存空间,虽然内容一样,但是地址值是不同的;第一次出现的字符串,会在栈内存和堆的字符串常量池中存处,下次再new一个相同内容的字符串时,愧就只会再栈内存中创建对象而已。
直接赋值得到的字符串,只要字符序列相同(顺序,大小写等),无论在代码里面出现几次,JVM都只会创建一个String对象,且是在堆的字符串常量池中创建维护
2.String对象的创建方式
首先,我们得知道String是一个引用类型的数据.那String对象就是new出来的?new出来的存在堆内存中吗?
是不是会有这么一个疑问,其实并不是这样的。由于,开发者经常用这个String类,所以java对String有单独的处理,比如重写了其的toString()方法,让其可以i直接打印数值,重写了equals()和hashCode()方法,让方法.equals()是比较数值,而不是地址值。
那常用的创建String对象的方法有两种:
第一种:构造方法
String a = new String(“aaaa”);
相当于创建了两个对象,一个对象放在栈内存中,一个对象放在堆的字符串常量池中。
所以,这就是1-2个对象,假如堆的字符串常量池中这里面有这个对象的话,就不会在创建。
第二种:直接赋值
String b = “bbb”;
只创建了一个对象而已,放字符串常量池中
所以,这就是0-1个对象,同上
举例:
String name1 = “test”;
String name2 = “test”;
name1 == name2 这是对的,因为name1只在字符串常量池中创建对象,name2直接使用这个对象。
String name3 = “test11”;
String name4 = new String(“test11”);
name3 == name4 这是错的,因为name4是有存在栈的对象
3.拓展:
拓展一:
字符串如果是变量相加,那是先开空间,在拼接;
字符串如果是常量相加(直接赋值),是先相加,然后再堆的字符串常量池中找,没找到再在栈中创建对象
这儿就是变量和常量的区别的,可以看我以后开辟的文章。
例子:
String s1 = “hello”;
String s2 = “world”;
String s3 = “helloworld”;
s3 == s1 + s2 false s1+s2先开辟了空间
s3.equals(s1+s2) true 比较的是内容
s3 == “hello” + “world” true 字符串常量池里面有
s3.equals( “hello” + “world”) true 比较内容
拓展二:
== 对于基本类型是比较值,对于引用类型比较的是地址值是否相等;
equals对于引用类型比较的是地址值,但是如果重写了Object的equals和hashcode方法那就是比较值,如String类型
二、StringBuilder
1.概念
StringBuilder类称为字符串构造者类。
是一个可变的字符串类(与String 的区别),说明还是同一个对象,但是里面的内容发生了改变,就相当于一个容器。 ----- 所以StringBuilder多用于字符串的拼接,从而不会产生多余的对象。
2.创建方式
只有一种创建方式:new
StringBuilder sb = new StringBuilder(“sbtest”);
3.内存位置
只会创建一个内存,存处在堆的的字符串常量池中。
4.底层原理
底层会创建一个可自动扩容的初始长度为16的数组(扩容后就是新的数组了)
也重写了toString()方法,可以直接输出
但是没有重写equals()方法,所以这个方法还是比较地址值
5.常用方法
①.StringBuilder append(任意类型); 字符串的拼接,支持链式
这个方法时直接在堆的字符串常量池中修改内容,不会创建新的对象,且以前对象的地址值不会改变。
----总结就是以前的对象不变,但是内容改变了
②.StringBuilder reverse(); 反序; 对象不变,只是内容变了
③.String toString(); 把StringBuilder转换成String类型,且创建了String对象
例子1:
StringBuilder sb1 = new StringBuilder(“sbtest”);
StringBuilder sb2 = new StringBuilder(“sbtest”);
sb1 == sb2吗?false new出来的就是不同的对象
例子2:
StringBuilder sb1 = new StringBuilder(“sbtest”);
sb1.append(“change”);
StringBuilder sb2 = new StringBuilder(“sbtest”);
sb1 == sb2吗?false new出来的就是不同的对象
问题:StringBuilder重写了toString()方法,为何还要转换成String呢?
因为StringBuilder没有重写equals()方法,所以这个方法还是比较地址值;所以要想比较内容就得先转成String才行再用equals()
三.StringBuffer
既然StringBuilder类对象可变,那么当其声明成全局变量,必然会带来线程安全问题(一个类是否线程安全取决于类的全局变量状态是否可以改变,能改变则说明该类线程不安全,否则线程安全。来自《Java并发编程的艺术》)。
为了解决StringBuilder类线程不安全的问题,StringBuffer类就出来了。除了这一点外,StringBuffer类与StringBuilder类完全一样。StringBuffer类线程安全的实现方式是用synchronized关键字修饰方法,即同步方法的方式。