目录
Ps:按每段后的——可返回目录
String
String
是Java中基础且重要的类,并且也是Immutable类的典型实现,被声明为final class,除了hash这个属性其它属性都声明为final。因为它的不可变性,所以例如拼接字符串时候会产生很多无用的中间对象,如果频繁进行这样的操作会影响性能。
String
本身是不可改变的,它只能赋值一次,每一次内容发生改变,都会生成一个新的对象,然后原有的对象引用新的对象,而每一次生成新对象都会对系统性能产生影响,这会降低.NET编译器的工作效率。
String的内存分配
- String有一个特殊之处:构造String对象时可以使用new构造,也可以使用"***"的方式直接构造。其中两种方法推荐使用第二种。
举例:
1.String a = "hello";
2. String a= new String("hello");
- 解释如下:
- 1:在栈内存中定义了一个a对象引用,指向堆内存的值“hello”内存地址。最终开辟了一个内存空间
- 2:在栈内存重定义了一个a对象引用,先指向堆内存值为“hello”内存地址,然后又指向new之后堆内存为“hello”的地址。最终开辟了两个空间,第一个空间没有对象引用,会被JVM垃圾回收。
理解上面的就不难理解下面这些代码:
public class TestString {
public static void main(String[] args){
String a = "hello";
String b = "hello";
String c = new String("hello");
String d = new String();
d = "hello";
String e = c;
System.out.println("a==b ? " + (a== b));
System.out.println("a==c ? " + (c== b));
System.out.println("a==d ? " + (a== d));
System.out.println("a==e ? " + (a== e));
System.out.println("c==d ? " + (c== d));
System.out.println("c==e ? " + (c== e));
}
}
其中只有a==b==d
、 c=e
。
解释:
1.String每new一次堆内存都不想等(喜新厌旧?),c会指向一个新的内存地址,所以a != c
2.“hello”赋值这种直接赋值方式指向的堆内存空间是一样的。所以a == b
。String在Java中使用了共享设计,在Java形成一个对象池,这个对象池可以保存多个对象,如果新实例化的对象已经在对象池中存在,就不在重复定义,直接从对象池中取出使用。所以对于已存在的内容时,会将对象指向已经实例的空间地址。d,在new分配完新地址之后,发现内容已存在,又会放弃new之后的地址,指向a对应的内存地址,所以a == b== d
。
3.c将自身的内存地址赋给e,e直接指向c的内存地址,所以c == e
。
4.所以在使用String时,建议使用直接赋值方式,减小内存空间,提高性能。
StringBuffer
StringBuffer
就是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类,提供append和add方法,可以将字符串添加到已有序列的末尾或指定位置,它的本质是一个线程安全的可修改的字符序列,把所有修改数据的方法都加上了synchronized。但是保证了线程安全是需要性能代价的。
——
StringBuilder
StringBuilder
是JDK1.5发布的,它和StringBuffer本质上没什么区别,就是去掉了保证线程安全的那部分,减少了开销。
StringBuffer 和 StringBuilder 二者都继承了 AbstractStringBuilder ,底层都是利用可修改的char数组(JDK 9 以后是 byte数组)。
而StringBuilder类则不同,每次操作都是对对象自身进行操作,而不是生成新的对象,其所占空间会随着内容的增加而扩充,这样,在做大量的修改操作时,不会因生成大量匿名对象而影响系统性能。
所以如果我们有大量的字符串拼接,如果能预知大小的话最好在new StringBuffer 或者StringBuilder 的时候设置好capacity,避免多次扩容的开销。扩容要抛弃原有数组,还要进行数组拷贝创建新的数组。
Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池。
创建一个字符串时,首先会检查池中是否有值相同的字符串对象,如果有就直接返回引用,不会创建字符串对象;如果没有则新建字符串对象,返回对象引用,并且将新创建的对象放入池中。但是,通过new方法创建的String对象是不检查字符串常量池的,而是直接在堆中创建新对象,也不会把对象放入池中。上述原则只适用于直接给String对象引用赋值的情况。
String str1 = new String("a");
//不检查字符串常量池的
String str2 = "bb";
//检查字符串常量池的
String还提供了intern()
方法。调用该方法时,如果字符串常量池中包括了一个等于此String对象的字符串(由equals方法确定),则返回池中的字符串的引用。否则,将此String对象添加到池中,并且返回此池中对象的引用。
在JDK6中,不推荐大量使用intern方法,因为这个版本字符串缓存在永久代中,这个空间是有限了,除了FullGC之外不会被清楚,所以大量的缓存在这容易OutOfMemoryError。
之后的版本把字符串放入了堆中,避免了永久代被挤满。
——
总结
-
在字符串不经常发生变化的业务场景优先使用String(代码更清晰简洁)。如常量的声明,少量的字符串操作(拼接,删除等)。
-
在单线程情况下,如有大量的字符串操作情况,应该使用StringBuilder来操作字符串。不能使用String"+"来拼接而是使用,避免产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。如JSON的封装等。
-
在多线程情况下,如有大量的字符串操作情况,应该使用StringBuffer。如HTTP参数解析和封装等。
-
对于简单的字符串连接操作,在性能上stringBuilder并不一定总是优于string。因为stringBuider对象创建代价较大,在字符串目标连接较少的情况下,过度滥用stringBuilder会导致性能的浪费,只有大量的或者无法预知次数的字符串操作,才考虑stringBuilder来实现。事实上,一般连接次数设置100次以内,根本看不出两者的性能差别。
String,StringBuffer,StringBuilder效率与内存占用比较
分别使用String,StringBuffer,StringBuilder进行10000次的字符串拼接操作,计算运行时间以及java程序运行时的内存占用。
public class StringWasteMemoryTest {
public static void main(String[] args) {
long beforeTime = System.currentTimeMillis();
StringTest();
// StringBufferTest();
// StringBuilderTest();
long afterTime = System.currentTimeMillis();
System.out.println("time: " + (afterTime - beforeTime));
long memory = Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory();
System.out.println("memory: " + memory);
}
private static void StringTest() {
String s = "";
for (int i = 0; i < 10000; i++) {
s += "qwertyuiopasdfghjklzxcvbnmqazwsxedcrfvtgbyhnujmiklopplokmnjiuhbvgytfcxdrzsewaq";
}
}
private static void StringBufferTest() {
StringBuffer stringBuffer = new StringBuffer("");
for (int i = 0; i < 10000; i++) {
stringBuffer = stringBuffer
.append("qwertyuiopasdfghjklzxcvbnmqazwsxedcrfvtgbyhnujmiklopplokmnjiuhbvgytfcxdrzsewaq");
}
}
private static void StringBuilderTest() {
StringBuilder stringBuilder = new StringBuilder("");
for (int i = 0; i < 10000; i++) {
stringBuilder = stringBuilder
.append("qwertyuiopasdfghjklzxcvbnmqazwsxedcrfvtgbyhnujmiklopplokmnjiuhbvgytfcxdrzsewaq");
}
}
}
运行结果如下:
String:
time: 1891
memory: 2060918184
StringBuffer:
time: 3
memory: 6291456
StringBuilder:
time: 2
memory: 6291456
StringBuffer和StringBuilder的运行效率和内存占用几乎一致,这是因为二者都是对对象本身进行操作,不会生成新的字符串对象。
在平时编程的过程中,如果字符串需要经常改变,应该尽量避免使用String。
参考文档
yes的练级攻略:面试官:String、StringBuffer、StringBuilder有什么区别?
Andyzty:Java中String、StringBuffer和StringBuilder的区别和堆栈内存分配
Luckie stone:String,StringBuffer,StringBuilder效率与内存占用比较
本文作者: InjoyMario
本文链接: https://blog.csdn.net/InjoyMario/article/details/95057232
版权声明: 本博客所有文章除特别声明外,均采用 ©BY-NC-SA 许可协议。转载请注明出处!