String 与 StringBuffer 与 StringBuilder 各自的妙用

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

String 与 StringBuffer 与 StringBuilder 各自的妙用

在这里插入图片描述


每博一文案

我从未见过一个早起勤奋谨慎诚实的人抱怨命运不好的。
最完美的状态不是你从不失误而是你从没放弃成长。没人能把你变的越来越好时间和经历
只是陪衬支撑你变的越来越好的是你坚强的意志修养品行以及不断的反思和修正。
   很喜欢的一段话: "人生最好的贵人就是努力向上的自己。" 生活不会辜负一个一直在努力的人。
    愿我们都能在各自坚持的道路上遇见更好的自己。
                                            ——————  人民日报(RENMIN RIBAO)

文章目录

1. String 的不可变性

在 Java 编程中广泛使用的字符串是一系列字符。在 Java 编程语言中字符串是对象。

Java 平台提供了创建和操作字符串的 String 类。

在这里插入图片描述

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

    /** Cache the hash code for the string */
    private int hash; // Default to 0

在这里插入图片描述


  • String 类代表字符串。java 程序中的所有字符串字面值 (如 “abc” ) 都作为此类的实例实现。
  • String 是一个 final 类不可被继承扩展子类。字符串是常量用双引号"" 括起来 它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的所以可以共享。
  • String 对象的字符内容是存储在一个字符数组 value[] 中的。
  • 这个 String 类是不可变的所以一旦创建了一个 String 对象就不能改变。 🌟🌟🌟🌟🌟

创建字符串最直接的方法是编写String str = "Hello World!";

在这种情况下“Hello World!” 是一个字符串字面值在你的代码中 用双引号括起来 的一系列字符。只要遇到代码中的字符串文字编译器就会用它的值创建一个 String 对象。

与任何其他对象一样您可以使用 new 关键字和构造器函数创建对象。String 类有 13 个构造函数允许你使用不同的来源诸如字符数组来提供串的初始值;

String str = "abc";
// 等同于
String data[] = {'a','b','c'};
String str = new String(data);

如下是一些 String 常用的构造器

	String str1 = "abc";

    byte[] bytes = {'a', 'b', 'c'};
    String str2 = new String(bytes);
    // 指定 bytes[] 数组中的“起始位置”,和“长度” 转换为字符串
    // String str3 = new String(bytes,定义对应数组的开始位置,以及需要转换为字符串的长度);
    String str3 = new String(bytes, 1, 1);

    char[] chars = {'a', 'b', 'c'};
    String str4 = new String(chars);

    // 同样可以指定char[] 数组中的“起始位置”,和“长度” 转换为字符串
    // String str5 = new String(chars,定义对应数组的开始位置,以及需要转换为字符串的长度);
    String str5 = new String(chars, 0, 3);

    String str6 = new String("abc");
    

其余的
在这里插入图片描述


1.1 字符串常量池

理解String 类是不可变的所以一旦创建了一个 String 对象就不能改变。

从源码上看。String 类中有一个 private final char value[]; 的字符数组用于存储字符串 的内容其中我们可以看到该数组是被 final 修饰的意味着该数组一经赋值操作该引用一旦指向某个对象的地址值后就无法修改了。以及数组一旦创建其的长度就无法修改了。这两方面就限定了字符串 String 一经创建就无法被修改了。

在这里插入图片描述

JDK 当中双引号括起来的字符串例如“abc”“def” 都是直接存储在方法区的 字符串常量池当中的。

为什么 SUN 公司 把字符串存储在一个 “字符串常量池” 当中呢 ? ??

因为字符串在实际的开发中的使用太频繁了所以把字符串放到方法区的字符串常量池当中注意字符串常量池中不会存储重复内容的字符串 。例如字符串"abc" 只会在字符串常量池中存储一份所有对象共用。通过共享减少内存的消耗。

public class StringTest {
    public static void main(String[] args) {
        String str = "abc";
        System.out.println(str);
    }
}

上述代码的内存图示

在这里插入图片描述


使用 new 对象的方式创建 String 的内存图

public class StringTest {
    public static void main(String[] args) {
        String str = new String("abc");

        System.out.println(str);
    }
}

在这里插入图片描述


