深入解析String,StringBuilder,StringBuffer异同
温馨提示:
本文最后更新于 2024年07月21日,已超过 271 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我。
String 字符串常量
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; }
String其实是一经定义就不能改变的char数组。
下面看下String类中的几个方法:
public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); } public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); } public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; }
从上面三个方法可以看出,无论是进行substring,concat还是replace操作,都不会在原String上操作,而是重新生成了一个新的字符串对象。也就是说,进行这些操作后,一开始的字符串是没有改变的。
在这永远要记住一点:对String对象的任何改变都不会影响到原对象,相关的任何更改操作都会生成新对象。
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(线程不安全)
既然Java中已经有String类了,为什么还要StringBuilder和StringBuffer类呢?
看这段代码:
class StringDemo{ public static void main(String[] args){ String s = ""; for(int i = 0; i < 6666; i++){ s += "fxyh"; } } }
- 这句s += "fxyh";的过程相当于将原有的s变量指向新的内容中的对象,然后再进行字符串相加操作,再把这个引用重新指向s变量。
- 看反编译的代码:
这段反编译的class文件可以很清除的看出:整个循环的执行过程,并且每次循环都会new出一个StringBuilder对象,然后进行append操作,最后toString方法返回了String对象,也就是说,这个循环完毕后new了6666个对象,想一下,如果这些对象没有被回收,会造成多大的内存浪费。
从反编译后的代码可以看出s += "fxyh";的操作实际自动被JVM优化成了下面这个代码:
StringBuilder str = new StringBuilder(s); str.append("fxyh"); str.toString();
- 再看下这段代码:
class StringDemo{ public static void main(String[] args){ StringBuilder sb = new StringBuilder(); for(int i = 0; i < 6666; i++){ sb.append("fxyh"); } } }
- 看反编译的代码:
- 从这里可以明显看出,这段代码的for循环,一直是对new 的StringBuilder对象进行append操作,而且这里只new了一次,因此在循环了这么多次后,这段代码所占的资源明显比上面的小很多。
- 看反编译的代码:
- 那么既然有了StringBuilder,为啥还要StringBuffer类,看了源码就明白了,StringBuffer类的成员方法和属性和StringBuilder基本一样,但是StringBuffer的成员方法多了一个关键字:synchronized,这个关键字可以在多线程中起到安全保护作用,也就是StringBuffer是线程安全的。
下面看几个测试方法,让我们更深入了解一下。
package com.fxyh.stringtest; public class Test1 { public static void main(String[] args) { testString1(); testStringBuffer(); testStringBuilder(); testString2(); testString3(); } public static void testString1() { String s = ""; long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { s += "fxyh"; } long end = System.currentTimeMillis(); System.out.println("testString1使用时间为:" + (end - start) + "毫秒"); } public static void testStringBuffer() { StringBuffer sb = new StringBuffer(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { sb.append("fxyh"); } long end = System.currentTimeMillis(); System.out.println("testStringBuffer使用时间为:" + (end - start) + "毫秒"); } public static void testStringBuilder() { StringBuilder sb = new StringBuilder(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { sb.append("fxyh"); } long end = System.currentTimeMillis(); System.out.println("testStringBuilder使用时间为:" + (end - start) + "毫秒"); } public static void testString2() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String s = "I"+"am"+"fxyh"; } long end = System.currentTimeMillis(); System.out.println("字符串直接相加操作使用时间为:" + (end - start) + "毫秒"); } public static void testString3() { String s1 = "I"; String s2 = "am"; String s3 = "fxyh"; long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String s = s1 + s2 + s3; } long end = System.currentTimeMillis(); System.out.println("字符串间接相加操作使用时间为:" + (end - start) + "毫秒"); } }
测试结果:
我们刚才说s+="fxyh";JVM会自动优化,看下面的这段代码
package com.fxyh.stringtest; public class Test1 { public static void main(String[] args) { testString1(); testString(); } public static void testString1() { String s = ""; long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { s += "fxyh"; } long end = System.currentTimeMillis(); System.out.println("testString1使用时间为:" + (end - start) + "毫秒"); } public static void testString(){ String s = ""; long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { StringBuilder sb = new StringBuilder(s); sb.append("fxyh"); s=sb.toString(); } long end = System.currentTimeMillis(); System.out.println("模拟jvm优化操作使用时间为:" + (end - start) + "毫秒"); } }
测试结果:
- 两个执行时间,基本是一样,同样也验证了s+="fxyh";JVM会自动优化。
下面对上面的执行结果进行一些推理:
- 对于直接相加的字符串,效率很高,因为在编译的时候,便已经确定了值,即"I"+"am"+"fxyh";这样的字符串相加,在编译的时候被优化成了“Iamfxyh”。
- 反编译后:
- 对于间接相加,形如s1 + s2 + s3;效率要比直接相加底,因为编译器不会对引用变量进行优化。
- 反编译后:
- String、StringBuilder、StringBuffer三者的执行效率:
- StringBuilder>StringBuffer>String
- 这个也不是一定的:比如String s = "I" + "am" + "fxyh"的效率就比StringBuilder sb = new StringBuilder().append("I").append("am").append("fxyh");要高。
- 所以我们要更具适当的情况,选用不同的对象。
- 当字符串相加操作或者改动少的情况下,建议使用String s = "fxyh";这种形式;
- 当字符串相加较多的情况下,建议使用StringBuilder,如果是多线程的话,则使用StringBuffer。
再来踩几个坑:
- 下面这段代码的输出结果是什么?
String a = “hello2″; String b = “hello” + 2; System.out.println((a == b));
输出结果为:true。 原因很简单,"hello"+2在编译的时候就已经优化成了"hello2",因此在运行期间,变量a和b是指向同一个对象。
- 下面这段代码的输出结果是什么?
String a = “hello2″; String b = “hello”; String c = b + 2; System.out.println((a == c));
输出结果为:false。 由于有符号引用存在,所以String c = b + 2; 在编译的时候不会优化成“hello2”,因此这种方式生成的对象是保存在堆上。所以a和c指向的并不是同一个对象。
- 下面这段代码的输出结果是什么?
String a = “hello2″; final String b = “hello”; String c = b + 2; System.out.println((a == c));
输出结果为:true。 对于被final修饰的变量,会在class文件的常量池中存在,也就是说,不会通过连接访问,对final变量的访问在编译期间都会直接被替换成真实的值。 因此String c = b + 2; 在编译的时候就会被优化成String c = “hello” + 2;
- 下面这段代码的输出结果是什么?
个人的一些见解,若有不对的地方,希望大家能帮我指正。
安利一波国内在维护的一款Linux系统-deepin,深度,好用!好用!好用!
正文到此结束
- 本文标签: Java
- 本文链接: https://fxyh.top/article/19
- 版权声明: 本文由fxyh原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权