Java基础=>String,StringBuffer与StringBuilder的区别

字符串常量池

  1. 什么是字符串常量池?
    JVM为了减少字符串对象的重复创建,其维护了一块特殊的内存,这段内存被称为字符串常量池(存储在方法区中)。

  2. 具体实现
    当代码中出现字符串时,JVM首先会对其进行检查。

    • 如果字符串常量池中存在相同内容的字符串对象,如果有,则不再创建,直接返回这个对象的地址返回
    • 如果字符串常量池中不存在相同内容的字符串对象,则创建一个新的字符串对象并放入常量池,并返回新创建的字符串的引用地址。
    • new String(“str”)时,首先也会去检查常量池是否存在“str”(存在则不创建、不存在则在常量池先创建一个),然后在堆空间再开辟一块内存区域创建字符串对象 。

概述String,StringBuffer与StringBuilder的区别

String在java编程中广泛应用,首先从源码进行分析
在这里插入图片描述
String底层是一个final类型的字符数组,所以String的值是不可变的每次对String的操作都会生成新的String对象,造成内存浪费 而StringBuffer和StringBuilder就不一样了,他们两都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder抽象类中我们可以看到
在这里插入图片描述
他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作。

接着看一下他们的继承结构以及部分源码实现
在这里插入图片描述
StringBuffer.apped()方法是线程安全的
在这里插入图片描述

StringBuilder.apped()方法线程不安全
在这里插入图片描述

从这三张图我们不难得知:

  1. StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)
  2. 由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

细说一下String

  1. String并不是基本数据类型,而是一个对象。
  2. 字符串为对象,那么在初始化之前,它的值为null,到这里就有必要提下””、null、new String()三者的区别。
  3. null 表示string还没有new ,也就是说对象的引用还没有创建,也没有分配内存空间给他
  4. 而””、new String()则说明了已经new了,只不过内部为空,但是它创建了对象的引用,是需要分配内存空间的。

java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串常量池(字符串缓冲池)。那个java的字符串缓冲池是如何工作的呢?

String a = "abc";
String b = "abc";
String c = new String("xyz");

在这里插入图片描述

String a = "abc";
  • 创建字符串的时候先查找字符串常量池有没有相同的对象,如果相同的对象就直接返回该对象的引用,如果没有相同的对象就在字符串常量池中创建该对象,然后将该对象的引用返回。对于这一步而言,缓冲池中没有abc这个字符串对象,所以首先创建一个字符串对象,然后将对象引用返回给a。
String b = "abc";
  • 这一句也是想要创建一个对象引用变量b使其指向abc这一对象。这时,首先查找字符串常量池,发现abc这个对象已经有了,这是就直接将这个对象的引用返回给b,此时a和b就共用了一个对象abc,不过不用担心,a改变了字符串不会影响b,因为字符串都是常量,一旦创建就没办法修改了,除非创建一个新的对象。
String c = new String("xyz");

String c = new String(“xyz”); JVM首先是在字符串常量池中找"xyz" 字符串,如果没有创建字符串常量,然后放到常量池中,若已存在,则不需要创建;当遇到 new 时,还会在内存(不是字符串常量池中)上创建一个新的String对象,存储"Hello",并将内存上的String对象引用地址返回。

从上边的分析可以看出,当new一个字符串时并不一定是创建了一个新的对象,有可能是与别的引用变量共同使用了同一个对象。下面看几个常见的有关字符串常量池的问题。

创建了几个对象

String a = "abc";
String b = "abc";
String c = new String("xyz");
String d = new String("xyz");
String e = "ab" + "cd";

这个程序与上边的程序比较相似,我们分比来看一下:

  1. String a = “abc”;这一句由于常量池中没有abc这个字符串对象,所以会创建一个对象;

  2. String b = “abc”;由于常量池中已经有了abc这个对象,所以不会再创建新的对象;

  3. String c = new String(“xyz”);由于常量池没有xyz这个字符串对象,所以会首先创建一个xyz的对象,然后放到常量池中,然后new的时候,在内存中(不是常量池中)又创建了一个新的字符串对象,所以一共创建了两个对象;

4、String d = new String(“xyz”);常量池中已有该字符串对象,则常量池中不再创建该对象,然后会在new的时候内存中创建一个新的字符串对象,所以只创建了一个对象;

5、String e = ”ab” + ”cd”;由于常量的值在编译的时候就被确定了。所以这一句等价于String e = ”abcd”;所以创建了一个对象;

所以创建的对象的个数分别是:1,0,2,1,1

了解了String类的工作原理,回归问题本身。
在String的工作原理中,已经提到了,new 一个String对象,是需要先在字符串常量中查找相同值或创建一个字符串常量,然后再在内存中创建一个String对象,所以 String str = new String(“xyz”); 会创建两个对象。

到底相等不相等

我们知道两个字符串对象相等的判断要用equal而不能使用==,但是学习了字符串常量池以后,应该知道为什么不能用==, 什么情况下==equal等价的>

首先,必须知道的是

  1. equal比较的是两个字符串的是否相等
  2. ==比较的是两个对象的内存地址是否相等

实例一:

public static void main(String[] args) { 
    String s1 = "money"; 
    String s2 = "money"; 
    if (s1 == s2) {
    	System.out.println("s1 == s2"); 
    } else {
    	System.out.println("s1 != s2"); 
    }
}

执行结果: s1 == s2
分析: 通过对字符串常量池的了解,我们知道s1和s2都是指向字符串常量池中的同一个对象,所以内存地址是一样的,所以用==可以判断两个字符串是否相等。

实例二:

public static void main(String[] args) { 
  	String s1 = "money"; 
    String s2 = new String("money"); 
    if (s1 == s2) {
    	System.out.println("s1 == s2"); 
    } else {
		 System.out.println("s1 != s2"); 
	}
    if (s1.equals(s2)) {
    	System.out.println("s1 equals s2"); 
    } else {
    	System.out.println("s1 not equals s2"); 
    }
} 

执行结果:
s1 != s2
s1 equals s2

分析: String s2 = new String(“money”);这一句话没有字符串常量池中创建新的对象,但是会在内存的其他位置创建一个新的对象,所以s1是指向字符串常量池的s2是指向内存的其他位置两者的内存地址不同的

实例三:

public static void main(String[] args) { 
    String s1 = "money"; 
    String s2 = new String("money"); 
    s2 = s2.intern(); 
    if (s1 == s2) {
        System.out.println("s1 == s2"); 
    }else {
        System.out.println("s1 != s2"); 
    }
    if (s1.equals(s2)) {
        System.out.println("s1 equals s2"); 
    }else {
        System.out.println("s1 not equals s2"); 
    }
}

输出结果:
s1 == s2
s1 equals s2
分析: 先来说说intern()这个方法的作用吧,这个方法的作用是返回在字符串常量池中的对象的引用,所以s2指向的也是字符串常量池中的地址,和s1是相等的。

intern()方法:返回在字符串常量池中的对象的引用

实例四:

public static void main(String[] args) { 
    String Monday = "Monday";  
    String Mon = "Mon";  
    String  day = "day";  
    System.out.println(Monday == "Mon" + "day");  
    System.out.println(Monday == "Mon" + day);  

}

输出结果:
true
false
分析: 第一个为什么等于true我们已经说过了,因为两者都是常量所以在编译阶段就已经能确定了,在第二个中,day是一个变量,所以不能提前确定他的值,所以两者不相等,从这个例子我们可以看出,只有+连接的两边都是字符串常量时,引用才会指向字符串常量池否则都是指向内存中的其他地址。

实例五:

public static void main(String[] args) { 
    String Monday = "Monday";  
    String Mon = "Mon";  
    final String  day = "day";  
    System.out.println(Monday == "Mon" + "day");  
    System.out.println(Monday == "Mon" + day);  
}

输出结果:
true
true
分析: 加上final后day也变成了常量,所以第二句的引用也是指向的字符串常量池。

String有没有线程安全问题

String类是一个不可变对象,其它有两层意思:

  1. 一是String类是一个final类,不能产生一个String的子类;
  2. 二是在String类中提供的所有方法中,如果有String返回值就会创建一个String对象,不对原对象进行修改,这就保证了原对象不可改变。

总结

  • Java中String对象是不可变的

  • Java支持通过构造方法或字面常量构建字符串

  • 字符串对象存放的位置可能在堆内存,也可能在字符串常量池。使用构造方法构建的字符串对象一定在堆内存,如果堆该字符串对象调用String.intern()方法,则可以将该字符串移入字符串常量池。

  • 字符串常量池在JVM底层本质上是一个Hashtable

  • 字符串上支持很多操作API,例如字符串连接、截取字符串、trim、替换字符等等,这些操作看似是写操作,实际上都会返回一个新的字符串

  • 字符串的连接操作有几种方式:“+”运算符重载,底层是依靠StringBuilder实现的;String.contact()方法,底层是依赖Array.copy实现的;StringBuilder,通过预先分配一个字符缓冲区来进行字符串的连接,适合大批量字符串连接的情况

  • String、StringBuilder和StringBuffer的底层数据结构都是char[]数组,不同的是String将该char数组设置成了不可变的(final),通过这个关键字实现了不可变对象。

  • StringBuilder是JDK1.5提供的,目的是补充StringBuffer用在单线程环境下——不必要且性能低的不足。

-String还提供了intern()方法。调用该方法时,如果字符串常量池中包括了一个等于此String对象的字符串(由equals方法确定),则返回池中的字符串的引用。否则,将此String对象添加到池中,并且返回此池中对象的引用。
- 在JDK6中,不推荐大量使用intern方法,因为这个版本字符串缓存在永久代中,这个空间是有限了,除了FullGC之外不会被清楚,所以大量的缓存在这容易OutOfMemoryError。

1.6之后的版本把字符串放入了堆中,避免了永久代被挤满。