String str = “abc” 与 String str = new Stiring(“abc”) 创建的字符串在内存上有什么区别 ???

String str = “abc” 是只创建了一个对象“abc” 在字符串常量池中str 的引用直接指向方法区中的字符串常量池中的 “abc” 的地址。

而 String str = new String(“abc”)则是创建了两个对象一个是在堆区中的 new String 对象该对象的引用指向另一个在方法区字符串常量池中的 "abc"的地址str 是指向 堆区的 new String 对象的地址该 new String 的引用再指向字符串常量池中的 "abc"的地址。str 并不是直接指向 方法区中的字符串常量池中的 “abc”的而是通过 new String 对象间接的指向的。


  • 当对字符串重新赋值时需要重新指定内存区域进行赋值不能在原有的 values[] 进行赋值操作因为 values[] 数组已经被 final 修饰了并且数组一旦创建其长度是无法修改的。
  • 当对现有的字符串进行连续的 + “拼接” 操作时需要重新指定内存区域进行赋]值不能使用原有的 values[] 数组进行赋值操作。
  • 当调用 String 中的 replace() 的对象方法修改指定字符或字符串时也时需要重新指定内存区域进行赋值的同样不能在原有的 values[] 数组中进行赋值操作。
  • 基本上String 类中所有对String 增删改基本上都是返回一个修改完的 String 字符串类型而不是在原有的基础上修改的。对于 String 的一些常用方法大家可以移步至 : 🔜🔜🔜 你必须要知道的字符串有关的方法类_ChinaRainbowSea的博客-CSDN博客
  • 这些都是 String 不可变性的体现
  • 如下代码
public class StringTest {
    public static void main(String[] args) {
        String str = "hello";
        str = "world";
        System.out.println(str);

        String str2 = "abc";
        str2 = str2 + "def";
        System.out.println(str2);

        String str3 = "Google";
        String str4 = str3.replace('o', 'e');  // replace 替换
        System.out.println(str3);
        System.out.println(str4);

    }
}

其上述代码的内存图示如下

在这里插入图片描述

1.2 String 相关的面试题陷阱

1.2.1 题目一

如下Java程序创建了多少个对象

public class StringTest {
    public static void main(String[] args) {
        String str = new String("hello");
        String str2 = new String("hello");
    }
}

答创建了三个对象分别是 堆区中的两个new String , new String ,和方法区中的字符串常量池中的 “hello” 字符串。

在这里插入图片描述


1.2.2 题目二

阅读如下代码观察其运行结果为
public class StringTest {
    public static void main(String[] args) {
        String str = new String("hello");
        String str2 = new String("hello");

        System.out.println("str == str2 : " + (str == str2));

        String str3 = "world";
        String str4 = "world";
        System.out.println("str3 == str4 : " + (str3 == str4));

        String str5 = "hello";
        System.out.println("str5 == str : " + (str5 == str));

    }
}

在这里插入图片描述

  • == 运算符对于引用类型来说比较的是地址值不是字符串的内容。这一点需要注意。
  • str == str2 是通过 new 对象创建的字符串其引用不是直接指向方法区中的字符串常量池的而是通过在堆区中创建一个 new String 对象的引用指向 字符串常量池中的 ”hello" 的而 str str2 是指向堆区中的 new String对象的地址值其new String 对象的地址值是不同的所以是 false
  • str3 == str4 是直接通过字符串字面量值 双引号括起来的字符串直接赋值的 “world” 。其 str3,str4 引用都是指向方法区中的字符串常量池中的 “world" 。字符串常量池中不会存储相同内容的字符串只会存储一份所有对象共用所以 str3 和 str4 的地址值是一样的。返回 true
  • str5 == str : 根据上述的几点分析同理可得 str5 是直接指向方法区中的字符串常量池的 ”hello“的str 是间接的指向方法区中的字符串常量池的 ”hello“的, str5 , str 两者之间直接引用的地址值是不同的所以返回 false

1.2.3 题目三

观察如下 Java代码其运行结果为

class Person {
    int age;
    String name;
    
    public Person() {
        // 无参构造器
    }
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}


public class StringTest {
    public static void main(String[] args) {
        Person p1 = new Person("Tom", 20);
        Person p2 = new Person("Tom", 19);

        System.out.println(p1.getName() == p2.getName());
    }
}

