一、java中String、StringBuffer和StringBuilder的区别
1.1 简略介绍
java中用于解决字符串罕用的有三个类:
1、java.lang.String
2、java.lang.StringBuffer
3、java.lang.StrungBuilder
三者共同之处:都是final类,不容许被继承,次要是从性能和安全性上思考的,因为这几个类都是常常被应用着,且思考到避免其中的参数被参数批改影响到其余的利用。

StringBuffer是线程平安,能够不须要额定的同步用于多线程中;
StringBuilder是非同步,运行于多线程中就须要应用着独自同步解决,然而速度就比StringBuffer快多了;
StringBuffer与StringBuilder两者共同之处:能够通过append、insert、deleteCharAt等进行字符串的操作。用法上一样。
String实现了三个接口:Serializable、Comparable、CarSequence
StringBuilder只实现了两个接口Serializable、CharSequence,相比之下String的实例能够通过compareTo办法进行比拟,其余两个不能够。
String是一个字符串常量,属于援用类型,字符串一旦创立就不可扭转,对于String字符串类型一旦产生扭转,就会返回新的字符串对象。
在这里插入图片形容从源码中咱们能够看出,String字符串底层fina润饰的char[]数组,一个char字符在内存中占两个字节(Unicode编码)value[]:用于贮存String的内容。
咱们来看一个例子:

String s = "Hello World";

   创立字符串常量时,JVM会首先查看在字符串常量池,如果在字符串常量池中存在了该字符串 “Hello World”,此时就会将该字符串对象的地址值赋值给援用 s(s寄存在栈中) .如果字符串不在常量池中,就会在常量池中创立字符串,而后将字符串对象的地址值交给s.值得注意的是:这里只创立了1个或0个字符串对象(Hello World已存在常量池就不会创立了)。
再看下一个例子:
String s = new String("Hello Java");

  创立字符串常量时,JVM会首先查看字符串常量池,如果字符串"Hello Java"曾经存在常量池中,间接复制堆中这个对象的正本,而后将堆中的地址值赋给援用s,不会在字符串常量池中创建对象.如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,而后在堆中复制该对象的正本,并将对象的地址值交给s。 值得注意的是:这里会创立两个字符串对象,别离在常量池中与堆中(不思考对象已存在的状况)。
这三个类之间的区别次要是在两个方面,即运行速度和线程平安这两方面。
  1、首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String
  String最慢的起因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创立之后该对象是不可更改的,但后两者的对象是变量,是能够更改的。以上面一段代码为例:

String str="abc";
System.out.println(str);
str=str+"de";
System.out.println(str);


 运行这段代码会发现先输入“abc”,而后又输入“abcde”,如同是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样解决的,首先创立一个String对象str,并把“abc”赋值给str,而后在第三行中,其实JVM又创立了一个新的对象也名为str,而后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是后面说的String对象一旦创立之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个一直创立新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是间接对该对象进行更改,而不进行创立和回收的操作,所以速度要比String快很多。
另外,有时候咱们会这样对字符串进行赋值:

String str="abc"+"de";
StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");
System.out.println(str);
System.out.println(stringBuilder.toString());
stringBuilder.deleteCharAt(stringBuilder.length()-1);//删除指定地位的字符


这样输入后果也是“abcde”和“abcde”,然而String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和String str="abcde";是齐全一样的,所以会很快,而如果写成上面这种模式

String str1="abc";
String str2="de";
String str=str1+str2;

那么JVM就会像下面说的那样,一直的创立、回收对象来进行这个操作了。速度就会很慢。

public static void main(String[] args) {

    long a=new Date().getTime();    String cc="";    int n=10000;    for (int i = 0; i < n; i++) {        cc+="."+i;    }    System.out.println("String应用的工夫"+(System.currentTimeMillis()-a)/1000.0+"s");    long s1=System.currentTimeMillis();    StringBuilder sb=new StringBuilder();    for (int i = 0; i < n; i++) {        sb.append("."+i);    }    System.out.println("StringBuilder应用的工夫"+(System.currentTimeMillis()-s1)/1000.0+"s");    long s2=System.currentTimeMillis();    StringBuffer sbf=new StringBuffer();    for (int i = 0; i < n; i++) {        sbf.append("."+i);    }    System.out.println("StringBuffer应用的工夫"+(System.currentTimeMillis()-s2)/1000.0+"s");

}

1.2 再来说线程平安
在线程平安上,StringBuilder是线程不平安的,而StringBuffer是线程平安的。
如果一个StringBuffer对象在字符串缓冲区被多个线程应用时,StringBuffer中很多办法能够带有synchronized关键字,所以能够保障线程是平安的,但StringBuilder的办法则没有该关键字,所以不能保障线程平安,有可能会呈现一些谬误的操作。所以如果要进行的操作是多线程的,那么就要应用StringBuffer,然而在单线程的状况下,还是倡议应用速度比拟快的StringBuilder。
(一个线程拜访一个对象中的synchronized(this)同步代码块时,其余试图拜访该对象的线程将被阻塞)

1.3 总结一下
String:实用于大量的字符串操作的状况。
StringBuilder:实用于单线程下在字符缓冲区进行大量操作的状况。
StringBuffer:实用多线程下在字符缓冲区进行大量操作的状况。

二、字符串常量池介绍
2.1 简略介绍
Java中的字符串常量池(String Pool)是存储在Java堆内存中的字符串池。咱们晓得String是java中比拟非凡的类,咱们能够应用new运算符创立String对象,也能够用双引号(”“)创立字串对象。

