java中String,StringBuilder,StringBuffer实现原理,优化
Posted Leo Han
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中String,StringBuilder,StringBuffer实现原理,优化相关的知识,希望对你有一定的参考价值。
在java中字符串使我们编程时经常需要使用的类型,java中提供了String、StringBuilder、StringBuffer等相关字符串操作类。
首先来看String,JDK中String的实现是一个final类,其内部主要还是一个char数组组成。
public final class String
implements java.io.Serializable, Comparable, CharSequence
/** The value is used for character storage. */
private final char value[];
…
之所以要定义为final类型,是因为String的hash算法是和char数组相关的:
public int hashCode()
int h = hash;
if (h == 0 && value.length > 0)
char val[] = value;
for (int i = 0; i < value.length; i++)
h = 31 * h + val[i];
hash = h;
return h;
并且String一旦初始化之后,后面就不能在对这个String实例的内容进行更新,只能生成一个新的。
主要还是因为这里String的hashCode计算方式是和value数组相关的。是通过value数组计算得到的。如果我们修改了value值,那么String的hashCode就会发生改变,而JDK中很多实现都是依赖于hashCode的,比如map。我们开始放入了一个key为String的对象,如果我们修改value数组,那么后续我们无法获取到这个key对应的值了(map的key是需要hashCode运算的)。
另外一个很经典的问题:
String a = "Hello World";
String b = new String("Hello World");
System.out.println(a == b );
System.out.println(a.equals(b));
System.out.println(a == b.intern());
这里会输出:
false
true
true
首先需要明确的一点是:
String a = "Hello World";
这里的Hello World
是一个字符串常量,这块内存布局如下:
这里的 String a = "Hello World";
中 “Hello World”;这个是在常量池里面分配的,a的引用是直接指向常量池的。
而b是在堆上分配的,b的引用是指向堆上的对象的的。
而如果调用了String.inern方法,那么返回的是一个字符串常量池的对象,我们可以看看这个方法的定义:
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class @code String.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this @code String object as determined by
* the @link #equals(Object) method, then the string from the pool is
* returned. Otherwise, this @code String object is added to the
* pool and a reference to this @code String object is returned.
* <p>
* It follows that for any two strings @code s and @code t,
* @code s.intern() == t.intern() is @code true
* if and only if @code s.equals(t) is @code true.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
```
可以看到intern方法返回是一个和当前String对象拥有相同内容的字符串常量池对象,如果字符串常量池没有会创建一个,如果已经有了,那么直接返回。
` String a = "Hello World";``这种方式分配的对象是直接在字符串常量池中的,与b.intern(0的常量池对象是同一个。
#### StringBuilder
StringBuilder相比String而言其实大体差不多,底层也是有一个char数组,但是其数组是可变的。
```
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
.....
```
StringBuilder是继承自AbstractStringBuilder,StringBuffer也是,只不过相关方法StringBuffer增加了synchronized同步语义。
StringBuilder默认初始容量是16,当我们使用append的时候,实现如下:
```java
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));
```
`ensureCapacityInternal`是确保底层char数组有足够大的容量来容纳字符内容,如果不够将进行扩容,通过`Arrays.copyOf()`对原数组进行扩容。
`AbstractStringBuilder`中还有一个属性`count`,用来标记当前数组中有多少个元素被填充,coutn <= value.length
这样,我们每次append数组的时候,如果容量不够都会进行扩容。
而StringBuider的toString方法很简单粗暴:
```java
public String toString()
// Create a copy, don't share the array
return new String(value, 0, count);
public String(char value[], int offset, int count)
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (count <= 0)
if (count < 0)
throw new StringIndexOutOfBoundsException(count);
if (offset <= value.length)
this.value = "".value;
return;
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count)
throw new StringIndexOutOfBoundsException(offset + count);
this.value = Arrays.copyOfRange(value, offset, offset+count);
```
就是将当前StringBuilder的value数组复制到String的value数组中去。
实际上如果我们再初始StringBuilder的时候可以将容量放大一点,这样可以避免在append过程中不断进行数组的扩容复制。
另外一个就是StringBuilder提供了一个`setLength`方法:
```java
public void setLength(int newLength)
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
ensureCapacityInternal(newLength);
if (count < newLength)
Arrays.fill(value, count, newLength, '\\0');
count = newLength;
```
可以看到如果新的大小比已有的内容大小大,那么会进行扩容并填充``\\0\\,如果不是,那么直接重设` count = newLength;`
如果我们`setLength(0)`那么原有分配的数组不会被回收内容不变,只是count=0,这样后续append的时候,就会覆盖之前的内容。
#### StringBuffer
StringBuffer基本上与StringBuilder一样,但是在append方法上会加上synchronized同步语义
以上是关于java中String,StringBuilder,StringBuffer实现原理,优化的主要内容,如果未能解决你的问题,请参考以下文章
JAVA辨析:String,StringBuffer与StringBuilder
Java之String+StringBuffer+StringBuilder
java中String&StringBuffer&StringBuilder
重温java中的String,StringBuffer,StringBuilder类