String :
String创建后不可变:
源码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash; // String对象的哈希码被频繁的使用,字符串不变性保证了hash码的唯一性
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
String 代码,内部成员 都有final修饰,但是可以通过反射,获取成员成员变量value,并设置setaccessible(true)跳过安全检查来修改。
final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。它所指向的对象其他的变化可以。
Java在运行时也保存了一个字符串池(String pool),这使得String成为了一个特别的类。只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字
因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串符串。
String创建内存对象:
实质为char[]数组,一个char2个字节(unicode编码),
String s1 = "ABC";
String s2 = "ABC";
String s3 = "D";
String s4 = "ABCD";
String s5 =s1+s3 ;
s1==s2 结果为(true)
s1和s2指向堆中地址为一个, JVM会先到常量池里查找,如果有的话返回常量池里的这个实例的引用,否则的话创建一个新实例并置入常量池里。所以,当我们在使用诸如String str = "abc";的格式定义对象时,总是想当然地认为,创建了String类的对象str。对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。
String的+拼接问题:
s4==s5 结果为(false)
s4=="ABC"+"D"; 结果为(true)
首先以最左边的字符串为参数创建StringBuilder对象,然后依次对右边进行append操作,最后将StringBuilder对象通过toString()方法转换成String对象。
两个引用用+拼接,是不会产生新的对象在常量池中,只有使用引号包含文本的方式创建的String对象, 这些对象之间使用"+"连接产生的新对象才会被加入字符串池中,但是如果对象被final修饰过,其本质和双引号的文本一样,
String.intern():调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用, 注意是常量池中的对象,不是堆中的对象
//可以用intern()方法检测一个字符串是否在jvm的常量池中
public static boolean inPool(String s) {
String s2 = s.intern();
if (s2 == s) return true;
return false;
}
equals和==:equals方法不能作用于基本数据类型的变量。如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;而String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) { // equals源码 比完长度 比数组
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
StringBuilder:
每次链接字符串,都构建一个新的String对象,即耗又浪费空间。使用stringbuilder类就可以避免这个问题的发生
源码:
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence { static final long serialVersionUID = 4383685877147921099L; public StringBuilder() { super(16);//默认长度为16 }
//new StringBuilder("123"); 创建一个长度为3+16长度的空数组,然后append
public StringBuilder(String str) { super(str.length() + 16); append(str); }
继承自AbstractStringBuilder(这个东西有好多,insert,delete,substring,setlength等可以用)
abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; //底层也是char[]数组 int count; //表示数组中已经使用的字符个数 int 默认为0public AbstractStringBuilder append(String str) { if (str == null) return appendNull();//为null拼接“null” int len = str.length(); /** *count + len计算出数组需要的最少长度 *如果字符数组的长度小于需要的长度,则调用newCapacity计算出需要的数组长度,计算逻辑为,先把原来的数组长度乘2然后加2,如果这个之后还是不够,就用最小的长度,但是如果最小的长度超过了int的最大数值,就报错OutOfMemoryError内存溢出* 然后把之前的数组放到新的里面,然后再添加要添加的。
**/ ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }
private AbstractStringBuilder appendNull() { int c = count; ensureCapacityInternal(c + 4); final char[] value = this.value; value[c++] = 'n'; value[c++] = 'u'; value[c++] = 'l'; value[c++] = 'l'; count = c; return this; }
private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity));//创建新的足够长度数组,然后把原来的值放进去 } }private int newCapacity(int minCapacity) { // overflow-conscious code int newCapacity = (value.length << 1) + 2;//<<1,左移一位,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方 if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; }
private int hugeCapacity(int minCapacity) { if (Integer.MAX_VALUE - minCapacity < 0) { // overflow throw new OutOfMemoryError(); } return (minCapacity > MAX_ARRAY_SIZE) ? minCapacity : MAX_ARRAY_SIZE; }
总结一下就是:底层也是char数组,默认长度为16,因为不是final的,也没加锁所以线程不安全,但是速度也会比较快,相比String也省内存,创建的字符串第一次长度为16+字符串的长度。之后append的扩容,是默认乘2然后再加2,如果长度还是不够就用原StringBuilder长度加上要拼接的字符串长度得到最小长度,用这个最小长度(interger得最大长度为2147483647,最小长度不能超过这个,不然会报错内存溢出)。创建出足够长得数组之后把之前得字符串放进去,然后再放进去要拼接得。
StringBuffer:
和上面的StringBuilder差不多,继承和方法啥的都一样,但是方法都被synchronized修饰所以线程安全。