2.2 Java中的字符串常量池
下图,分明地解释了如何在Java堆内存中保护字符串常量池,以及当咱们应用不同的形式创立字符串时在堆内存中如何寄存。


之所以有字符串常量池,是因为String在Java中是不可变(immutable)的,它是String interning概念的实现。字符串常量池也是亨元模式(Flyweight)的实例。

字符串常量池有助于为Java运行时节俭大量空间,尽管创立字符串时须要更多的工夫。

当咱们应用双引号创立一个字符串时,首先在字符串常量池中查找是否有雷同值的字符串,如果发现则返回其援用,否则它会在池中创立一个新的字符串,而后返回新字符串的援用。

如果应用new运算符创立字符串,则会强制String类在堆空间中创立一个新的String对象。咱们能够应用intern()办法将其放入字符串常量池或从字符串常量池中查找具备雷同的值字符串对象并返回其援用

如下是上文图中无关字符串常量池的程序实现:

package cn.gavin.basic;public class StringTest {    public static void main(String[] args) {//        String s0 = "hello";//        String s1 = "hello";//        String s2 = "he" + "llo";//        System.out.println( s0 == s1 ); // true//        System.out.println( s0 == s2 ); // true//        System.out.println( s0.equals(s1) ); // true        System.out.println("-----------------------");        String s1 = "Cat";        String s2 = "Cat";        String s3 = new String("Cat"); // 创立1或2个对象,至多创立1个对象/这里只创立1个,因为常量池有了        String s4 = new String("Cat");// 创立1个        String s5= "Ca";        String s6= s5+"t";        String s7= "Ca"+"t";        System.out.println("s1 == s2 :"+(s1==s2));        System.out.println("s1 == s3 :"+(s1==s3));        System.out.println("s3 == s4 :"+(s3==s4));        System.out.println("s1 == s6 :"+(s1==s6));        System.out.println("s1 == s7 :"+(s1==s7));        System.out.println("-----------------------");        // String 有重写equals 比拟的是对象,不是地址        System.out.println("equals s1-s2 :"+s1.equals(s2));        System.out.println("equals s1-s3 :"+s1.equals(s3));        System.out.println("equals s3-s4 :"+s3.equals(s4));        System.out.println("equals s1-s6 :"+s1.equals(s6));        System.out.println("equals s1-s7 :"+s1.equals(s7));    }}

上述程序的输入是
s1 == s2 :true
s1 == s3 :false
s3 == s4 :false
s1 == s6 :false
s1 == s7 :true

equals s1-s2 :true
equals s1-s3 :true
equals s3-s4 :true
equals s1-s6 :true
equals s1-s7 :true

有时在java面试中,你会被问到无关字符串常量池的问题。例如,在上面的语句中创立了多少个字符串对象:
String str = new String("Cat");
在下面的语句中,可能创立1或2个字符串对象。如果池中曾经有一个字符串“Cat”,那么池中只会创立一个字符串“str”。如果池中没有字符串字面量“Cat”,那么它将首先在池中创立,而后在堆空间中创立,因而将创立总共2个字符串对象。

2.3 字符串常量池简略理解
字符串的调配,和其余的对象调配一样,消耗昂扬的工夫与空间代价。JVM为了进步性能和缩小内存开销,在实例化字符串常量的时候进行了一些优化。为 了缩小在JVM中创立的字符串的数量,字符串类保护了一个字符串池,每当代码创立字符串常量时,JVM会首先查看字符串常量池。如果字符串曾经存在池中, 就返回池中的实例援用。如果字符串不在池中,就会实例化一个字符串并放到池中。Java可能进行这样的优化是因为字符串是不可变的,能够不必放心数据抵触 进行共享。

2.4 Java字符串常量池是什么?为什么要有这种常量池?
1、存储在Java堆内存中的字符串池
2、为了让数据不抵触进行共享等


有人会问一个问题:
String A = "ABC";
String B = new String("ABC");
这两者有啥区别?

间接赋值的说法是字符串间接量
当程序第一次应用某个字符串间接量时,Java会应用常量池(constant pool) 来缓存该字符串间接量
如果程序前面再次用到该字符串间接量时,Java会间接应用常量池中存在的字符串间接量

比拟办法:
==:比拟援用类型比拟的是地址值是否雷同
equals:比拟援用类型默认也是比拟地址值是否雷同,留神:String类重写了equals()办法,比拟的是内容是否雷同。

常量池:
指的是在编译期被确定,并被保留在已编译的.class文件中的一些数据,包含类、办法、接口中的常量,也包含字符串间接量

String s0 = "hello";
String s1 = "hello";
String s2 = "he" + "llo";
System.out.println( s0 == s1 ); // true
System.out.println( s0 == s2 ); // true
System.out.println( s0.equals(s1) ); // true

这样上来,s0 == s1== s2 会始终相等上来,
(a) ==的判断,
(b) equals()的判断;
都相等,因为他们的地址都相等,因而只在常量池中有一份内存空间,地址全副雷同;

String B = new String("ABC");
1
在内存堆中结构一个String对象,将新结构进去的String对象的援用赋给str。 因而 只有是new String(),则,栈中的地址都是指向最新的new进去的堆中的地址,
(a)“”==“” 是判断地址的,当然不雷同;
(b)至于equals,String类型重写了 equals()办法,判断值是否相等,显著相等,因而 equals 是相等的;