关于三者的区别以及产生的原因
首先介绍下String为Immutable类,任意字符串的拼接都会产生新的String对象,所以在进行很多字符串拼接的场景下会产生很多无用对象影响应用程序的性能,因而派生出了StringBuilder以及StringBuffer类型。
其实就是为了解决字符串拼接时产生过多的无用对象,无论是StringBuilder或StringBuffer进行append()方法拼接时都会返回当前的this对象,而他们两者的区别就在于StringBuffer是线程安全的而StringBuilder是
非线程安全的,所以如果存在大量字符串拼接的场景我们使用StringBuilder即可,还有需要注意的一点是javac编译器会优化我们的源代码,对于我们手动拼接的字符串代码,会给我们优化成StringBuilder类型进行
append操作(JDK1.6及以后版本,1.5默认使用的StringBuffer拼接),所以一些少量的操作也没有必要死记硬背的按照规则使用StringBuilder对象进行操作。
扩展知识点
仅以JDK1.8及以上版本作为讲解,低版本不做解释!
String类型JDK9之前都是使用char[]数组保存value值,9及以后改成byte数组和一个编码的coder组成,这样做的原因其实就是为了节省内存,char占两个字节。在编程语言中,不影响性能的情况下,能更少的使用
内存就是一种优化的方式。
当我们显示的声明String s = "abc" 时,线程栈上的引用指向的是堆内存常量池中的引用,此时堆中是没有字符串常量的。这里有个例子就是
String str1 = "abc";
String str2 = "de";
String str3 = str1 + str2;
System.out.println( str3 == str1 + str2);
这里打印的结果是false,这里需要使用Javap反编译工具的字节码来解释:
str3 = str1 + str2的时候使用了StringBuilder对象,所以比较的两个不同的StringBuilder对象,虽然值时相同的,但是堆内存的两个对象不同,所以返回的时false。这里需要在理解一个东西就是String a = new String("abc");
这个操作其实是堆内存中的字符串对象的值指向了字符串中常量池中的字面量“abc”的值,所以当两边使用intern方法时,他们就是true了。即str3.intern() == ( str1 + str2 ).intern();因为Intern方法返回的是字符串常量池中的引用
这里还有个JVM的知识的注意点:
文中提到的字符串常量池、文件字节码中所说的常量池、以及运行时每个类的常量池区别,字符串常量池在JDK1.7以前是存在permgen永久代中的,1.7将字符串常量池挪到了heap中,1.8直接删除了permGen,方法区的具体实现是metaspace和Heap。这里注意一个概念,JVM规范中提到是类元信息等是以及静态常量等储存在方法区中,这里的方法区是概念性的,但是永久代以及元空间的说法是具体的垃圾收集器实现方法区的内存区域.
所以在1.7以前的Jdk,如果引用中存在大量的字符串常量,由于没有在永久代进行垃圾回收,是有可能在永久代发生oom的,但是以后jdk的版本中,因为是在堆内存中,所以基本上不是因为字符串常量池对象过多导致OOM。我们同时可以在JVM上设置参数来观察当前JVM实例中的字符串信息,由于底层是由C++的StringTable结构体实现的,所以当我们设置JVM参数-XX:+PrintStringTableStatistics时,可以看到:
输出StringTableStatistics信息。默认桶的个数是65536,存储的字符串对象个数为2928,串池中字符串常量个数为也是2928,总的占用空间约为0.77M。上面代码只是输出i,但串池中常量个数为2928,是因为类名、方法名等字面量的常量也存在常量池中的。
另外还有一个关于Intern的知识点需要了解一下,在JDK7及以上版本:
String str1 = new StringBuilder("ja").append("va").toString(); String str2 = new StringBuilder("计算机").append("测试").toString(); System.out.println(str1 == str1.intern()); System.out.println(str2 == str2.intern());
这个结果是false和true。首先我们要了解一个概念就是字符串常量池,当我们使用intern方法时或者直接声明String xx = "aaa"时,JVM首先会在字符串常量池中寻找是否存在aaa的常量,如果存在的话将该常量池的引用返回
注意常量池中存放的是堆上的引用。所以这就解释了为什么第二个为true了,而第一个为false。是因为JVM加载时别的地方已经存在了java的字符串,所以此时常量池存的堆中的另一个引用地址,而str1是当前字符串在堆中的引用,所以两者的引用不同