String,StringBuilder,StringBuffer面试总结

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 默认为0​​​​​​​
public 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修饰所以线程安全。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值