在这里插入图片描述

p1.getName() == p2.getName() 虽然这里的 p1,p2 是两个不同的对象但是其中的 String 成员变量的值是都是 字符串 ”Tom“ 都是直接指向方法区中的字符串常量池的 “Tom"的字符串常量池中不会存储相同重复的字符串对于重复的字符串仅仅只会存储一份所有对象共用共享减少内存的消耗。所以 p1 和 p2 对象中的 String name 成员变量都是 ”Tom” 都是指向字符串常量池中的同一个 “Tom" 的其地址值相同所以返回 true

1.2.4 题目四

观察如下Java程序其运行结果是

public class StringTest {
    public static void main(String[] args) {
        String s1 = "javaEE";
        String s2 = "hadoop";

        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop";
        String s5 = s1 + "haddoop";
        String s6 = "javaEE" + s2;
        String s7 = s1 + s2;

        // == 运算符 比较的是地址值对于引用类型来说
        System.out.println("s3 == s4: " + (s3 == s4));
        System.out.println("s3 == s5: " + (s3 == s5));
        System.out.println("s3 == s6: " + (s3 == s6));
        System.out.println("s3 == s7: " + (s3 == s7));
        System.out.println("s5 == s6: " + (s5 == s6));
        System.out.println("s5 == s7: " + (s5 == s7));
        System.out.println("s6 == s7: " + (s6 == s7));

        String s8 = s7.intern();
        // 堆空间的 s7对象调用intern()对象方法会将字符串常量池中已经存在 s7 字符串内容的值的地址
        // 直接赋值给 s8 , 等同于 s8 = "abc";
        System.out.println("s8 == s4: " + (s8 == s4));
    }
}

答:

在这里插入图片描述

作这道题目记住如下知识点就可以了

  • 常量(字符串字面常量) + (拼接) + 常量(字符串字面常量(是被“” 双引号括起来的字符串)) 后的字符串结果是直接指向字符串常量池当中的。没有堆区的一个String 对象间接指向的且字符串常量池中是不会存储相同内容的常量(字符串)的。
  • 使用 + 字符串拼接字符串时只要存在一个变量(不是字符串字面常量(就是双引号“”括起来的) )结果就在堆中。
  • 如果使用 intern() 方法返回值就在常量池中
String  s2 = "abc";
String  s1 = s2.intern();

s2 调用 intern() 对象方法会将字符串常量池中已经存储到的 s2 中的 “abc” 直接赋值到 s1 当中等同于 String s1 = “abc”

  • 特殊的对于被 final 修饰的变量字符串本身就是字符串常量 等同于 s = “abc” 字面常量。

1.2.5 题目五:

观察如下Java代码其运行结果为

public class StringTest {
    public static void main(String[] args) {
        String str = "good";
        char[] arrs = {'a','b','c'};

        change(str,arrs);

        System.out.println(str);
        for (int i = 0; i < arrs.length; i++) {
            System.out.println(arrs[i]);
        }
    }

    public static void change(String str,char[] arrs) {
        str = "Google";
        arrs[0] = 'A';
    }
}

答:

在这里插入图片描述

注意了对于引用类型来说方法 change(String str,char[] arrs) 所传的是地址值。

其中参数 String str = “Google” 是修改该变量中的字符串但是注意了字符串是被 final 修饰的是不可变的所以main 方法中的 String str = “good" 是不会被修改的。而其中的 char[ ] 字符数组是可以被修改的所以其中的 arrs[0] = ‘A’ 就被修改了。

总的来说就是记住一点字符串的是被 final 修饰的以及其中存储字符串的 values[] 数组(一旦创建)其数组长度是不可改变的标志着字符串不可变性

1.2.6 题目六

阅读如下Java程序观察其运行结果

public class StringTest {
    public static void main(String[] args) {

        final String str = "hello";
        final String str1 = new String("hello");
        final String str2 = "world";
        String str3 = str + str2;
        String str4 = str1 + str2;
        String str5 = "helloworld";
        System.out.println(str4 == str5);
        System.out.println(str3 == str5);
    }
}

在这里插入图片描述

与第 四道是相同的其原理也是一样的。

