java基础解析系列--String、StringBuffer、StringBuilder
前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容。
String
==问题
String s6=new String("jiajun"); String s1="jiajun"; String s2="jiajun"; System.out.println(s1==s2);//true System.out.println(s1==s6);//false
- 看常量池中是否已有此字符串,如果有,将指针指向这个字符串
- 如果使用new来创建字符串对象,那么这个字符串是存放在堆中,无论堆中是否已有这个对象
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); }
- 从源码可以看出,任何操作都是创建一个新的对象,不影响原对象
StringBuffer和StringBuidler
初始容量
- StringBuilder和StringBuffer的构造参数来初始化容量
public StringBuilder() { super(16); }AbstractStringBuilder(int capacity) { value = new char[capacity]; }
- 默认情况下容量为16
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } } public static char[] copyOf(char[] original, int newLength) { char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
- 从源码看出,在执行append方法的时候,会执行ensureCapacityInternal方法来保证容量,而如果超出容量的话,会重新创建一个char数组,并将旧的字符数组复制到新的字符数组
线程安全
public synchronized StringBuffer append(StringBuffer sb) { toStringCache = null; super.append(sb); return this; } public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } public StringBuilder append(StringBuffer sb) { super.append(sb); return this; } public StringBuilder append(String str) { super.append(str); return this; } public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }public synchronized StringBuffer append(StringBuffer sb) { toStringCache = null; super.append(sb); return this; } public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } public StringBuilder append(StringBuffer sb) { super.append(sb); return this; } public StringBuilder append(String str) { super.append(str); return this; } public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
- 可以看出,String的方法是加了synchronzied,也就加了锁,那么而在单线程的情况下或者不用考虑线程安全的情况下,那么StringBuilder的性能是更高的
toString方法
public synchronized String toString() { if (toStringCache == null) { toStringCache = Arrays.copyOfRange(value, 0, count); } return new String(toStringCache, true); }public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
- 通过源码发现,toString方法会创建一个新的String对象
性能试验
间接相加和直接相加
public class d { public static void main(String[] args) { String s="I"+"love"+"jiajun"; String s1="I"; String s2="love"; String s3="jiajun"; String s4=s1+s2+s3; }}
- 通过反编译的结果可以看出,第一种方式字符串直接相加,在编译器就直接优化了”Ilovejiajun“
- 而第二种方式间接相加,从结果可以看出,是先创建一个StringBuilder,然后再apend,最后再toString方法,可以发现性能比第一种低
public class d { public static void main(String[] args) { String s="I"+"love"+"jiajun"; String s1="I"; String s2=s1+"lovejiajun"; System.out.println(s==s2); }}
- 同样从反编译的结果可以看出,第二种方式并没有被优化,也是通过StringBuilder来实现的,最后通过toString方法创建一个String对象,所以返回的false
- 但是当s1是用final修饰的却是不一样的,虚拟机会对其进行优化,所以不会像之前一样创建一个StringBuilder,最后在堆中产生一个对象
public class d { public static void main(String[] args) { final String s1="I"; String s2=s1+"lovejiajun"; String s3="Ilovejiajun"; //s1==s3 }}
用+和用append
public class Demo3 { public static void main(String[] args) { run1(); run2(); } public static void run1() { long start = System.currentTimeMillis(); String result = ""; for (int i = 0; i < 10000; i++) { result += i; } System.out.println(System.currentTimeMillis() - start); } public static void run2() { long start = System.currentTimeMillis(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < 10000; i++) { builder.append(i); } System.out.println(System.currentTimeMillis() - start); }
//输出:223 1
- 从实验发现,用append效率更高,从实验一发现,当字符串相加的时候,实际上每次都会重新初始化StringBuilder然后执行相加,这样效率并不高
初始化容量
public class Demo3 { public static void main(String[] args) { test1(); test2(); } public static void test1() { StringBuilder sb = new StringBuilder(7000000); long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { sb.append("jiajun"); } long end=System.currentTimeMillis()-start; System.out.println(end); } public static void test2() { StringBuilder sb = new StringBuilder(); long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { sb.append("jiajun"); } long end=System.currentTimeMillis()-start; System.out.println(end); } //输出:18 26}
- 通过实验可以看出,适当的初始化容量可以提高性能,因为当不初始化容量的时候,如果此时append超出容量,那么将会从新创建一个char数组,并且进行复制
总结
- 用new创建对象的时候,会在堆中创建对象,而如果是直接用引号形式的话,会先看常量池是否有此字符串,有的话指向常量池的字符串
- StringBuilder是非线程安全的,StringBuffer是线程安全的
- 使用StringBuilder和StringBuffer的时候最好初始化一个合适的容量,因为如果默认容量不够的话,会重新创建一个char数组,再进行复制
- 字符串相加的时候,直接相加的时候,编译器会进行优化,而如果是间接相加的时候,实际上会创建一个StringBuilder来进行append
例如:
StringTest.java
public class StringTest { public static void main(String[] args) { String s = "111111" + "2222222" + "33333333333"; String s2=s+"4444"; s=s+"555"; }}
编译之后:StringTest.class
public class StringTest{ public static void main(String[] paramArrayOfString) { String str1 = "111111222222233333333333"; String str2 = str1 + "4444"; str1 = str1 + "555"; }}
JavaP反编译字节码文件:
C:\Users\liqiang\Desktop>javap -c StringTest.classCompiled from "StringTest.java"public class StringTest { public StringTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String 111111222222233333333333 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder." ":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String 4444 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_2 23: new #3 // class java/lang/StringBuilder 26: dup 27: invokespecial #4 // Method java/lang/StringBuilder." ":()V 30: aload_1 31: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 34: ldc #8 // String 555 36: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 39: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 42: astore_1 43: return}