String,StringBuffer和StringBuilder的区别

深入理解String

1、概述

String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final byte[] value;

    /** The identifier of the encoding used to encode the bytes in {@code value}. */
    private final byte coder;
}

value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

2、不可变的好处

1. 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。

4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

3、深入理解String的不可变性

通过字面量方式为字符串赋值时,此时的字符串存储在方法区的字符串常量池中,并且在字符串常量池中不会存储相同内容的字符串。

String s1="hi";
String s2="hi";
s1="hello";

总结1:String字符串具有不可变性,当字符串重新赋值时,不会在原来的内存地址进行修改,而是重新分配新的内存地址进行赋值。

String s3="abc";
String s4=s3+"def";
System.out.println(s3); //abc

总结2:当字符串进行拼接时,也不会在原来的内存地址进行修改,而是重新分配新的内存地址进行赋值。

String s5="abcd";
String s6=s5.replace("d","g");
System.out.println(s5);

3、String Pool

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。

当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。

下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 和 s2.intern() 方法取得同一个字符串引用。intern() 首先把 “aaa” 放到 String Pool 中,然后返回这个字符串引用,因此 s3 和 s4 引用的是同一个字符串。

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s3 == s4);           // true

如果是采用 “bbb” 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。

String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6);  // true

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

4、实例化方式

具有两种实例化方式

方式一:通过字面量方式实例化

String str = "abc";

方式二:通过new+构造器的方式实例化

String str=new String("abc");

使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。

  • “abc” 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 “abc” 字符串字面量;
  • 而使用 new 的方式会在堆中创建一个字符串对象。

创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。

public class NewStringTest {
    public static void main(String[] args) {
        String s = new String("abc");
    }
}

使用 javap -verbose 进行反编译,得到以下内容:

// ...
Constant pool:
// ...
   #2 = Class              #18            // java/lang/String
   #3 = String             #19            // abc
// ...
  #18 = Utf8               java/lang/String
  #19 = Utf8               abc
// ...

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2                  // class java/lang/String
         3: dup
         4: ldc           #3                  // String abc
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: astore_1
// ...

在 Constant Pool 中,#19 存储这字符串字面量 “abc”,#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。

以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

String两种实例化方式的对比

通过字面量方式为字符串赋值时,此时的字符串存储在方法区的字符串常量池中;

通过new+构造器方式实例化字符串时,字符串对象存储在堆中,但是字符串的值仍然存储在方法区的常量池中。

5、String的常用方法

int length():返回字符串的长度
char charAt(int index):返回指定索引处的字符
boolean isEmpty():判断字符串是否为空
String toLowerCase():将字符串中的所有字符转换为小写
String toUpperCase():将字符串中的所有字符转换为大写
String trim():返回字符串的副本,去掉前导空白和尾部空白,中间的空白不会被去掉
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):忽略大小写,比较字符串的内容是否相同
String concat(String str):将指定字符串连接到此字符串的结尾,等价于用“+int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回从beginIndex到末尾的子字符串
String substring(int beginIndex, int endIndex):返回从beginIndex到endIndex前一位的子字符串,不包括endIndex

boolean endsWith(String suffix): 判断字符串是否以指定的后缀结束
boolean startsWith(String prefix):判断字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):判断字符串在指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):判断当前字符串中是否包含指定的字符串

int indexOf(String str):返回指定子字符串在当前字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回从指定的索引后,指定子字符串在当前字符串中第一次出现处的索引
int lastIndexOf(String str):返回指定子字符串在当前字符串中最后一次出现处的索引
int lastIndexOf(String str, int fromIndex):返回从指定的索引后,指定子字符串在当前字符串中最后一次出现处的索引
注:indexOf和lastIndexOf方法如果未查找到指定子字符串时,返回值都为-1String replace(char oldChar, char newChar):替换当前字符串中指定的子字符串
String[] split(String regex):根据指定的符号拆分当前字符串,然后返回一个String数组

6、String与char[]之间相互装换

String ——> char[] : 调用String的toCharArray方法
String s1="ABCD";
char[] chars = s1.toCharArray();

char[] ——> String : 调用String的构造器
char[] chars=new char[]{'A','B','C','D'};
String s = new String(chars);
System.out.println(s); //ABCD

7、String与byte[]之间相互装换

String ——> byte[] : 调用String的getBytes方法
String str="ABCD";
byte[] bytes = str.getBytes(); //也可以使用指定的字符编码

byte[] ——> String : 调用String的构造器
byte[] bytes=new byte[]{'A','B','1','2'};
String str = new String(bytes); //也可以使用指定的字符编码
System.out.println(str); //AB12

深入理解StringBuffer和StringBuilder

StringBuffer、StringBuilder和String类似,底层也是用一个数组来存储字符串的值,并且数组的默认长度为16,即一个空的StringBuffer对象数组长度为16。实例化一个StringBuffer对象即创建了一个大小为16个字符的字符串缓冲区。但是当我们调用有参构造函数创建一个StringBuffer对象时,数组长度就不再是16了,而是根据当前对象的值来决定数组的长度,数组的长度为“当前对象的值的长+16”。所以一个 StringBuffer 创建完成之后,有16个字符的空间可以对其值进行修改。如果修改的值范围超出了16个字符,会先检查StringBuffer对象的原char数组的容量能不能装下新的字符串,如果装不下则会对 char 数组进行扩容。

那StringBuffer是怎样进行扩容的呢?
扩容的逻辑就是创建一个新的 char 数组,将现有容量扩大一倍再加上2,如果还是不够大则直接等于需要的容量大小。扩容完成之后,将原数组的内容复制到新数组,最后将指针指向新的 char 数组。

StringBuffer和StringBuilder中常用的方法:

StringBuffer append(xxx):拼接字符串
StringBuffer delete(int start,int end):删除指定范围的内容,左开右闭
StringBuffer replace(int start, int end, String str):替换指定范围的内容
StringBuffer insert(int offset, xxx):在指定位置插入指定的内容
StringBuffer reverse() :把当前字符序列逆转

public int indexOf(String str) : 返回指定子字符串在当前字符串中第一次出现处的索引
public String substring(int start,int end) :返回指定范围的子字符串
public int length() : 返回字符串的长度
public char charAt(int n ) : 获取指定索引处的字符
public void setCharAt(int n ,char ch) : 设置指定索引处的字符

面试题:String、StringBuffer和StringBuilder的异同?
相同点:底层都是通过char数组实现的
不同点:

String对象一旦创建,其值是不能修改的,如果要修改,会重新开辟内存空间来存储修改之后的对象;而StringBuffer和StringBuilder对象的值是可以被修改的;

StringBuffer几乎所有的方法都使用synchronized实现了同步,线程比较安全,在多线程系统中可以保证数据同步,但是效率比较低;而StringBuilder 没有实现同步,线程不安全,在多线程系统中不能使用 StringBuilder,但是效率比较高。

如果我们在实际开发过程中需要对字符串进行频繁的修改,不要使用String,否则会造成内存空间的浪费;当需要考虑线程安全的场景下使用 StringBuffer,如果不需要考虑线程安全,追求效率的场景下可以使用 StringBuilder。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我! 毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+数据库(含vue前端源码).zip
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值