其实对于JDK源码一直有一种情愫,希望能看到前辈们的精华之处,感受代码之美,今天起我也去感受下,如果有和我志同道合的盆友呢,希望咱们一起努力,多多指教吧!
先来看下JDK帮助文档对于三个类的解释,源码中对于三个类的解释都是英文,所以我找的解释就是中文的JDK帮助文档。
public final class String extends Objectimplements Serializable, Comparable, CharSequence
String 类代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。例如:
String str = "abc";
等效于:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
下面给出了一些如何使用字符串的更多示例:
System.out.println("abc");
String cde = "cde";
System.out.println("abc" + cde);
String c = "abc".substring(2,3);
String d = cde.substring(1, 2);
String 类包括的方法可用于检查序列的单个字符、比较字符串、搜索字符串、提取子字符串、创建字符串副本并将所有字符全部转换为大写或小写。大小写映射基于 Character 类指定的 Unicode 标准版。
Java 语言提供对字符串串联符号(”+”)以及将其他对象转换为字符串的特殊支持。字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字符串转换是通过 toString 方法实现的,该方法由 Object 类定义,并可被 Java 中的所有类继承。有关字符串串联和转换的更多信息,请参阅 Gosling、Joy 和 Steele 合著的 The Java Language Specification。
除非另行说明,否则将 null 参数传递给此类中的构造方法或方法将抛出 NullPointerException。
String 表示一个 UTF-16 格式的字符串,其中的增补字符 由代理项对 表示(有关详细信息,请参阅 Character 类中的 Unicode 字符表示形式)。索引值是指 char 代码单元,因此增补字符在 String 中占用两个位置。
String 类提供处理 Unicode 代码点(即字符)和 Unicode 代码单元(即 char 值)的方法。
public final class StringBuffer extends Object implements Serializable, CharSequence
线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
例如,如果 z 引用一个当前内容为 “start” 的字符串缓冲区对象,则此方法调用 z.append(“le”) 会使字符串缓冲区包含 “startle”,而 z.insert(4, “le”) 将更改字符串缓冲区,使之包含 “starlet”。
通常,如果 sb 引用 StringBuilder 的一个实例,则 sb.append(x) 和 sb.insert(sb.length(), x) 具有相同的效果。
当发生与源序列有关的操作(如源序列中的追加或插入操作)时,该类只在执行此操作的字符串缓冲区上而不是在源上实现同步。
每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。从 JDK 5 开始,为该类补充了一个单个线程使用的等价类,即 StringBuilder。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。
一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。
在 StringBuilder 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串生成器中。append 方法始终将这些字符添加到生成器的末端;而 insert 方法则在指定的点添加字符。
例如,如果 z 引用一个当前内容为 “start” 的字符串的生成器对象,则该方法调用 z.append(“le”) 将使字符串生成器包含 “startle”,而 z.insert(4, “le”) 将更改字符串生成器,使之包含 “starlet”。
通常,如果 sb 引用 StringBuilder 的实例,则 sb.append(x) 和 sb.insert(sb.length(), x) 具有相同的效果。每个字符串生成器都有一定的容量。只要字符串生成器所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区。如果内部缓冲区溢出,则此容量自动增大。
将 StringBuilder 的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用 StringBuffer。
public final class String
implements java.io.Serializable, Comparable, CharSequence
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
CharSequence是一个定义字符串操作的接口,StringBuffer、StringBuilder、String中都实现了这个接口.
String 是java中的字符串,它继承于CharSequence。 也就是说String也是CharSequence类型。
CharSequence是一个接口,它只包括length(), charAt(int index), subSequence(int start, int end)这几个API接口。除了String实现了CharSequence之外,StringBuffer和StringBuilder也实现了CharSequence接口。 也就是说,CharSequence其实也就是定义了字符串操作的接口,其他具体的实现是由String、StringBuilder、StringBuffer完成的,String、StringBuilder、StringBuffer都可以转化为CharSequence类型。
(1)String类
/**存储字符数组*/
private final char value[];
/**保存String字符串的哈希值 */
private int hash; // Default to 0
/**从这个构造方法可以看出,如果original指向的是已有的String对象,那么不会产生拷贝 */
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
/**用字符数组构造String对象,其实就是对String的value分配内存并赋值 */
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/**用StringBuffer构造String对象,保证线程安全的同时,为String的value分配内存并赋值 */
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
/**与StringBuffer类似,只不过没有同步机制,是非线程安全的 */
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
(2)StringBuilder类
public StringBuilder() {
super(16);
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
从源码中可以得知,StringBuilder的构造函数与父类有着莫大关系,那么去看下父类AbstractStringBuilder的源码。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
...
}
从源码中可以看出,StringBuilder也是用字符数组存储内容,并且在初始化时StringBuilder时,会将字符数组的长度初始化为16,当用字符串初始化StringBuilder时,会将字符数组的长度初始化为字符串长度+16.
(3)StringBuffer类
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
从源码中可以看出,StringBuffer较为常用的构造方法与StringBuilder的完全类似,其实他们两个类的代码基本上都一致,只不过StringBuffer多了个关键字synchronized,下面还会提到。
(1)String类
其实对于String类,记住String类的操作(修改)都是返回一个新的String对象就可以了,比如较为常用的subString,caocat方法等,下面以subString源码简单说明一下:
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);
}
subString还有很多重载的方法,但最后都是new String(),返回的是新的对象(和C++语言的机制不一样)。
(2)StringBuilder类
对于StringBuiler类的操作最多的就是insert,append,以append方法举例,看下StringBuilder的append如何实现。
/**以append(String str)举例,原理一样 */
public StringBuilder append(String str) {
super.append(str);
return this;
}
从源码可以看出,StringBuilder的append方法调用的是父类AbstractStringBuilder类的append方法,下面看下代码:
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
//确保容量足够存储str
ensureCapacityInternal(count + len);
//将内容拷贝到value数组中
str.getChars(0, len, value, count);
count += len;
return this;
}
比较有意思的是ensureCapacityInternal方法,让我们看下代码:
private void ensureCapacityInternal(int minimumCapacity) {
//如果发现字符数组中以存字符的数量与将要存储的字符数量之和大于整个数组的长度,那么需要增大数组容量
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
//将字符数组的容量增大至原来的两倍
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
从上述代码可以看出,对于StringBuilder的操作(修改)都是在原本字符数组的基础上操作,当发现数组容量不足时,会自动增大数组容量以满足要求。
(3)StringBuffer类
StringBuffer类与StringBuilder类基本一致,具体体现在哪呢?我们同样以append方法为例,看看StringBuffer类中append的实现方法。
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
从代码中可以看出,StringBuffer调用的仍然是父类AbstractStringBuilder类的append方法,,与StringBuilder不同之处,就在于前面加了symchronized关键字,这也就表示着StringBuffer是线程安全的。
String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。String类其实是通过char数组来保存字符串的。“对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”
StringBuffer类的成员方法前面多了一个关键字:synchronized,StringBuffer是线程安全的
对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如”I”+”love”+”java”; 的字符串相加,在编译期间便被优化成了”Ilovejava”。这个可以用javap -c命令反编译生成的class文件进行验证。对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。
String、StringBuilder、StringBuffer三者的执行效率:StringBuilder > StringBuffer > String,当然这个是相对的,不一定在所有情况下都是这样。当字符串相加操作或者改动较少的情况下,建议使用 String str=”hello”这种形式;当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。