因为String在Java中使用过于频繁,Java为了避免在系统中产生大量的String对象,引入了字符串常量池的概念。
其运行机制是:
//通过直接量赋值方式,放入字符串常量池 String str1 = "123"; //通过new方式赋值方式,不放入字符串常量池 String str2 = new String(“123”); //此时的str1 != str2 String str3 = "1" + "2" + "3"; //str3 == str1 是成立的
上述代码中在编译期的时候,str3即被编译成”123”字符串,而此时常量池中已经存在该字符串,所以str3与str1是相等的
String对象一旦生成,则不能再对它的值进行改变,这里的不可变指这个字符串对象无法改变,而我们平时定义的字符串变量虽然可以改变,但是实质上它是改变了这个变量的引用,相当于将这个变量指向了另外一个字符串对象,而一开始的字符串对象还是没有变的。
不可变的主要作用在于当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待的时间,从而大幅度提高系统性能。
不可变模式是一个可以提高多线程程序的性能,降低多线程程序复杂度的设计模式。
当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。
String str1 = "123"; String str2 = "123"; //此时的str1 == str2是成立的
我们看String的源码可以发现String底层是采用字符数组( char[]
)来存储字符串值,该数组的定义如下
private final char value[];
这个数组定义为 private final
,在java中数组也是对象,所以当String对象一旦初始化完成,其内部变量 value
的引用就无法改变,顶多只能改变数组中元素的值,但是在看遍String的所有方法后,发现String中根本没有一个方法可以改变 value
这个char数组里面的元素,所以在String初始化完成后即不可变。
String a = "abcde"; a = a.subString(1);//这时a="bcde"
虽然我们在编码过程中经常会调用String的 toLowerCase
, substring
等方法,如上面的例子中虽然a最终被改变成了 "bcde"
,但是实际上这是生成的一个新字符串对象,只是将变量a的引用指向了这个新对象,而没有改变原有字符串对象 "abcde"
的值。
StringBuffer
和 StringBuilder
都实现了 AbstractStringBuilder
抽象类,拥有几乎一致对外提供的调用接口;
其底层在内存中的存储方式与String相同,都是采用char数组存储数据,只是这个char数组没被final修饰,因此这个char数组的引用可以改变且该数组中的元素也可以改变,所以 StringBuffer/StringBuilder
对象的值是可以改变的。
而 StringBuffer/StringBuilder
在改变char数组过程中是在该对象自身内部进行的,所以对象本身的引用还是同一个。因此定义一个 StringBuffer/StringBuilder
变量,修改其值之后,其引用还是同一个,不会改变。
两者对象在构造过程中,首先按照默认大小申请一个字符数组,由于会不断加入新数据,当超过默认大小后,会创建一个更大的数组,并将原先的数组内容复制过来,再丢弃旧的数组。因此,对于较大对象的扩容会涉及大量的内存复制操作,如果能够预先评估大小,可提升性能。
唯一需要注意的是:StringBuffer是线程安全的,但是StringBuilder是线程不安全的。可参看Java标准类库的源代码,StringBuffer类中方法定义前面都会有synchronize关键字。为此,StringBuffer的性能要远低于StringBuilder。
原创文章,严禁随意转载。欢迎大家添加个人微信讨论交流,添加时请备注:博客。