对于被 final 修饰的变量字符串本身就是字符串常量 等同于 s = “abc” 字面常量。

所以 str3 = str + str2, str3 == str5 其中的 strstr2 都是通过字面常量创建的并且被 final 修饰其 str3 = str + str2,就等同于是 str3 = “hello” + “wolrd” 字符串字面常量的 + 拼接是直接指向字符串常量池中的字符串的地址的没有经过 堆区创建 new 对象的。所以str3 与 str5 的地址是一样的都是来自字符串常量池中的 ”helloworld“字符串。所以返回 true

而其中的 str1 是通过 new String 对象的方式经过堆中创建字符串对象再引用的指向 字符串常量池其地址值不是直接指向字符串常量池的而是在堆区中的new String 对象中。自然与 str5 直接指向字符串常量池中的地址值是不同的所以返回 false

1.2.7 题目七

观察如下 Java 程序其运行结果为:

public class StringExam {
    public static void main(String[] args) {
        String str = null;
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(str);  // 4, 添加的是 "null" 这个字符串
        System.out.println(stringBuffer.length());  // 4
        System.out.println(stringBuffer); // "null:

        StringBuffer stringBuffer2 = new StringBuffer(str);   // 抛出 null 异常 java.lang.NullPointerException
        System.out.println(stringBuffer2);
    }
}

在这里插入图片描述

答: 注意了虽然 str 定义了是 null 但是当调用 StringBuffer .append() 添加字符的时候是会将其参数的内容转换为 “null” 字符串的所以 append(str) 实际上添加的是 null 字符串。null 四个字符stringBuffer.length() 返回的便是 4 了。

当通过 new StringBuffer (str) 传个 null 就会报 NullPointerException 异常了。因为是需要在堆区中创建空间的一个 null 引用无法指向对应方法区中的字符串常量池中的字符串地址的。

2. StringBuffer 类

public class StringBufferTest {
    public static void main(String[] args) {
        String str = "";

        for (int i = 0; i < 1000; i++) {
            str = str + String.valueOf(i);
        }   
    }
}

像上述代码str + String.valueOf(i) 不断的拼接字符串实际上原来上面拼接好的 “0”,“01”,“012”…都是虽然被创建了但是又被丢弃了因为要的是最后一次 for()循环的结果。因为字符串的不可变性。 就会在内存中创建 “0”“01”“012”…一次性的字符串创建了又丢弃了导致大量的这样一次性不使用就丢弃的字符串对象存留在内存中降低效率。极大的影响了程序的性能。

对于上述这样频繁对字符串拼接的操作的为了减少内存的消耗提高程序的性能。Java提供了两个类分别为 StringBuffer 类 和 StringBuilder 类 用于专门的字符串频繁的拼接问题。

这里我们首先认识一下StringBuffer

StringBuffer 对象就像 String 对象除了它们可以被修改。在内部这些对象被视为包含一系列字符的变长数组。 在任何时候序列的长度和内容都可以通过方法调用来改变。

在这里插入图片描述

在这里插入图片描述

  • java.lang.StringBuffer 代表线程安全的可变字符序列 。一个类似于 String 的字符串缓冲区但不能修改。虽然在任意时间点上它都包含某种特定的字符序列但通过某些方法调用可以改变该序列的长度和内容。说白了就是可以对字符串内容进行增删改查此时不会产生新的对象。

  • 其中 StringBuffer 的方法很多 都与 String 相同。

  • 可以作为参数传递时方法内部可以改变值。不像 String 不可被修改。

  • StringBuffer 继承(extend) 了 AbstractStringBuilder 抽象类其中的源码分析如下

在这里插入图片描述


  • StringBuffer类不同于 String其对象必须使用构造器生成。有四个构造器
public StringBuffer();  // 构造一个其中不带字符的字符串缓冲区其初始容量为 16 个字符。 
public StringBuffer(int capacity); // 构造一个不带字符但具有指定初始容量的字符串缓冲区
public StringBuffer(String str);  // 构造一个字符串缓冲区并将其内容初始化为指定的字符串内容。该字符串的初始容量为 16 加上字符串参数的长度.
public StringBuffer(CharSequence seq); // 

在这里插入图片描述


