关于java:Java基础StringStringBuffer和StringBuilder的区别字符串常量池

4次阅读

共计 6151 个字符,预计需要花费 16 分钟才能阅读完成。

一、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 是相等的;

正文完
 0