java 基础中字符串用到最多的就是String,StringBuffer和StringBuilder,这三个也是面试中最常问到的,三者之间的异同,其实实际工作中,我们大致知道使用场景,但是要用理论话的语言去总结,总是比较麻烦。
1.String
1.1继承实现关系
1.2 源码分析
1> final 修饰的类无法被继承;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
2> String的数据存放在final修饰的char数组中,所以String对象初始化之后值不能改
/** The value is used for character storage. */
private final char value[];
3>String 值比较,总共两种方式,一种是继承了Comparable接口,实现compareTo()方法通过逐个字符串比较;
另一种方法是,compareToIgnoreCase() 方法,通过静态成员类,实现Comparator方法,逐个char变大写比较,变小写比较;
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
//。。。。。。
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
//。。。。。
}
4>字符串之间的关联,String提供了静态的方法,可以将多个字符串通过delimiter进行拼接;
public static String join(CharSequence delimiter, CharSequence... elements) {
}
5>字符串的trim()方法,对字符串双端去空
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
2.AbstractStringBuilder
2.1 继承实现关系
2.2源码分析
1>初始长度设置,通过构造方法:数据存储在非fianl的char数组中,通过构造函数传参初始化;
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
2>.在append的时候,char[]数组进行扩容,一般是现有值长度的2倍+2
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));//复制生成新的char[]数组
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;//容量编程 现有长度的2倍+2
if (newCapacity - minCapacity < 0) { //扩容后还是小于最小长度时,将最小长度赋值给扩容长度
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
3.StringBuilder
1.源码分析
1> final修饰的类,不可以继承
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
2> 默认初始长度为16
public StringBuilder() {
super(16);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
3>转成字符串toString()方法是生成新的String对象
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
4.StringBuffer
1.源码分析
1> final修饰不可继承
public final class StringBuffer
2> toString缓存数据;
1.通过一个transient修饰的字段toStringCache,进行转字符串存储;
2.在每个值改变的操作将toStringCache设置为空;
3.在toString()方法中,如果toStringCache为空,就copy新的char[]数组赋值给toStringCache ;
4.如果StringBuffer中的数值没变动,调用toString方法的时候,直接调用toStringCache就可以了;
private transient char[] toStringCache;
@Override
public synchronized StringBuffer append(long lng) {
toStringCache = null;
super.append(lng);
return this;
}
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
通过上面的可以知道,如下:
相同点:
1.都是final类,不可以继承;
2.内部存取数据的都是char数组;
3.都实现了CharSequeue,可以对字符串进行相关操作;
不同点:
1.String 和StringBuilder,StringBuffer的区别:
- 1. String的值final 数组,赋值后不可改变;StringBuilder,StringBuffer 非final char数组,初始化后可以改变值;
- 2. String的字符拼接,每拼接一次,相当于生成一个新的字符串;StringBuilder,StringBuffer通过给内部的char数组扩容,不用生成新的对象,效率更高;
- 3. char数组初始长度都是16 ;
2.StringBuilder 和 StringBuffer 之间的区别:
- 1. StringBuffer的所有操作都加synchorinized,因此是线程安全的;
- 2. StringBuffer的toString方法,有缓存,多次toString效率更高;
- 3.StringBuilder 效率更高,但是线程不安全;
常见问题:
1.自己写一个String,或StringBuilder类可以替换原有的JDK的String么?
答:这个问题其实和String,StringBuilder没关系了,而涉及到的是JVM类加载中,双亲委派模型;
双亲委派模型,是类加载器接收到当前类的加载请求,并不会直接去加载,而是逐级向父加载器发送请求,当父加载器没有相关的类,当前子加载器才自行加载;