stringbuffer和stringbuilder是什么_Java编程

2022-09-29 11:13:24 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

文章目录

        • 字符串常量池
        • String 与 CharSequence区别
        • 概述String,StringBuffer与StringBuilder的区别
        • 细说一下String
        • 创建了几个对象
        • 到底相等不相等
          • String有没有线程安全问题
        • 总结
字符串常量池
  1. 什么是字符串常量池? JVM为了减少字符串对象的重复创建,其维护了一块特殊的内存,这段内存被称为字符串常量池(存储在方法区中)。
  2. 具体实现 当代码中出现字符串时,JVM首先会对其进行检查。
    • 如果字符串常量池中存在相同内容的字符串对象,如果有,则不再创建,直接返回这个对象的地址返回
    • 如果字符串常量池中不存在相同内容的字符串对象,则创建一个新的字符串对象并放入常量池,并返回新创建的字符串的引用地址。
    • new String(“str”)时,首先也会去检查常量池是否存在“str”(存在则不创建、不存在则在常量池先创建一个),然后在堆空间再开辟一块内存区域创建字符串对象 。
String 与 CharSequence区别
  • String 继承于CharSequence,也就是说String也是CharSequence类型。
  • CharSequence是一个接口,它只包括length(), charAt(int index), subSequence(int start, int end)这几个API接口。
  • 除了String实现了CharSequence之外,StringBuffer和StringBuilder也实现了CharSequence接口。
  • CharSequence就是字符串,String, StringBuilder和StringBuffer本质上都是通过字符数组实现的!
  • CharSequence与String都能用于定义字符串,但CharSequence的值是可读可写字符串,而String的值是只读字符串
概述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的字符串缓冲池是如何工作的呢?

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

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

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

创建了几个对象
代码语言:javascript复制
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. ==比较的是两个对象的内存地址是否相等

实例一:

代码语言:javascript复制
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都是指向字符串常量池中的同一个对象,所以内存地址是一样的,所以用==可以判断两个字符串是否相等。

实例二:

代码语言:javascript复制
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是指向内存的其他位置两者的内存地址不同的

实例三:

代码语言:javascript复制
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()方法:返回在字符串常量池中的对象的引用

实例四:

代码语言:javascript复制
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是一个变量,所以不能提前确定他的值,所以两者不相等,从这个例子我们可以看出,只有 连接的两边都是字符串常量时,引用才会指向字符串常量池否则都是指向内存中的其他地址。

实例五:

代码语言:javascript复制
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支持通过构造方法或字面常量创建字符串
  • 字符串对象存放的位置可能在堆内存,也可能在字符串常量池(和创建方法以及JDK的版本有关)。使用构造方法构建的字符串对象一定在堆内存,如果堆该字符串对象调用String.intern()方法,则可以将该字符串移入字符串常量池。
  • 字符串常量池在JVM底层本质上是一个Hashtable
  • 字符串上支持很多操作API,例如字符串连接、截取字符串、trim、替换字符等等,这些操作看似是写操作,实际上都会返回一个新的字符串
  • 字符串的连接方式:“ ”运算符重载,底层是依靠StringBuilder实现的;String.contact()方法,底层是依赖Array.copy实现的;ringBuilder,通过预先分配一个字符缓冲区来进行字符串的连接,适合大批量字符串连接的情况
  • StringBuilder是JDK1.5提供的,目的是补充StringBuffer用在单线程环境下——不必要且性能低的不足。
  • String、StringBuilder和StringBuffer的底层数据结构都是char[]数组,不同的是String将该char数组设置成了不可变的(final)
  • String的intern()方法调用该方法时,如果字符串常量池中包括了一个等于此String对象的字符串(由equals方法确定),则返回池中的字符串的引用。否则,将此String对象添加到池中,并且返回此池中对象的引用
    • 在JDK6中,不推荐大量使用intern方法,因为这个版本字符串缓存在永久代中,这个空间是有限了,除了FullGC之外不会被清楚,所以大量的缓存在这容易OutOfMemoryError。
  1. 如果要操作少量的数据用 String;
  2. 多线程操作字符串常量池下操作大量数据 StringBuffer;
  3. 单线程操作字符串常量池下操作大量数据 StringBuilder。

字符串在JDK1.6—JDK1.8的区别

  • JDK1.6及以前,常量池在方法区,这时的方法区也叫做永久代,其中存放的是字符串的实例(字符串存在永久代中,容易出现性能问题和内存溢出。)
  • JDK1.7(含)方法区合并到了堆内存中,这时的常量池也可以说是在堆内存中,存储的是字符串对象的引用,字符串实例是在堆中 1.6之后的版本把字符串放入了堆中,避免了永久代被挤满。
  • JDK1.8 已移除永久代,方法区又从堆内存中剥离出来了字符串常量池是在本地内存当中,存储的也只是引用。但实现方式与之前的永久代不同,这时的方法区被叫做元空间,常量池就存储在元空间

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/193656.html原文链接:https://javaforall.cn

0 人点赞