1.了解String的内部实现
String的部分源码如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[]; // 用于存储具体值的char数组,定义为final,不可改变
privateinthash;// Default to 0
private staticfinallongserialVersionUID = -6849794470754667710L;
private staticfinal ObjectStreamField[]serialPersistentFields = new ObjectStreamField[0];
/**
* 最常用的构造函数,通过传入的String对象对其进行拷贝。
*/
public String(String original) {
this.value =original.value;
this.hash =original.hash;
}
public String(charvalue[]) {
this.value = Arrays.copyOf(value,value.length);
}
//..其他构造方法省略由上可以发现,String类通过内部维护一个final的char数组来实现字符串,即String类可以看作是对char数组的一层包装
2.关于使用“”创建一个String对象
除了使用String对象,还可以使用“”来创建一个String对象,如:
String str = new String("test");
该行代码对应的字节码:
0: new #16 // class java/lang/String
创建一个对象,并将其引用值压入栈顶。
3: dup
复制栈顶的值并将复制值压入栈顶。
4: ldc #18 // String test
将常量值从常量池中推送至栈顶
6: invokespecial #20 // Method java/lang/String."<init>":(Ljava/lang/String;)V
调用实例初始化方法
9: astore_1
将引用类型数值存入本地第一个变量中
上面字节码在执行时,会先在String常量池中查找“test”字符串,若无则创建一个新的字符串,然后通过传入该字符串作为String构造函数的参数再创建一个对象,所以上面的语句实际上创建了两个String对象,而一个是在常量池中,一个则是纯正的对象放在java堆上。
代码测试:
public class test {
public static void main(String[] args) {
String str ="test";
String str2 =new String("test");
System.out.println(str==str2);
System.out.println(str=="test");
}
}
3.关于使用字符串的连接 “+”
String str1 ="test1";
Stringstr2 ="test2";
Stringsrt3 =str1 + str2;
对应的字节码:
0: ldc #16 // String test1
2: astore_1
3: ldc #18 // String test2
5: astore_2
6: new #20 // class java/lang/StringBuilder
9: dup
10: aload_1
11: invokestatic #22 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
14: invokespecial #28 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
17: aload_2
18: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
4.了解StringBuffer的内部实现
StringBuffer继承自 AbstractStringBuilder,所以先看其部分源代码:
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
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;
}
StringBuffer部分源码:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
private transient char[] toStringCache;
static finallongserialVersionUID = 3388685877147921107L;
public StringBuffer() {
super(16);
}
public StringBuffer(intcapacity) {
super(capacity);
}
public synchronized StringBuffer append(Stringstr) {
toStringCache =null;
super.append(str);
return this;
}
public synchronized String toString() {
if (toStringCache ==null) {
toStringCache = Arrays.copyOfRange(value, 0,count);
}
return new String(toStringCache,true);
}
}
可以看出, AbstractStringBuilder类中使用一个char数组来存储字符串的值,而且没有像String类中被定义为final,可以通过append方法来改变char数组实现字符串的连接等操作(append方法中会使用native方法对char数组进行操作,效率很高)。而StringBuffer类在其基础上多了一个char数组作为缓存,将append方法设置为同步方法,然后清楚缓存后调用父类的方法进行处理,还实现了toString方法。
5.为什么String类是final的,保存具体值的char数组也是final的 (个人理解)
我们知道用“”是在常量池中创建String对象,这里面的对象可以复用,下次创建同样的String时就不用再进行额外的开销,这是java用于优化性能的方法。因为常量池中的String会在不同的地方被引用,如果其值可以被修改,那么其他调用的地方也被修改了,造成了错乱,所以String的值应该是不可变的。为了保证这个约束,String类被设计成final的,防止子类破坏这个约定。