从JDK源码看,String、StringBuilder、StringBuffer都是存放在char[] 数组字符串。
简单看下三者的部分源码:
String定义属性和构造方法:java
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; public String() { this.value = "".value; } public String(String original) { this.value = original.value; this.hash = original.hash; } public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
StringBuilder源码:web
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence { public StringBuilder() { super(16); } public StringBuilder(int capacity) { super(capacity); } public StringBuilder(String str) { super(str.length() + 16); append(str); }
StringBuffer源码:数组
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence { private transient char[] toStringCache; public StringBuffer() { super(16); }
比较明显的是:
String 中定义的char[] 数组是用final 修饰,因此,String 是不可变字符序列,而StringBuilder和StringBuffer是可变字符序列;
若是Sting 须要改变则须要从新建立新对象;
StringBuffer 和 StringBuilder 都继承 AbstractStringBuilder类,他们在初始化时,都是调用父类的构造器。缓存
接下来,咱们在简单看下AbstractStringBuilder类源码:安全
abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value; /** * The count is the number of characters used. */ int count; /** * This no-arg constructor is necessary for serialization of subclasses. */ AbstractStringBuilder() { } /** * Creates an AbstractStringBuilder of the specified capacity. */ AbstractStringBuilder(int capacity) { value = new char[capacity]; }
能够看到 AbstractStringBuilder 其实也定义了char[] 数组,不一样的是,AbstractStringBuilder 中的char[] 数组能够可变的,在细看一点,能够看到AbstractStringBuilder 有扩容的方法:app
private int newCapacity(int minCapacity) { // overflow-conscious code int newCapacity = (value.length << 1) + 2; if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; } private int hugeCapacity(int minCapacity) { if (Integer.MAX_VALUE - minCapacity < 0) { // overflow throw new OutOfMemoryError(); } return (minCapacity > MAX_ARRAY_SIZE) ? minCapacity : MAX_ARRAY_SIZE; }
接下来咱们继续,看下String 、StringBuffer 和 StringBuilder的经常使用方法:
String的经常使用方法:jvm
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 substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }
StringBuilder的经常使用方法:ide
@Override public StringBuilder append(int i) { super.append(i); return this; } @Override public StringBuilder append(long lng) { super.append(lng); return this; } @Override public StringBuilder append(float f) { super.append(f); return this; }
StringBuffer的经常使用方法:svg
@Override public synchronized StringBuffer append(CharSequence s, int start, int end) { toStringCache = null; super.append(s, start, end); return this; } @Override public synchronized StringBuffer append(char[] str) { toStringCache = null; super.append(str); return this; }
从它们的经常使用方法能够看出:
String 每次返回的都是新字符串,因此咱们使用String的方法操做字符串后不影响原来的字符串;
StringBuffer 和 StringBuilder 返回的都是this,也就是对象自己,全部咱们能够在代码中连着写append(xx).append(xxx).append(xxx);
不一样的是StringBuffer的方法就加了synchronized 也就是咱们说的线程安全。
总结一下:
性能
咱们经过各自拼接10000字符串来比较一下三者在执行时对时间和对内存资源的占用。
下面是测试代码:
package com.xzlf.string; public class TestString { public static void main(String[] args) { // 使用 String 进行字符拼接 String str = ""; long num1 = Runtime.getRuntime().freeMemory();// 获取系统剩余内存空间 long time1 = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { str += i; // 至关于产生了5000个对象 } long num2 = Runtime.getRuntime().freeMemory(); long time2 = System.currentTimeMillis(); System.out.println("String 占用了内存:" + (num1 - num2)); System.out.println("String 占用了时间:" + (time2 - time1)); // 使用 StringBuilder 进行字符串拼接 StringBuilder sb = new StringBuilder(""); long num3 = Runtime.getRuntime().freeMemory(); long time3 = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { sb.append(i); } long num4 = Runtime.getRuntime().freeMemory(); long time4 = System.currentTimeMillis(); System.out.println("StringBuilder 占用了内存:" + (num3 - num4)); System.out.println("StringBuilder 占用了时间:" + (time4 - time3)); // 使用 StringBuilder 进行字符串拼接 StringBuffer sb2 = new StringBuffer(""); long num5 = Runtime.getRuntime().freeMemory(); long time5 = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { sb2.append(i); } long num6 = Runtime.getRuntime().freeMemory(); long time6 = System.currentTimeMillis(); System.out.println("StringBuffer 占用了内存:" + (num5 - num6)); System.out.println("StringBuffer 占用了时间:" + (time6 - time5)); } }
以上代码运行结果为:
能够看到,String建立了大量无用对象,消耗了大量内存耗时上大概是StringBuffer 和 builder的100倍。
固然,咱们只循环了10000次,StringBuilder的优点不是很明显,为了防止java 虚拟机 jvm 垃圾回收机制的干扰 咱们我StringBuilder 和 StringBuffer 单独拿出来吧循环次数加到10万次、100万次和1000万次测试:
代码吧String部分注释掉,因为循环次数较多,jvm 在运行时会有垃圾回收,内存对比会不正确,也先注释:
package com.xzlf.string; public class TestString { public static void main(String[] args) { // 使用 String 进行字符拼接 // String str = ""; // long num1 = Runtime.getRuntime().freeMemory();// 获取系统剩余内存空间 // long time1 = System.currentTimeMillis(); // for (int i = 0; i < 10000; i++) { // str += i; // 至关于产生了5000个对象 // } // long num2 = Runtime.getRuntime().freeMemory(); // long time2 = System.currentTimeMillis(); // System.out.println("String 占用了内存:" + (num1 - num2)); // System.out.println("String 占用了时间:" + (time2 - time1)); // 使用 StringBuilder 进行字符串拼接 StringBuilder sb = new StringBuilder(""); long num3 = Runtime.getRuntime().freeMemory(); long time3 = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { sb.append(i); } long num4 = Runtime.getRuntime().freeMemory(); long time4 = System.currentTimeMillis(); // System.out.println("StringBuilder 占用了内存:" + (num3 - num4)); System.out.println("StringBuilder 占用了时间:" + (time4 - time3)); // 使用 StringBuilder 进行字符串拼接 StringBuffer sb2 = new StringBuffer(""); long num5 = Runtime.getRuntime().freeMemory(); long time5 = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { sb2.append(i); } long num6 = Runtime.getRuntime().freeMemory(); long time6 = System.currentTimeMillis(); // System.out.println("StringBuffer 占用了内存:" + (num5 - num6)); System.out.println("StringBuffer 占用了时间:" + (time6 - time5)); } }
我这边测试10万次结果为:
100万次结果为:
1000万次结果为:
在数量太少的状况下,StringBuilder 在StringBuffer加锁的状况下,并无体现出优点,反而StringBuffer 更胜一筹。
这种状况相信不少测试过的小伙伴也应该遇到过???
对于这种状况,其实也不难理解,append的操做本质仍是操做char[] 数组,咱们仍是继续看源码,
StringBuffer比StringBuilder多了一个缓冲区,
咱们看下StringBuffer的toString方法:
@Override public synchronized String toString() { if (toStringCache == null) { toStringCache = Arrays.copyOfRange(value, 0, count); } return new String(toStringCache, true); }
StringBuilder 的toString()方法:
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
咱们能够看到StringBuffer的缓存有数据时,就直接在缓存区取,而StringBuilder每次都是直接copy。这样StringBuffer 相对StringBuilder来讲实际上是作了一个性能上的优化,全部只有当数量足够大,StringBuffer的缓冲区填补不了加锁影响的性能时,StringBuilder才在性能上展示出了它的优点