在这里插入图片描述

  1. 如果要操作少量的数据用 String;

  2. 多线程操作字符串常量池下操作大量数据 StringBuffer;

  3. 单线程操作字符串常量池下操作大量数据 StringBuilder。


  • 输入描述字符串常量池是全局的,JVM 中独此一份,因此也称为全局字符串常量池。

  • 运行时常量池中的字符串字面量若是成员的,则在类的加载初始化阶段就使用到了字符串常量池;若是本地的,则在使用到的时候(执行此代码时)才会使用到字符串常量池。

  • 其实,“使用常量池”对应的字节码是一个 ldc 指令,在给 String 类型的引用赋值的时候会先执行这个指令,看常量池中是否存在这个字符串对象的引用,若有就直接返回这个引用,若没有,就在堆里创建这个字符串对象并在字符串常量池中记录下这个引用(jdk1.7)。

  • String 类的 intern() 方法还可在运行期间把字符串放到字符串常量池中。

  • JVM 中除了字符串常量池,8种基本数据类型中除了两种浮点类型剩余的6种基本数据类型的包装类,都使用了缓冲池技术,但是 Byte、Short、Integer、Long、Character 这5种整型的包装类也只是在对应值在[-128,127]时才会使用缓冲池,超出此范围仍然会去创建新的对象。其中:

    • 在 jdk1.6(含)之前也是方法区的一部分,并且其中存放的是字符串的实例(字符串存在永久代中,容易出现性能问题和内存溢出。)
    • 在 jdk1.7(含)之后是在堆内存之中,存储的是字符串对象的引用,字符串实例是在堆中;
    • jdk1.8 已移除永久代,字符串常量池是在本地内存当中,存储的也只是引用。
版权声明:本文为qq877728715原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq877728715/article/details/102950711

智能推荐

RIP/DHCP/ACL综合实验

组播: 加入组的组成员才会接受到消息,只需要将流量发送一次到组播地址 减少控制面流量,减少头部复制, RIP1  广播   有类  不支持认证 RIP2  组播   无类  (支持VLAN)、支持认证 所有距离矢量路由协议:具有距离矢量特征的协议,都会在边界自动汇总 控制平面  路由的产生是控制平面的流量 数据平面  ...

【Sublime】使用 Sublime 工具时运行python文件

使用 Sublime 工具时报Decode error - output not utf-8解决办法   在菜单中tools中第四项编译系统 内最后一项增添新的编译系统 自动新建 Python.sublime-build文件,并添加"encoding":"cp936"这一行,保存即可 使用python2 则注释encoding改为utf-8 ctr...

java乐观锁和悲观锁最底层的实现

1. CAS实现的乐观锁 CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的,也可以理解为自旋锁 JUC是指import java.util.concurrent下面的包, 比如:import java.util.concurrent.atomic.AtomicInteger; 最终实现是汇编指令:lock...

Python 中各种imread函数的区别与联系

  原博客:https://blog.csdn.net/renelian1572/article/details/78761278 最近一直在用python做图像处理相关的东西,被各种imread函数搞得很头疼,因此今天决定将这些imread总结一下,以免以后因此犯些愚蠢的错误。如果你正好也对此感到困惑可以看下这篇总结。当然,要了解具体的细节,还是应该 read the fuc...

用栈判断一个字符串是否平衡

注: (1)本文定义:左符号:‘(’、‘[’、‘{’…… 右符号:‘)’、‘]’、‘}’……. (2)所谓的字符串的符号平衡,是指字符串中的左符号与右符号对应且相等,如字符串中的如‘(&r...

猜你喜欢

JAVA环境变量配置

位置 计算机->属性->高级系统设置->环境变量 方式一 用户变量新建path 系统变量新建classpath 方式二 系统变量 新建JAVA_HOME,值为JDK路径 编辑path,前加 方式三 用户变量新建JAVA_HOME 此路径含lib、bin、jre等文件夹。后运行tomcat,eclipse等需此变量,故最好设。 用户变量编辑Path,前加 系统可在任何路径识别jav...

常用的伪类选择器

CSS选择器众多 CSS选择器及权重计算 最常用的莫过于类选择器,其它的相对用的就不会那么多了,当然属性选择器和为类选择器用的也会比较多,这里我们就常用的伪类选择器来讲一讲。 什么是伪类选择器? CSS伪类是用来添加一些选择器的特殊效果。 常用的为类选择器 状态伪类 我们中最常见的为类选择器就是a标签(链接)上的为类选择器。 当我们使用它们的时候,需要遵循一定的顺序问题,否则将可能出现bug 注意...

ButterKnife的使用介绍及原理探究(六)

前面分析了ButterKnife的源码,了解其实现原理,那么就将原理运用于实践吧。 github地址:       点击打开链接 一、自定义注解 这里为了便于理解,只提供BindView注解。 二、添加注解处理器 添加ViewInjectProcessor注解处理器,看代码, 这里分别实现了init、getSupportedAnnotationTypes、g...

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。考试复习题库1|要求:只能使用单字符比较操作。

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。 要求只能使用单字符比较操作。 参考代码: 实验结果截图:...

小demo:slideDown()实现二级菜单栏下拉效果

效果如下,鼠标经过显示隐藏的二级菜单栏 但是这样的时候会存在一个问题,就是鼠标快速不停移入移出会导致二级菜单栏闪屏现象,一般需要使用stop()来清除事件  ...