String
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串。
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
注意:
1、String源码中可以看出,String底层实际是通过Char【】来保存字符。
2、new String("A") 和 “A ”是不一样的,虽然这种情况可能取得常量池的数据是一样的,但是只要关键字new,就会创建新对象(位置在堆中),例如:
public static void main(String[] args) { String s1 = "A"; String s2 = "A"; String s3 = new String("A"); System.out.println(s1 == s2);//true System.out.println(s1 == s3);//false }
3、因为字符串存在常量池,不需要创建对象,所以"A"+"B"这种计算在编译时即可识别。如果通过 “+”来连接两个String对象,则无法在编译时被识别,而且过程涉及到新对象的创建(即新的引用地址)。
public static void main(String[] args) { String s1 = "B"; String s2 = "AB"; String s3 = "A"+s1; System.out.println(s2 == s3);//false }
public static void main(String[] args) { String s1 = "B"; String s2 = "AB"; String s3 = "A"+s1; String s4 = new String("AB"); System.out.println(s2 == s3);//false System.out.println(s2 == s4);//false System.out.println(s3 == s4);//false }
4、关于String.intern()
intern方法使用:一个初始为空的字符串池,它由类String独自维护。当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
补充:equals 和 ==
(1)对于==,如果作用于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),则直接比较其存储的"值"是否相等;如果作用于引用类型的变量(String),则比较的是所指向的对象的地址(即是否指向同一个对象)。
(2)equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。
(3)对于equals方法,注意:equals方法不能作用于基本数据类型的变量。如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;而String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。
摘抄自:https://www.cnblogs.com/xiaoxi/p/6036701.html
StringBuffer&StringBuilder
StringBuffer和StringBuilder都是继承AbstractStringBuilder的类,默认初始容量为16。和String不一样的是,String存在于常量池中,而StringBuffer和StringBuilder是对象,都需要new出来,只不过可以动态的操作字符串,底层也是动态Char[] 。
注意:
1、扩容机制StringBuffer和StringBuilder在调用方法:append()时都会计算一次添加数据后的容量"L1",然后与初始的容量"L0"进行比较,看容量是否溢出,如果溢出则进行扩容.
扩容后的容量为 L0 * 2 + 2(即原始容量的2倍,再加2),如果扩容后的容量还是不够用,就把添加数据时计算的容量"L1"置为新的容量。
1 //StringBuilder的append() 2 public StringBuilder append(String str) { 3 super.append(str); 4 return this; 5 } 6 //StringBuffer的append() 7 public synchronized StringBuffer append(String str) { 8 toStringCache = null; 9 super.append(str); 10 return this; 11 } 12 //调转至父类AbstractStringBuilder的append() 13 public AbstractStringBuilder append(StringBuffer sb) { 14 if (sb == null) 15 return appendNull(); 16 int len = sb.length(); 17 ensureCapacityInternal(count + len); 18 sb.getChars(0, len, value, count); 19 count += len; 20 return this; 21 } 22 private void ensureCapacityInternal(int minimumCapacity) { 23 if (minimumCapacity - value.length > 0) 24 expandCapacity(minimumCapacity); 25 } 26 void expandCapacity(int minimumCapacity) { 27 int newCapacity = value.length * 2 + 2; 28 if (newCapacity - minimumCapacity < 0) 29 newCapacity = minimumCapacity; 30 if (newCapacity < 0) { 31 if (minimumCapacity < 0) 32 throw new OutOfMemoryError(); 33 newCapacity = Integer.MAX_VALUE; 34 } 35 value = Arrays.copyOf(value, newCapacity); 36 }
2、StringBuffer是线程安全的,StringBuilder线程不安全。从源码中可以看出,StringBuffer的大部分方法都用了synchronized关键字加锁,所以保证了线程安全,当然因为有锁,所以效率上会比StringBuilder差。
1 //StringBuffer 2 public synchronized StringBuffer append(String str) { 3 toStringCache = null; 4 super.append(str); 5 return this; 6 } 7 //StringBuilder 8 public StringBuilder append(String str) { 9 super.append(str); 10 return this; 11 } 12 13 //StringBuffer 14 public synchronized StringBuffer delete(int start, int end) { 15 toStringCache = null; 16 super.delete(start, end); 17 return this; 18 } 19 //StringBuilder 20 public StringBuilder delete(int start, int end) { 21 super.delete(start, end); 22 return this; 23 } 24 25 //StringBuffer 26 public synchronized StringBuffer replace(int start, int end, String str) { 27 toStringCache = null; 28 super.replace(start, end, str); 29 return this; 30 } 31 //StringBuilder 32 public StringBuilder replace(int start, int end, String str) { 33 super.replace(start, end, str); 34 return this; 35 }
效率对比:
1 public static void main(String[] args) { 2 String s1 = ""; 3 StringBuilder builder = new StringBuilder(2000); 4 StringBuffer buffer = new StringBuffer(2000); 5 long t1 = System.currentTimeMillis(); 6 for (int i = 0; i < 1000; i++) { 7 s1 += i; 8 } 9 long t2 = System.currentTimeMillis(); 10 for (int i = 0; i < 1000; i++) { 11 builder.append(i); 12 } 13 long t3 = System.currentTimeMillis(); 14 for (int i = 0; i < 1000; i++) { 15 buffer.append(i); 16 } 17 long t4 = System.currentTimeMillis(); 18 long time1 = t2 - t1; 19 long time2 = t3 - t2; 20 long time3 = t4 - t3; 21 System.out.println("String的操作时间 " + time1); 22 System.out.println("StringBuilder的操作时间 " + time2); 23 System.out.println("StringBuffer的操作时间 " + time3); 24 }
总结:
1、String操作比较方便但是仅限于少量字符串的添加,无法直接进行删减和更改,如果操作大量字符串则选用StringBuilder或者StringBuffer。
2、因为StringBuffer线程安全,在需要保证线程安全时只能用StringBuffer。
3、在需要进行大量字符串操作又不需要考虑线程安全时选StringBuilder。