String
String的创建机制
因为String在Java中使用过于频繁,Java为了避免在系统中产生大量的String对象,引入了字符串常量池的概念。
其运行机制是:
- 创建一个字符串时,首先检查池中是否有值相同的字符串对象(equals决定),如果有则不需要创建而是直接从常量池中找到的该字符串对象的引用;
- 如果没有则新建一个字符串对象,返回该对象引用,并且将新创建的字符串对象放入池中 但是通过new方法创建的String对象是不检查字符串池的,而是直接在堆区或栈区创建一个新的对象,也不会把对象放入池中。
举例:1
2
3
4
5
6
7
8
9
//通过直接量赋值方式,放入字符串常量池
String str1 = "123";
//通过new方式赋值方式,不放入字符串常量池
String str2 = new String(“123”);
//此时的str1 != str2
String str3 = "1" + "2" + "3";
//str3 == str1 是成立的
上述代码中在编译期的时候,str3即被编译成”123”字符串,而此时常量池中已经存在该字符串,所以str3与str1是相等的
String的特性
不可变
String对象一旦生成,则不能再对它的值进行改变,这里的不可变指这个字符串对象无法改变,而我们平时定义的字符串变量虽然可以改变,但是实质上它是改变了这个变量的引用,相当于将这个变量指向了另外一个字符串对象,而一开始的字符串对象还是没有变的。
不可变的主要作用在于当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待的时间,从而大幅度提高系统性能。
不可变模式是一个可以提高多线程程序的性能,降低多线程程序复杂度的设计模式。
针对常量池的优化
当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。1
2
3String str1 = "123";
String str2 = "123";
//此时的str1 == str2是成立的
为什么不可变
我们看String的源码可以发现String底层是采用字符数组(char[]
)来存储字符串值,该数组的定义如下1
private final char value[];
这个数组定义为private final
,在java中数组也是对象,所以当String对象一旦初始化完成,其内部变量value
的引用就无法改变,顶多只能改变数组中元素的值,但是在看遍String的所有方法后,发现String中根本没有一个方法可以改变value
这个char数组里面的元素,所以在String初始化完成后即不可变。1
2String a = "abcde";
a = a.subString(1);//这时a="bcde"
虽然我们在编码过程中经常会调用String的toLowerCase
,substring
等方法,如上面的例子中虽然a最终被改变成了"bcde"
,但是实际上这是生成的一个新字符串对象,只是将变量a的引用指向了这个新对象,而没有改变原有字符串对象"abcde"
的值。
StringBuffer & StringBuilder
StringBuffer
和StringBuilder
都实现了AbstractStringBuilder
抽象类,拥有几乎一致对外提供的调用接口;
其底层在内存中的存储方式与String相同,都是采用char数组存储数据,只是这个char数组没被final修饰,因此这个char数组的引用可以改变且该数组中的元素也可以改变,所以StringBuffer/StringBuilder
对象的值是可以改变的。
而StringBuffer/StringBuilder
在改变char数组过程中是在该对象自身内部进行的,所以对象本身的引用还是同一个。因此定义一个StringBuffer/StringBuilder
变量,修改其值之后,其引用还是同一个,不会改变。
两者对象在构造过程中,首先按照默认大小申请一个字符数组,由于会不断加入新数据,当超过默认大小后,会创建一个更大的数组,并将原先的数组内容复制过来,再丢弃旧的数组。因此,对于较大对象的扩容会涉及大量的内存复制操作,如果能够预先评估大小,可提升性能。
唯一需要注意的是:StringBuffer是线程安全的,但是StringBuilder是线程不安全的。可参看Java标准类库的源代码,StringBuffer类中方法定义前面都会有synchronize关键字。为此,StringBuffer的性能要远低于StringBuilder。
应用场景
- 在字符串内容不经常发生变化的业务场景优先使用String类。例如:常量声明、少量的字符串拼接操作等。如果有大量的字符串内容拼接,避免使用String与String之间的“+”操作,因为这样会产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。
- 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用StringBuffer,例如XML解析、HTTP参数解析与封装。
- 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用StringBuilder,例如SQL语句拼装、JSON封装等。
原创文章,转载请出处注明。
下面是我的个人公众号,欢迎关注交流