zoukankan      html  css  js  c++  java
  • 从为什么String=String谈到StringBuilder和StringBuffer

    前言

    有这么一段代码:

    复制代码
    1 public class TestMain
    2 {
    3     public static void main(String[] args)
    4     {
    5         String str0 = "123";
    6         String str1 = "123";
    7         System.out.println(str0 == str1);
    8     }
    9 }
    复制代码

    运行结果是什么?答案当然是true。对,答案的确是true,但是这是为什么 呢?很多人第一反应肯定是两个"123"的String当然相等啊,这还要想。但是"=="在Java比较的根本不是两个对象的值,而是比较两个对象的引 用是否相等,和两个String都是"123"又有什么关系呢?或者我们把程序修改一下

    复制代码
    1 public class TestMain
    2 {
    3     public static void main(String[] args)
    4     {
    5         String str2 = new String("234");
    6         String str3 = new String("234");
    7         System.out.println(str2 == str3);
    8     }
    9 }
    复制代码

    这时候运行结果就是false了,因为尽管两个String对象都是"234",但是str2和str3是两个不同的引用,所以返回的false。OK,围绕第一段代码返回true,第二段代码返回false,开始文章的内容。

    为什么String=String?

    在JVM中有一块区域叫做常量池,关于常量池,我在写虚拟机的时候有专门提到http://www.cnblogs.com/xrq730/p/4827590.html。 常量池中的数据是那些在编译期间被确定,并被保存在已编译的.class文件中的一些数据。除了包含所有的8种基本数据类型(char、byte、 short、int、long、float、double、boolean)外,还有String及其数组的常量值,另外还有一些以文本形式出现的符号引 用。

    Java栈的特点是存取速度快(比堆块),但是空间小,数据生命周期固定,只能生存到方法结束。我们定义的boolean b = true、char c = 'c'、String str = “123”,这些语句,我们拆分为几部分来看:

    1、true、c、123,这些等号右边的指的是编译期间可以被确定的内容,都被维护在常量池中

    2、b、c、str这些等号左边第一个出现的指的是一个引用,引用的内容是等号右边数据在常量池中的地址

    3、boolean、char、String这些是引用的类型

    栈有一个特点,就是数据共享。 回到我们第一个例子,第五行String str0 = "123",编译的时候,在常量池中创建了一个常量"123",然后走第六行String str1 = "123",先去常量池中找有没有这个"123",发现有,str1也指向常量池中的"123",所以第七行的str0 == str1返回的是true,因为str0和str1指向的都是常量池中的"123"这个字符串的地址。当然如果String str1 = "234",就又不一样了,因为常量池中没有"234",所以会在常量池中创建一个"234",然后str1代表的是这个"234"的地址。分析了 String,其实其他基本数据类型也都是一样的:先看常量池中有没有要创建的数据,有就返回数据的地址,没有就创建一个

    第二个例 子呢?Java虚拟机的解释器每遇到一个new关键字,都会在堆内存中开辟一块内存来存放一个String对象,所以str2、str3指向的堆内存中虽 然存储的是相等的"234",但是由于是两块不同的堆内存,因此str2 == str3返回的仍然是false,网上找到一张图表示一下这个概念:

    为什么要使用StringBuilder和StringBuffer拼接字符串?

    大家在开发中一定有一个原则是"利用StringBuilder和StringBuffer拼接字符串",但是为什么呢?用一段代码来分析一下:

    复制代码
     1 public class TestMain
     2 {
     3     public static void main(String[] args)
     4     {
     5         String str = "111";
     6         str += "222";
     7         str += "111";
     8         str += "444";
     9         System.out.println(str);
    10     }
    11 }
    复制代码

    分析一下代码运行过程:

    1、第5行,去常量池中找"111",没找到,常量池中创建一个,str指向常量池中的“111”

    2、第6行,去常量池中找拼接后的"111222",没找到,常量池中创建一个,str指向常量池中的“111222”

    3、第7行,去常量池中找拼接后的"111222111",没找到,常量池中创建一个,str指向常量池中的“111222111”

    4、第8行,去常量池中找拼接后的"111222111444",没找到,常量池中创建一个,str指向常量池中的“111222111444”

    看到了吧,这就是String拼接字符串的坏处。我们最终只需要"111222111444",但是却给我们在常量池中创建了这么多中间常量"111"、"111222"、"111222111"。这意味着,用String拼接字符串可能会导致常量池中产生大量的无用的常量(万一创建的常量池中本身就有呢),消耗内存空间,虽然一两个String不算什么,但是积少成多,代码中各个地方都使用String拼接字符串,那就是极大的消耗

    同时,这就是要使用StringBuilder和StringBuffer的原因,以StringBuilder为例:

    复制代码
     1 public class TestMain
     2 {
     3     public static void main(String[] args)
     4     {
     5         StringBuilder sb = new StringBuilder("111");
     6         sb.append("222");
     7         sb.append("111");
     8         sb.append("111");
     9         sb.append("444");
    10         System.out.println(sb.toString());
    11     }
    12 }
    复制代码

    StringBuffer和StringBuilder原理一样,无非是在底层维 护了一个char数组,每次append的时候就往char数组里面放字符而已,在最终sb.toString()的时候,用一个new String()方法把char数组里面的内容都转成String,这样,常量池中创建了一个最终产生的String,在需要对字符串进行拼接尤其是大量 拼接的地方,大量地节省常量池的空间。

    StringBuffer和StringBuilder用法一模一样,唯一的区别只是StringBuffer是线程安全的,它对所有方法都做了同步,StringBuilder是线程非安全的,所以在不涉及线程安全的场景,比如方法内部,尽量使用StringBuilder,避免同步带来的消耗。另外,StringBuffer和StringBuilder还有一个优化点,如果可以估计到要拼接的字符串的长度的话,尽量利用构造函数指定他们的长度,避免数组扩容带来的消耗,这个之后写List的时候会专门写到。

    小心陷阱

    虽然说不要用"+"拼接字符串,因为会产生大量的无用常量,但也不是不可以,比如可以使用以下的方式:

    复制代码
    1 public class TestMain
    2 {
    3     public static void main(String[] args)
    4     {
    5         String str = "111" + "222" + "333" + "444";
    6         System.out.println(str);
    7     }
    8 }
    复制代码

    这么做,实际上编译的时候,Java会把"111"、"222"、"333"、"444"都拼接直接拼接在一起当作一整个字符串来看,然后给str,实际上这样,在常量池中只有一个"111222333444",并不会产生无用的常量。不过这么写得很少,主要原因有两点:

    1、例子比较简单,但实际上大量的“+”会导致代码的可读性非常差

    2、待拼接的内容可能从各种地方获取,比如调用接口、从.properties文件中、从.xml文件中,这样的场景下尽管用多个“+”的方式也不是不可以,但会造成不方便

  • 相关阅读:
    原生和jQuery的ajax用法
    sublime常用快捷键
    用filter:grayscale将图片过滤成灰色
    Docker搭建Zookeeper集群问题总结
    Linux下jdk环境配置
    window MySQL解压缩版部署及配置
    Windows下Nginx的配置及配置文件部分介绍
    JS 特性:可选链(?.)
    509道Java面试题解析:2020年最新Java面试题
    阿里面试题BIO和NIO数量问题附答案和代码
  • 原文地址:https://www.cnblogs.com/szlbm/p/5504619.html
Copyright © 2011-2022 走看看