String 与 StringBuffer 与 StringBuilder 各自的妙用

news2024/9/25 11:16:08

String 与 StringBuffer 与 StringBuilder 各自的妙用

在这里插入图片描述


每博一文案

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

文章目录

    • String 与 StringBuffer 与 StringBuilder 各自的妙用
    • 每博一文案
    • 1. String 的不可变性
      • 1.1 字符串常量池
      • 1.2 String 相关的面试题陷阱
        • 1.2.1 题目一:
        • 1.2.2 题目二:
        • 1.2.3 题目三:
        • 1.2.4 题目四:
        • 1.2.5 题目五:
        • 1.2.6 题目六:
        • 1.2.7 题目七:
    • 2. StringBuffer 类
      • 2.1 StringBuffer 类的常用方法
        • 2.1.1 append( )
        • 2.1.2 setCharAt( )
        • 2.1.3 replace( )
        • 2.1.4 delete( )
        • 2.1.5 insert ( )
        • 2.1.6 reverse()
        • 2.1.7 substring( )
        • 2.1.8 charAt( )
        • 2.1.9 setCharAt( )
        • 2.1.10 StringBuffer 常用方法的总结:
    • 3. StringBuilder 类
      • 3.1 StringBuilder 类的常用方法
      • 3.2 String 与 StringBuffer 与 StringBuilder 三者的区别
    • 4. 有关String的几道算法题
    • 5. 总结:
    • 6. 最后:

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 :其中的 str,str2 都是通过字面常量创建的,并且被 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. 最后:

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


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/175932.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringMVC】看完这篇简单理解并入门SpringMVC:通过入门案例举例子的方式快速理解

SpringMVC简介1.什么是MVC2.什么是SpringMVC3.SpringMVC的特点4.入门案例1.准备工作2.配置web.xml3.创建请求控制器4.创建SpringMVC的配置文件5.测试HelloWorld6.总结1.什么是MVC MVC是一种软件架构的思想&#xff0c;将软件按照模型、视图、控制器来划分 M&#xff1a;Model&…

【Java】比较器 Comparator Comparable

一、背景 我们在使用 Collections.sort() 对链表进行排序&#xff08;或者使用 Arrays.sort() 对数组进行排序&#xff09;时&#xff0c;常常需要根据不同情况自定义排序规则。比如&#xff1a;当我们存储学生对象时&#xff0c;我们需要按照学生年龄进行排序&#xff0c;这时…

树状数组(Binary Indexed Tree (B.I.T))

树状数组 树状数组 (Binary Indexed Tree(B.I.T), Fenwick Tree) 是一个查询和修改复杂度都为 log(n) 的数据结构。 「前缀和查询」与「单点更新」 直接前驱&#xff1a;c[i] 的直接前驱为 c[i - lowbid(i)]&#xff0c;即 c[i] 左侧紧邻的子树的根。 直接后继&#xff1a;c[i…

财务精度:BigInteger 与 BigDecimal

财务精度&#xff1a;BigInteger 与 BigDecimal 每博一文案 师父说: 人这一辈子&#xff0c;真地好难。 有些人&#xff0c;好着好着&#xff0c;忽然就变陌生了&#xff0c;有些手&#xff0c;牵着牵着&#xff0c;瞬间就放开了&#xff0c;有些路&#xff0c;走着走着&#…

算法练习笔记——栈的常用方法以及算法练习

栈学习常用方法介绍力扣练习力扣 20. 有效的括号力扣 32. 最长有效括号常用方法介绍 Stack<Character> characters new Stack<>();//判断栈是否为空boolean empty characters.empty();//将a压入栈底&#xff0c;同时也返回aCharacter push characters.push(a);/…

MYSQL中的常见知识问题(一)

1、MYSQL中redolog、binlog 、undolog的区别与作用。redolog&#xff1a;即重做日志&#xff0c;用来实现事物的一个持久性&#xff0c;由radiobuff和radiolog两部分组成。其中 radiobuff是一个缓冲&#xff0c;存放在内存里面&#xff1b;radiolog是文件&#xff0c;存放在磁盘…

