操作字符串的类有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
而StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
String | StringBuffer | StringBuilder | |
类是否可变 | 不可变(Final) | 可变 | 可变 |
功能介绍 | 每次对String的操作都会在“常量池”中生成新的String对象 任何对它指向的字符串的操作都不会产生新的对象。 | 每个StringBuffer对象都有一定的缓冲区容量,字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,自动扩容 | 功能与StringBuffer相同,相比少了同步锁,执行速度更快 |
线程安全性 | 线程安全 | 线程安全 | 线程不安全 |
使用场景推荐 | 单次操作或循环外操作字符串 | 多线程操作字符串 | 单线程操作字符 |
初始容量 | 16 | 16 |
StringBulider > StringBuffer > String
String <(StringBuffer,StringBuilder)的原因?
String:字符串常量
StringBuffer:字符串变量(有同步锁)
StringBuilder:字符串变量(无同步锁)
从上面的名字可以看到,String是"字符串常量",也就是不可改变的对象。源码如下:
public final class String{}
对于上面这句话的理解你可能会产生这样一个疑问 ,比如这段代码:
String str = "唐伯虎"; str = str + "点香烟"; System.out.print(str); // result : "唐伯虎点香烟"
我们明明改变了String型的变量str啊,为什么说是没有改变呢?我们来看一下这张对String操作时内存变化的图:
我们可以看到,初始String值为"唐伯虎",然后在这个字符串后面加上新的字符串"点香烟",这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了"唐伯虎点香烟"字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费,执行效率同理。
为了应对经常性操作字符串的场景,Java才提供了其他两个操作字符串的类 —— StringBuffer、StringBuilder。
他们俩均属于字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些而外的对象进行操作了,速度自然就相对快了。
我们一般在StringBuffer、StringBuild类上的主要操作是 append 和 insert 方法,这些方法允许被重载,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点(index)添加字符。
StringBuilder一个可变的字符序列是JDK1.5新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。
如果可能,建议优先采用StringBuilder类,因为在大多数实现中,它比 StringBuffer 要快。且两者的方法基本相同。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
String 类型和 StringBuffer、 StringBuild类型的主要性能区别其实在于 String 是不可变的对象(final), 因此在每次对 String 类型进行改变的时候其实都等同于在堆中生成了一个新的 String 对象,然后将指针指向新的 String 对象,这样不仅效率低下,而且大量浪费有限的内存空间,所以经常改变内容的字符串最好不要用 String 。因为每次生成对象都会对系统性能产生影响,特别是当内存中的无引用对象过多了以后, JVM 的 GC 开始工作,那速度是一定会相当慢的。另外当GC清理速度跟不上new String的速度时,还会导致内存溢出Error,会直接kill掉主程序!报错如下:
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded Exception in thread "I/O dispatcher 3797236" java.lang.OutOfMemoryError: GC overhead limit exceeded
StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。
synchronized的含义:
每一个类对象都对应一把锁,当某个线程A调用类对象O中的synchronized方法M时,必须获得对象O的锁才能够执行M方法,否则线程A阻塞。一旦线程A开始执行M方法,将独占对象O的锁。使得其它需要调用O对象的M方法的线程阻塞。只有线程A执行完毕,释放锁后。那些阻塞线程才有机会重新调用M方法。这就是解决线程同步问题的锁机制。 > 了解了synchronized的含义以后,大家可能都会有这个感觉。多线程编程中StringBuffer比StringBuilder要安全多了 ,事实确实如此。如果有多个线程需要对同一个字符串缓冲区进行操作的时候,StringBuffer应该是不二选择。
注意:是不是String也不安全呢?事实上不存在这个问题,String是不可变的。线程对于堆中指定的一个String对象只能读取,无法修改。试问:还有什么不安全的呢?
实际应用场景中:
来看一下源码,在没有传参的情况下默认初始容量是16。
/** * Constructs a string builder with no characters in it and an * initial capacity of 16 characters. */ public StringBuilder() { super(16); }
有参数的情况下,初始容量是16+字符串的长度,并且是用append()方法追加的字符。
/** * Constructs a string builder initialized to the contents of the * specified string. The initial capacity of the string builder is * {@code 16} plus the length of the string argument. * * @param str the initial contents of the buffer. */ public StringBuilder(String str) { super(str.length() + 16); append(str); }
到这里就有疑问那,那这个字符串的长度是多少呢。是它本身的长度还是16+它自身的长度,来接着往下看。一路追寻append()方法终于找到答案了。注意,这个len居然是String自身的长度,现在明白了吧。其实平时咱们也在用str.length();方法就是没注意它的长度是怎么来的。
/** * Appends the specified string to this character sequence. * <p> * The characters of the {@code String} argument are appended, in * order, increasing the length of this sequence by the length of the * argument. If {@code str} is {@code null}, then the four * characters {@code "null"} are appended. * <p> * Let <i>n</i> be the length of this character sequence just prior to * execution of the {@code append} method. Then the character at * index <i>k</i> in the new character sequence is equal to the character * at index <i>k</i> in the old character sequence, if <i>k</i> is less * than <i>n</i>; otherwise, it is equal to the character at index * <i>k-n</i> in the argument {@code str}. * * @param str a string. * @return a reference to this object. */ 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; }
忽然又想到一个问题,那要是在追加字符串的时候长度比16大怎么办,我们看到有个ensureCapacityInternal()的方法,追进去看看,然后发现它是这么扩容的 int newCapacity = (value.length << 1) + 2;增加为自身长度的一倍然后再加2;这个时候如果还是放不下,那就直接扩容到它需要的长度 newCapacity = minCapacity;
/** * For positive values of {@code minimumCapacity}, this method * behaves like {@code ensureCapacity}, however it is never * synchronized. * If {@code minimumCapacity} is non positive due to numeric * overflow, this method throws {@code OutOfMemoryError}. */ private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } } /** * Returns a capacity at least as large as the given minimum capacity. * Returns the current capacity increased by the same amount + 2 if * that suffices. * Will not return a capacity greater than {@code MAX_ARRAY_SIZE} * unless the given minimum capacity is greater than that. * * @param minCapacity the desired minimum capacity * @throws OutOfMemoryError if minCapacity is less than zero or * greater than Integer.MAX_VALUE */ 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; }