public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer str1 = new StringBuffer();          // 初始容量为16的字符串缓冲区
        StringBuffer str2 = new StringBuffer(16);        // 构造指定容量的字符串缓冲区
        StringBuffer str3 = new StringBuffer("hello");   // 将内容初始化为指定字符串内容
    }

}

2.1 StringBuffer 类的常用方法

2.1.1 append( )

append() 对象方法向SringBuffer 字符串添加器字符串就像 + 拼接字符串的效果是一样的。

参数将被转换成字符串就好象使用了 String.valueOf 方法一样。然后将所得字符串中的字符追加到此序列

StringBuilder append(boolean b)
StringBuilder append(char c)
StringBuilder append(char[] str)
StringBuilder append(char[] str, int offset, int len)
StringBuilder append(double d)
StringBuilder append(float f)
StringBuilder append(int i)
StringBuilder append(long lng)
StringBuilder append(Object obj)
StringBuilder append(String s)

在这里插入图片描述


扩容问题如果要添加的数据底层数组装不下了那么就需要扩容底层的数组 ???

扩容的原理是如下阅读源码得到答案

其中的StringBuffer 中的 append()方法调用的所继承 AbstractStringBuilder 父类的中的 append()方法

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

如下就是 AbstractStringBuilder 父类中的 append()方法如下。

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

先是通过 int len = str.length() 获取到所传 String 类型的长度作为参数和 count + len 其中的 count 是 StringBuffer 中字符串的实际长度。

在这里插入图片描述

对于使用 StringBuffer 的无参构造器其默认是 16 大小的字符数组。

    public StringBuffer() {
        super(16);
    }

在这里插入图片描述

在这里插入图片描述

当数组容量不够存储时使用Arrays.copyOf()方法将其原本所不够的数组内容拷贝复制到一个 newCapacity(int minCapacity) 方法中的一个 <<1 在原本容量不足的基础上扩大 2 倍的一个新的数组的存储。再将其扩大容量后的数组的地址复制给原理的 value从而达到一个扩容的效果。

在这里插入图片描述

在这里插入图片描述

如何优化 StringBuffer的性能 ???

答在创建StringBuffer 的时候尽可能给定一个初始化容量的构造器最好减少StringBuffer 底层数组的扩容次数预估计一下

给一个大一些的初始化容量。

关键点: 给一个合适的初始化容量减少扩容的次数可以提高程序的执行效率。


public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer str1 = new StringBuffer();

        str1.append(1);        // 添加 int 类型的值
        str1.append(3.14);     // 添加 double 类型的值
        str1.append("hello");  // 添加 String 类型的值
        str1.append(true);     // 添加 boolean 类型的值
        str1.append('A');      // 添加 char 类型的值

        System.out.println(str1);
    }

}

在这里插入图片描述

2.1.2 setCharAt( )

修改字符串中指定的下标位置的字符内容这里修改的是原本的字符串的值并不会返回一个新的字符串。

注意: 下标位置 index 参数 必须 >= 0(起始下标从 0 开始 ,下标位置不可以超过字符串的索引下标不然报越界异常。

在这里插入图片描述

举例:

public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer("hello");
        str.setCharAt(0,'A');
        str.setCharAt(4,'B');  // 是从原本的字符串基础上修改的。
        System.out.println(str);
    }

}

在这里插入图片描述


2.1.3 replace( )

replace() 替换字符串中指定的范围中的内容。

注意了其中的参数 repalce(int start ,int end ,String str) 中的起始位置和结束位置。[起始位置结束位置) 是左闭右开的左边的值可以取到右边的值是无法取到的。

在这里插入图片描述

举例:

public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer("helloworld");
        str.replace(0,3,"999");
        System.out.println(str);
        
    }

}

在这里插入图片描述

2.1.4 delete( )

delete() 删除指定位置的字符串的内容。

注意 不要超出了字符串的索引范围起始位置是从 0 开始的其中对应参数 delete(int start , int end) [起始下标位置结束下标位置)是左闭有开的左边的可以取到右边的取不到。

在这里插入图片描述

举例:

public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer("helloworld");
        str.delete(0,5);
        System.out.println(str);

    }

}

在这里插入图片描述

2.1.5 insert ( )