基于粒子群优化和引力搜索混合优化算法改进的前馈神经网络(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【3-神经网络八股】北京大学TensorFlow2.0

课程地址&#xff1a;【北京大学】Tensorflow2.0_哔哩哔哩_bilibiliPython3.7和TensorFlow2.1六讲&#xff1a;神经网络计算&#xff1a;神经网络的计算过程&#xff0c;搭建第一个神经网络模型神经网络优化&#xff1a;神经网络的优化方法&#xff0c;掌握学习率、激活函数、损…

走进 HTML

文章目录01 什么是HTML&#xff1f;02 HTML的基本结构03 网页基本标签04 图像标签05 链接标签06 块元素和行内元素07 列表07 表格08 视频和音频09 页面结构10 iframe内联框架11 表单语法&#x1f449; 表单元素格式&#x1f449; 表单的应用&#x1f449; 表单初级验证01 什么是…

【Mysql】 数据库用户管理

【Mysql】 数据库用户管理 DCL:英文全称是Data Control Language(数据控制语言)&#xff0c;用来管理数据库用户、控制数据库的访问权限。 1. 管理用户 想要对数据库用户进行操作&#xff0c;我们首先得进入 mysql 数据库 use mysql1.1 查询用户 select * from user;该条命…

每日一问-ChapGPT-20230122-关于春节习俗

文章目录每日一问-ChapGPT系列起因每日一问-ChapGPT-20230116-关于春节习俗世界有哪些国家过春节中国各个地区过春节都有哪些习俗台湾的春节习俗有哪些新加坡过春节有哪些习俗初一到初七的传统习俗有哪些在热闹的节日里&#xff0c;自己无法融入氛围&#xff0c;是什么原因不喜…

【MySQL】第六部分 单行函数

【MySQL】第六部分 单行函数 文章目录【MySQL】第六部分 单行函数6. 单行函数6.1 常用的函数6.2 角度与弧度转换函数6.3 三角函数6.4 指数与对数6.5 进制转换6.6 字符串函数6.7 日期时间函数6.7.1 获取时间和日期6.7.2 日期与时间戳的转换6.7.3 获取月份、星期、星期数、天数等…

筑基二层 —— 图解函数递归、数组详解

目录 一.修炼必备 二.图解递归的执行过程 三.数组 3.1 一维数组 3.2 二维数组 3.3 数组的共同问题 一.修炼必备 1.入门必备&#xff1a;VS2019社区版&#xff0c;下载地址&#xff1a;Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (microsoft.com) 2.趁手武…

I.MX6ULL裸机开发笔记7:汇编点亮LED灯

一、vscode调整 加入汇编插件ARM vscode权限受限&#xff08;因为Ubuntu中的文件有的是root权限创建的&#xff0c;vscode以普通用户打开的话没有操作权限&#xff09;chmod 修改文件或者文件夹权限 二、编程步骤 使能GPIO时钟设置引脚复用位GPIO设置引脚属性&#xff08;上下…

深入跨域问题(3) - 利用 JSONP 解决跨域

目录 1.简单例子&#xff1a; 2.基本实现 3.JSONP 与 CORS 的对比 什么是跨域&#xff0c;在这篇文章内部就不再讲述了&#xff0c;本文主要着重于实现 JSONP 。 script 标签&#xff1a; 根据同源策略的限制&#xff0c;在 端口&#xff0c;域名&#xff0c;协议 这三者 …

AX7A200教程(3): DDR3突发读写

上一个章节我们新建工程&#xff0c;然后进行基本的初始化操作&#xff0c;这个章节将在上个工程的基础上进行突发读写因ddr3读写部分控制信号比较多&#xff0c;所以ddr3读写控制模块比较复杂&#xff0c;本章节着重于一个256位数据的突发读写&#xff0c;ddr读写控制模块暂不…

【JavaWeb】前端开发三剑客之CSS(下)

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【JavaWeb】 ✈️✈️本篇内容:CSS的深度学习&#xff01; &#x1f680;&#x1f680;代码托管平台github&#xff1a;JavaWeb代码存放仓库&#xff01; ⛵⛵作者…

(考研湖科大教书匠计算机网络)第二章物理层-第一、二节:物理层基本概念和传输媒体

文章目录一&#xff1a;物理层概念二&#xff1a;物理层传输媒体&#xff08;1&#xff09;导引型传输媒体A&#xff1a;同轴电缆B&#xff1a; 双绞线C&#xff1a;光纤①&#xff1a;光纤通信②&#xff1a;光纤D&#xff1a;电力线&#xff08;2&#xff09;非导引型传输媒体…

day22 多线程02

1.线程池 1.1 线程状态介绍 当线程被创建并启动以后&#xff0c;它既不是一启动就进入了执行状态&#xff0c;也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢&#xff1f;Java中的线程 状态被定义在了java.lang.Thread.State…

[ESP][驱动]ST7701S RGB屏幕驱动

ST7701SForESP ST7701S ESP系列驱动&#xff0c;基于ESP-IDF5.0&#xff0c;ESP32S3编写。 本库只负责SPI的配置&#xff0c;SPI设置屏幕两方面。由于RGB库和图形库的配置无法解耦&#xff0c;具体使用的RGB库&#xff0c;图形库需要自行配置添加。 SPI的指令&#xff0c;地…