java面试题----String、StringBuffer、StringBudder区别

面试题1 - 什么情况下用+运算符进行字符串连接比调用StringBuffer/StringBuilder对象的append方法连接字符串性能更好?

面试题2 - 请说出下面程序的输出。

class StringEqualTest {

    public static void main(String[] args) {
        String s1 = "Programming";
        String s2 = new String("Programming");
        String s3 = "Program";
        String s4 = "ming";
        String s5 = "Program" + "ming";
        String s6 = s3 + s4;
        System.out.println(s1 == s2);
        System.out.println(s1 == s5);
        System.out.println(s1 == s6);
        System.out.println(s1 == s6.intern());
        System.out.println(s2 == s2.intern());
    }
}
返回值:
false
true
false
true
false


  今天在看面试题的时候注意到一个String的intern()方法,平常没用过,只是见过这个方法,也没去仔细看过这个方法。所以今天看了一下。个人觉得给String类中加入这个方法可能是为了提升一点点性能,因为从常量池取数据比从堆里面去数据要快一些。(个人感觉)

  API上的那几句关于这个方法,其实总结一句就是调用这个方法之后把字符串对象加入常量池中,常量池我们都知道他是存在于方法区的,他是方法区的一部分,而方法区是线程共享的,所以常量池也就是线程共享的,但是他并不是线程不安全的,他其实是线程安全的,他仅仅是让有相同值的引用指向同一个位置而已,如果引用值变化了,但是常量池中没有新的值,那么就会新开辟一个常量结果来交给新的引用,而并非像线程不同步那样,针对同一个对象,new出来的字符串和直接赋值给变量的字符串存放的位置是不一样的,前者是在堆里面,而后者在常量池里面,另外,在做字符串拼接操作,也就是字符串相"+"的时候,得出的结果是存在在常量池或者堆里面,这个是根据情况不同不一定的,我写了几行代码测试了一下。

  先上结果:

    1.直接定义字符串变量的时候赋值,如果表达式右边只有字符串常量,那么就是把变量存放在常量池里面。

    2.new出来的字符串是存放在堆里面。

    3.对字符串进行拼接操作,也就是做"+"运算的时候,分2中情况:

      i.表达式右边是纯字符串常量,那么存放在栈里面。

      ii.表达式右边如果存在字符串引用,也就是字符串对象的句柄,那么就存放在堆里面。

复制代码
    String str1 = "aaa";
        String str2 = "bbb";
        String str3 = "aaabbb";
        String str4 = str1 + str2;
        String str5 = "aaa" + "bbb";
        System.out.println(str3 == str4); // false
        System.out.println(str3 == str4.intern()); // true
        System.out.println(str3 == str5);// true
复制代码

  结果:str1、str2、str3、str5都是存在于常量池,str4由于表达式右半边有引用类型,所以str4存在于堆内存,而str5表达式右边没有引用类型,是纯字符串常量,就存放在了常量池里面。其实Integer这种包装类型的-128 ~ +127也是存放在常量池里面,比如Integer i1 = 10;Integer i2 = 10; i1 == i2结果是true,估计也是为了性能优化。

 

 

学习了String类和StringBuffer类,现在从三分面来总结一下String、StringBuffer、StringBudder三者的区别:

  • 是否可变:

String:底层利用字符数组保存字符串常量,是不可变的,因为String类的原码中有:private final char value[];因为有final修饰,所以String类的对象是不可改变的。所以每次修String对象的值时,实际上是生成了一个新的对象,而指针指向了新的String对象;

StringBufferStringBudder底层是利用字符数组保存字符串变量的,在jdk1.7中它们都继承了AbstractStringBuilder类,而在AbstractStringBuilder类中有char[] value;,所以这两者对象是可变的;

  • 执行速度:一般情况StringBudder > StringBuffer > String

    String:因为String对象是不可变的,所以每次修String对象的时候,实际上是生成了一个新的对象,而指针指向了新的String对象;所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。

    StringBuffer:而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。
    但是有特殊情况: String str = “abc” + “def”;StringBuffer Sb = new StringBuilder(“abc”).append(“ def”);这时这种情况下:String的速度就要比StringBuffer的速度要快,因为这时jvm认为String str = “abc” + “def”;其实是:String str = “abcdef”;所以速度当然很快,这里说的是当String保存的字符串中有其他String的字符串时,速度就会变慢。

  • 是否线程安全:

    String: String中的对象是不可变的,也就可以理解为常量,显然线程安全。

    StringBuffer:是线程安全的,因为对方法加了同步锁或者对调用的方法加了同步锁,部分方法原码如下:

 public synchronized int length() {
        return count;
    }

    public synchronized int capacity() {
        return value.length;
    }


    public synchronized void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > value.length) {
            expandCapacity(minimumCapacity);
        }
    }

StringBudder:并没有对方法进行加同步锁,所以是非线程安全的。部分方法原码如下:

 private StringBuilder append(StringBuilder sb) {
        if (sb == null)
            return append("null");
        int len = sb.length();
        int newcount = count + len;
        if (newcount > value.length)
            expandCapacity(newcount);
        sb.getChars(0, len, value, count);
        count = newcount;
        return this;
    }
      public StringBuilder delete(int start, int end) {
        super.delete(start, end);
        return this;
    }
  • 对三者的使用意见:

1.如果要操作少量的数据用: String
2.单线程操作字符串缓冲区 下操作大量数据 :StringBuilder
3.多线程操作字符串缓冲区 下操作大量数据 : StringBuffer

 

 

posted @ 2018-07-17 22:47  后知、后觉  阅读(1830)  评论(0编辑  收藏  举报