insert() 在指定的下标位置插入内容。将第二个参数插入到字符串构建器中。第一个整数参数表示数据要插入之前的索引。数据在插入操作发生之前转换为字符串。

StringBuilder insert(int offset, boolean b)
StringBuilder insert(int offset, char c)
StringBuilder insert(int offset, char[] str)
StringBuilder insert(int index, char[] str, int offset, int len)
StringBuilder insert(int offset, double d)
StringBuilder insert(int offset, float f)
StringBuilder insert(int offset, int i)
StringBuilder insert(int offset, long lng)
StringBuilder insert(int offset, Object obj)
StringBuilder insert(int offset, String s)

在这里插入图片描述

在这里插入图片描述


举例

public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer("上海伦敦东京");
        System.out.println(str);
        str.insert(0, 99); // 插入 int
        str.insert(str.length(), 3.14);  // 插入 false 类型
        str.insert(2, "纽约");
        str.insert(5, true);

        System.out.println(str);

    }

}

在这里插入图片描述

2.1.6 reverse()

reverse() 把当前字符序列逆转.

在这里插入图片描述

举例:

public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer("helloWorld");
        System.out.println(stringBuffer);
        stringBuffer.reverse();   // 当前字符串反转
        System.out.println(stringBuffer);

    }

}

在这里插入图片描述


2.1.7 substring( )

substring() 字符串的截断。同样注意是参数的截取是左闭右开的。同时不要超过了字符串的索引位置。

在这里插入图片描述

举例:

public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer("helloWorld");
        String str = stringBuffer.substring(5, stringBuffer.length());
        System.out.println(str);

    }
}

在这里插入图片描述

2.1.8 charAt( )

charAt() 返回字符串对应索引下标的字符注意不要超过了字符串的索引范围。导致报异常。

在这里插入图片描述

举例:

public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer("helloWorld");
        char c1 = stringBuffer.charAt(0);
        System.out.println(c1);
        char c2 = stringBuffer.charAt(5);
        System.out.println(c2);

    }
}

在这里插入图片描述


2.1.9 setCharAt( )

setCharAt() : 修改对应字符串索引下标的 字符 。注意不要超过了字符串的索引下标位置。

在这里插入图片描述

举例:

public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer("helloWorld");
        stringBuffer.setCharAt(0,'H');
        stringBuffer.setCharAt(1,'E');
        System.out.println(stringBuffer);

    }
}

在这里插入图片描述

2.1.10 StringBuffer 常用方法的总结

* // 总结:
* 增加: append(xxx)
* 删: delete(int start,int end)
* 改: setCharAt(int n,char ch) / replace(int start,int end, String str)
* 查: charAt(int n)
* 插: insert(int offset, xxx)
* 长度: length()
* 遍历: for + charAt()/toString()
  • 字符串增加操作的方法 append(xxx)
  • 字符串删除操作的方法delete(int start, int end)
  • 字符串修改操作的方法setCharAt(int n ,char ch) replace(int start , int end , String str)
  • 字符串查询操作的方法charAt(int n )
  • 字符串插入操作的方法insert(int offset , xxx)
  • 字符串长度查询的方法length()
  • 字符串遍历操作的方法for() 循环 + charAt() toString()

3. StringBuilder 类

StringBuilder 和 StringBuffer 非常类似均代表可变的字符序列而且 提供相关功能的方法也一样。只是由于其方法是同步的所以它是线程安全的。

如下

在这里插入图片描述

在这里插入图片描述

3.1 StringBuilder 类的常用方法

StringBuilder 类的方法和 StringBuffer 类是一样的这里就不多说明了大家可以回看 StringBuffer 类的常用方法。

为什么 StringBuilder / StringBuffer 是可以变的 ???

我们从源码上分析可以看到 StringBuffer / StringBuilder 内部实际上是一个 char[] value 字符数组存储的

这个 value[] 数组并没有被 final 修饰StringBuffer / StringBuilder 的初始化容量我们记得应该是 16 当

存储满了会自动进行扩容好像是 2 倍的扩容底层调用了数组拷贝的方法 Arrays.copy() 是这样扩容的。

所以StringBuffer / StringBuilder 适合于使用字符串的频繁拼接操作。

在这里插入图片描述

