2.java- 再谈字符串类 String Stringbuffer,StringBuilder

1.String类能被继承吗,为什么?

          不能。类被final 修饰,不能被继承。

2. String,Stringbuffer,StringBuilder的区别?

       2.1我想从三个角度讲下区别

        2.11 是否可变?  String 对象是不可变对象,每次进行字符串变量修改的时候都会新创建一个对象,  而StringBuffer StringBuilder 可变的字符串对象, 在原有的数据上进行修改。(反应在内存堆中的变化)

       2.12 是否线程安全?

         只有StringBuffer 线程安全的字符串对象,在方法上加了Synchronized  

       2.13 相加效率?

           StringBuilder > Stringbuffer > String

 

      2.2 使用场景的考虑

              每次改变String的值,都要新创建一个对象,再将引用指向该对象,浪费内存空间,内存中的无用引用多了以后,jvm的GC就开始工作,系统就会慢了。

          2.21.单线程下在字符串缓存区进行大量操作。 StringBuilder

          2.22 多线程下在字符串缓存区进行大量操作。 StringBuffer  【实际开发中当中 基本不用,多线程下就考虑用锁,而不是这货】

          2. 23 String 少量的字符串操作

     2.3 谈谈String在内存中JVM 分布

           这里写图片描述

         这里写图片描述   

 

这里写图片描述

 

 

3.介绍String.intern()

JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。 
这里写图片描述

 

                  jdk1.7 调用的时候,常量池未有这个常量,把对应这个常量的引用拷贝到常量池 ,并返回这个指向 (堆) 的引用

                  jdk1.7之前的:(区别低版 本,拷贝副本到常量池,再返回 (常量池) 引用)

 

4.其他的一些面试题

1. 下面这段代码的输出结果是什么?

  String a = "hello2";   String b = "hello" + 2;   System.out.println((a == b));

  输出结果为:true。原因很简单,"hello"+2在编译期间就已经被优化成"hello2",因此在运行期间,变量a和变量b指向的是同一个对象。

2.下面这段代码的输出结果是什么?

  String a = "hello2";    String b = "hello";       String c = b + 2;       System.out.println((a == c));

  输出结果为:false。由于有符号引用的存在,所以  String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象。javap -c得到的内容:

3.下面这段代码的输出结果是什么?

  String a = "hello2";     final String b = "hello";       String c = b + 2;       System.out.println((a == c));

  输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = "hello" + 2; 下图是javap -c的内容:

 

4.下面这段代码输出结果为:

1

2

3

4

5

6

7

8

9

10

11

12

public class Main {

    public static void main(String[] args) {

        String a = "hello2";

        final String b = getHello();

        String c = b + 2;

        System.out.println((a == c));

    }

     

    public static String getHello() {

        return "hello";

    }

}

  输出结果为false。这里面虽然将b用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此a和c指向的不是同一个对象

 

5.下面这段代码的输出结果是什么?

1

2

3

4

5

6

7

8

9

10

11

12

13

public class Main {

    public static void main(String[] args) {

        String a = "hello";

        String b =  new String("hello");

        String c =  new String("hello");

        String d = b.intern();

         

        System.out.println(a==b);

        System.out.println(b==c);

        System.out.println(b==d);

        System.out.println(a==d);

    }

}

  输出结果为(JDK版本 JDK6):

  

 

 

 

 

作者:wuxinliulei
链接:https://www.zhihu.com/question/29495158/answer/72534524
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

String 字符串常量 since JDK1.0
java.lang.String public final class String


StringBuffer 字符串变量(线程安全) since JDK1.0
java.lang.StringBuffer public final class StringBuffer


StringBuilder 字符串变量(非线程安全)since JDK1.5
java.lang.StringBuilder public final class StringBuilder

全部是lang包下的类,全部是final的类,不能被继承。

------------------------------------------------------------------

简要的说,String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。(曾经有朋友遇到过打日志使用+号的字符串导致频繁Full GC的)

而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。
所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:

String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);


你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个
String S1 = “This is only a” + “ simple” + “test”; 其实就是:
String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:

String S2 = "This is only a";
String S3 = "simple";
String S4 = "test";
String S1 = S2 +S3 + S4;


这时候 JVM 会规规矩矩的按照原来的方式去做

在大部分情况下

 StringBuffer > String


StringBuffer
Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。
在大部分情况下 StringBuilder > StringBuffer

java.lang.StringBuilder
java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

 

StringBuffer:
是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象
它只能通过构造函数来建立,
StringBuffer sb = new StringBuffer();
note:不能通过付值符号对他进行付值. 
sb = "welcome to here!";//error
对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.向StringBuffer
中付值的时候可以通过它的append方法.
sb.append("hello");
并且由于String 对象是不可变对象,每次操作Sting 都会重新建立新的对象来保存新的值.
这样原来的对象就没用了,就要被垃圾回收.这也是要影响性能的.

 

StringBuilder:

是一个可变对象,和StringBuffer相比,不是线程安全的,一般用在单个线程操作的时候(这种情况很普遍,所以一般优先选用StringBuilder),速度比StringBuffe快很多。

字符串连接操作中StringBuffer的效率要比String高:

String str = new String("welcome to ");

str += "here";

的处理步骤实际上是通过建立一个StringBuffer,让侯调用append(),最后

再将StringBuffer toSting();

这样的话String的连接操作就比StringBuffer多出了一些附加操作,当然效率上要打折扣.

看看以下代码:
将26个英文字母重复加了5000次,

 

String tempstr = "abcdefghijklmnopqrstuvwxyz"; 
int times = 5000; 
long lstart1 = System.currentTimeMillis(); 
String str = ""; 
for (int i = 0; i < times; i++) 
{ 
	str += tempstr; 
} 
long lend1 = System.currentTimeMillis(); 
long time = (lend1 - lstart1); 
System.out.println(time);


可惜我的计算机不是超级计算机,得到的结果每次不一定一样一般为 46687左右。
也就是46秒。
我们再看看以下代码

String tempstr = "abcdefghijklmnopqrstuvwxyz"; 
int times = 5000; 
long lstart2 = System.currentTimeMillis(); 
StringBuffer sb = new StringBuffer(); 
for (int i = 0; i < times; i++) 
{ 
    sb.append(tempstr); 
} 
long lend2 = System.currentTimeMillis(); 
long time2 = (lend2 - lstart2); 
System.out.println(time2); 


得到的结果为 16 有时还是 0
所以结论很明显,StringBuffer 的速度几乎是String 上万倍。当然这个数据不是很准确。因为循环的次数在100000次的时候,差异更大。不信你试试。

根据上面所说:

str += "here";
的处理步骤实际上是通过建立一个StringBuffer,然后调用append(),最后
再将StringBuffer toSting();

所以str += "here";可以等同于

StringBuffer sb = new StringBuffer(str);
sb.append("here");
str = sb.toString();

所以上面直接利用"+"来连接String的代码可以基本等同于以下代码

  String tempstr = "abcdefghijklmnopqrstuvwxyz"; 
  int times = 5000; 
  long lstart2 = System.currentTimeMillis(); 
  String str = ""; 
  for (int i = 0; i < times; i++) { 
         StringBuffer sb = new StringBuffer(str); 
         sb.append(tempstr); 
         str = sb.toString(); 
   } 
   long lend2 = System.currentTimeMillis(); 
   long time2 = (lend2 - lstart2); 
        System.out.println(time2);


平均执行时间为46922左右,也就是46秒。

总结: 如果在程序中需要对字符串进行频繁的修改连接操作的话.使用StringBuffer性能会更好

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章