3.2 String 与 StringBuffer 与 StringBuilder 三者的区别

String(JDK1.0): 不可变字符序列。

StringBuffer(JDK1.0): 可变序列效率低线程安全。

StringBuilder(JDK5.0): 可变序列效率高线程不安全。

注意作为参数传递的话方法内部 String 不会改变其值StringBufferStringBuilder 会改变其值。

因为 StringBuffer 是线程安全的使用了 synchronized 同步线程安全会由多线程变成单线程处理效率降低了但是线程安全了而StringBuilder(JDK5.0) 没有被 synchronized 线程是不安全的不会出现多线程变成单线程处理。所以 StringBuffer 效率上要比 StringBuilder 差一点但是更安全。

关于线程安全问题大家可以移步至Java多线程多线程同步安全问题的 “三“ 种处理方式 ||多线程 ”死锁“ 的避免 || 单例模式”懒汉式“的线程同步安全问题_ChinaRainbowSea的博客-CSDN博客

举例: String / StringBuffer / StringBuilder 三者的执行效率的比较

public class StringBufferTest {
    public static void main(String[] args) {
        // 设置初始比较时间
        long startTime = 0L;   // 默认 整数是 int 类型
        long endTime = 0L;

        String stringTest = "";
        StringBuffer stringBufferTest = new StringBuffer("");
        StringBuilder stringBuilderTest = new StringBuilder("");

        // String 执行
        startTime = System.currentTimeMillis();    // 获取到当前的毫秒值(时间戳)
        for (int i = 0; i < 200000; i++) {
            stringTest += i;
        }
        endTime = System.currentTimeMillis();
        System.out.println("String: " + (endTime - startTime));


        // StringBuffer 执行
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 200000; i++) {
            stringBufferTest.append(String.valueOf(i));  //将int 类型转换为 String
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuffer: " + (endTime - startTime));

        // StringBuilder
        startTime = System.currentTimeMillis();  // 获取到当前系统的毫秒数(时间戳)
        for (int i = 0; i < 200000; i++) {
            stringBuilderTest.append(String.valueOf(i));
        }
        endTime = System.currentTimeMillis();
        System.out.println("StringBuilder: " + (endTime - startTime));

    }
}

在这里插入图片描述

从结果上看可以比较出处理效率上 StringBuilder > StringBuffer > String .

4. 有关String的几道算法题

由于涉及到篇幅的问题感兴趣的可以移步至🔜🔜🔜 String 有趣简单的编程题_ChinaRainbowSea的博客-CSDN博客

5. 总结

  1. String 是不可变的无法被修改的其中存储字符串的是一个被 final 修饰的 char[] values的字符数组并且数组一旦创建其长度是无法被修改的。

  2. String 存储的方法区的常量池的存在。

  3. String 作为参数传递的话方法内部 String 不会改变其值StringBufferStringBuilder 会改变其值。

  4. 常量(字符串字面常量) + (拼接) + 常量(字符串字面常量(是被“” 双引号括起来的字符串)) 后的字符串结果是直接指向字符串常量池当中的。没有堆区的一个String 对象间接指向的且字符串常量池中是不会存储相同内容的常量(字符串)的。

  5. 使用 + 字符串拼接字符串时只要存在一个变量(不是字符串字面常量(就是双引号“”括起来的) )结果就在堆中

  6. 对于String 字符串的频繁拼接操作建议使用 StringBuilder / StringBuffer 的类

  7. StringBuilder / StringBuffer 是可变的字符序列其中存储的字符串数组 并没有被 final 修饰。

  8. StringBuffer(JDK1.0): 可变序列效率低线程安全。StringBuilder(JDK5.0): 可变序列效率高线程不安全。

  9. StringBuffer 扩容性问题默认存储容量是 16 不足时再原来的基础上 扩容 2 倍。

  10. 在创建StringBuffer 的时候尽可能给定一个初始化容量的构造器最好减少StringBuffer 底层数组的扩容次数提高程序的效率。预估计一下给一个大一些的初始化容量。

6. 最后

限于自身水平其中存在的错误希望大家给予指教韩信点兵——多多益善谢谢大家江湖再见后会有期


在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“String 与 StringBuffer 与 StringBuilder 各自的妙用” 的相关文章