黑马学习资料Day07

2025-11-09 黑马程序员学习笔记 46 0

String与StringBuilder 详细总结(附实战案例)

在Java开发中,StringStringBuilder是处理字符串的核心类,前者用于存储不可变字符串,后者用于高效处理可变字符串。本文基于API学习文档,系统梳理两类的核心特性、方法及实战案例,适合作为开发参考或学习笔记。

一、API基础认知

1. 什么是API?

API(Application Programming Interface,应用程序编程接口)是Java官方或第三方提供的“现成类”,开发者可直接调用其方法解决问题,无需重复编写底层逻辑。例如之前学习的Scanner(键盘录入)、Random(生成随机数),以及本文重点的StringStringBuilder均属于API。

2. 学习目标

  • 掌握String类的不可变性、常量池优化及核心方法;
  • 理解StringBuilder的可变性、高效性及适用场景;
  • 能独立完成字符串处理实战案例(登录验证、验证码生成等);
  • 区分StringStringBuilder的内存差异及使用场景。

二、String类:不可变字符串的处理

1. String类的核心特点

String类是Java中最常用的字符串处理类,其核心特性决定了它的使用场景和注意事项:

  • 不可变性String对象一旦创建,其内容(字符序列)无法修改。任何“修改”操作(如拼接、替换)都会生成新的String对象,原对象不变。
  • 线程安全:因不可变性,多线程环境下无需担心并发修改问题,可安全共享String对象。
  • 常量池优化:Java会在“字符串常量池”(元空间中的一块区域)缓存首次创建的字符串。后续使用String s = "abc"时,会优先从常量池取值,避免重复创建对象,节省内存。
  • 支持正则表达式:提供matches()(匹配正则)、split()(按正则切割)、replace()(按正则替换)等方法,可处理复杂字符串逻辑。
  • 可序列化与可比较:实现Serializable接口(支持网络传输/持久化)、Comparable接口(支持字符串排序)。

2. String类的常见构造方法

构造方法说明
public String()创建空白字符串(长度为0,无任何字符)
public String(String original)根据传入的字符串,创建新的String对象(复用常量池已有对象)
public String(char[] chs)将字符数组转换为字符串(如new String(new char[]{'a','b'})得到"ab"
public String(byte[] bytes)将字节数组转换为字符串(常用于网络传输或文件读取后的解码)

3. String类的内存存储(面试重点)

String的创建方式不同,内存存储逻辑也不同,这是面试高频考点,需重点理解:

场景1:直接赋值(String s1 = "abc"

  • 执行逻辑:JVM先检查字符串常量池,若已存在"abc",则s1直接指向常量池中的对象;若不存在,先在常量池创建"abc",再让s1指向它。
  • 示例代码:

    String s1 = "abc";
    String s2 = "abc";
    System.out.println(s1 == s2); // true(s1和s2指向常量池同一对象)

场景2:new关键字创建(String s2 = new String("abc")

  • 执行逻辑:无论常量池是否存在"abc",都会在堆内存创建新的String对象,该对象的value属性指向常量池中的"abc";最终s2指向堆内存的新对象。
  • 示例代码:

    String s1 = "abc";
    String s2 = new String("abc");
    System.out.println(s1 == s2); // false(s1指向常量池,s2指向堆内存)

场景3:变量拼接(String s3 = s2 + "c"

  • 执行逻辑:JDK8及以下会创建StringBuilder对象,通过append()拼接后调用toString()生成新的String对象(存入堆内存,不进入常量池);因此s3与常量池中的"abc"地址不同。
  • 示例代码:

    String s1 = "abc";
    String s2 = "ab";
    String s3 = s2 + "c";
    System.out.println(s1 == s3); // false(s3指向堆内存新对象)

场景4:常量拼接(String s2 = "a" + "b" + "c"

  • 执行逻辑:编译期会直接优化为"abc",相当于String s2 = "abc",因此s2指向常量池中的"abc",与s1地址相同。
  • 示例代码:

    String s1 = "abc";
    String s2 = "a" + "b" + "c";
    System.out.println(s1 == s2); // true(编译期优化为同一常量)

4. String类的核心方法

String类提供了丰富的方法用于字符串处理,以下是开发中最常用的类别:

(1)字符串比较

  • public boolean equals(String str):严格比较两个字符串的内容(大小写、字符序列完全一致才返回true),区分==(比较地址)。
  • public boolean equalsIgnoreCase(String str):忽略大小写比较内容(如"Abc".equalsIgnoreCase("aBC")返回true)。

(2)字符串遍历

  • public char[] toCharArray():将字符串转换为字符数组,便于遍历每个字符。
  • public char charAt(int index):根据索引(从0开始)获取指定位置的字符(若索引越界,抛StringIndexOutOfBoundsException)。
  • public int length():返回字符串的长度(字符个数),注意与数组length属性(无括号)区分。

(3)字符串截取

  • public String substring(int beginIndex):从指定索引开始,截取到字符串末尾(如"abcde".substring(2)返回"cde")。
  • public String substring(int beginIndex, int endIndex):截取[beginIndex, endIndex)范围的字符(包头不包尾,如"abcde".substring(1,3)返回"bc")。

(4)字符串替换与切割

  • public String replace(CharSequence oldStr, CharSequence newStr):将字符串中的oldStr全部替换为newStr,返回新字符串(原字符串不变)。
  • public String[] split(String regex):根据regex(正则表达式)切割字符串,返回切割后的字符串数组(如"a,b,c".split(",")返回["a","b","c"])。

5. String类实战案例(附代码与思路)

案例1:用户登录(三次机会)

题目需求

已知正确的用户名("itheima")和密码("123456"),模拟用户登录:键盘录入用户名和密码,共给三次机会,登录成功/失败给出对应提示。

解题思路
  1. 定义正确的用户名和密码(常量);
  2. 使用for循环控制三次机会(循环变量从3递减到1);
  3. 每次循环中,用Scanner录入用户输入的用户名和密码;
  4. equals()比较输入值与正确值,匹配则提示成功并退出循环,不匹配则提示剩余次数;
  5. 三次均失败,提示“登录失败”。
核心代码
import java.util.Scanner;

public class LoginDemo {
    public static void main(String[] args) {
        // 1. 定义正确的用户名和密码
        String correctUsername = "itheima";
        String correctPassword = "123456";
        Scanner sc = new Scanner(System.in);

        // 2. 循环控制三次机会
        for (int i = 3; i >= 1; i--) {
            System.out.print("请输入用户名:");
            String inputUsername = sc.next();
            System.out.print("请输入密码:");
            String inputPassword = sc.next();

            // 3. 比较输入值与正确值
            if (correctUsername.equals(inputUsername) && correctPassword.equals(inputPassword)) {
                System.out.println("登录成功!");
                break; // 登录成功,退出循环
            } else {
                if (i > 1) {
                    System.out.println("用户名或密码错误,剩余" + (i - 1) + "次机会");
                } else {
                    System.out.println("三次机会已用完,登录失败!");
                }
            }
        }
        sc.close();
    }
}
代码解释
  • correctUsername.equals(inputUsername):严格比较用户名内容,避免==比较地址的错误;
  • 循环变量i从3递减,每次失败后提示剩余次数,逻辑更清晰;
  • 三次失败后不进入break,直接执行循环外逻辑,提示最终结果。

案例2:统计字符次数

题目需求

键盘录入一个字符串,统计其中大写字母小写字母数字字符的出现次数(不考虑其他字符)。例如:输入"aAb3&c2B*4CD1",输出“小写字母:3个,大写字母:4个,数字:4个”。

解题思路
  1. 初始化三个计数器(upperCountlowerCountnumberCount),初始值为0;
  2. toCharArray()将字符串转换为字符数组,遍历每个字符;
  3. 对每个字符进行判断:

    • 大写字母:char >= 'A' && char <= 'Z'upperCount++
    • 小写字母:char >= 'a' && char <= 'z'lowerCount++
    • 数字:char >= '0' && char <= '9'numberCount++
  4. 遍历结束后,打印三个计数器的值。
核心代码
import java.util.Scanner;

public class CharCountDemo {
    public static void main(String[] args) {
        // 1. 初始化计数器
        int upperCount = 0; // 大写字母计数
        int lowerCount = 0; // 小写字母计数
        int numberCount = 0; // 数字计数
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入字符串:");
        String str = sc.next();

        // 2. 转换为字符数组并遍历
        char[] chars = str.toCharArray();
        for (char c : chars) {
            if (c >= 'A' && c <= 'Z') {
                upperCount++;
            } else if (c >= 'a' && c <= 'z') {
                lowerCount++;
            } else if (c >= '0' && c <= '9') {
                numberCount++;
            }
            // 其他字符不处理
        }

        // 3. 打印结果
        System.out.println("小写字母:" + lowerCount + "个");
        System.out.println("大写字母:" + upperCount + "个");
        System.out.println("数字:" + numberCount + "个");
        sc.close();
    }
}
代码解释
  • str.toCharArray():将字符串拆分为字符数组,便于逐个判断;
  • 字符比较依赖ASCII码:'A'-'Z'对应ASCII值65-90,'a'-'z'对应97-122,'0'-'9'对应48-57;
  • 多分支if-else确保每个字符只被统计一次,逻辑严谨。

案例3:手机号屏蔽

题目需求

以字符串形式从键盘接受一个手机号(11位),将中间四位号码屏蔽,最终效果如:"15616501234""156****1234"

解题思路
  1. 键盘录入手机号(需确保是11位,此处简化处理);
  2. substring()截取手机号的前3位(0-3,包头不包尾)和后4位(7-11);
  3. 将前3位、"****"、后4位拼接,得到屏蔽后的手机号;
  4. 打印结果。
核心代码
import java.util.Scanner;

public class PhoneMaskDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入11位手机号:");
        String phone = sc.next();

        // 1. 截取前3位(0-3)和后4位(7-11)
        String prefix = phone.substring(0, 3); // 前3位:索引0到2
        String suffix = phone.substring(7);    // 从索引7到末尾(后4位)

        // 2. 拼接屏蔽后的字符串
        String maskedPhone = prefix + "****" + suffix;

        // 3. 打印结果
        System.out.println("屏蔽后手机号:" + maskedPhone);
        sc.close();
    }
}
代码解释
  • phone.substring(0,3):截取索引0、1、2的字符(前3位),符合[begin, end)规则;
  • phone.substring(7):从索引7开始截取到末尾,11位手机号的索引7-10对应后4位;
  • 拼接时直接用+,因仅一次拼接,String的不可变性对性能影响可忽略。

三、StringBuilder类:高效处理可变字符串

1. 为什么需要StringBuilder?

String的不可变性导致“频繁修改字符串”(如循环拼接)时,会创建大量临时对象,浪费内存且效率低。StringBuilder通过可变字符数组实现,修改直接作用于数组,避免频繁创建对象,是处理可变字符串的核心工具。

2. StringBuilder的核心特点

  • 可变性:底层存储为char[]数组,添加、删除、替换字符时直接修改数组,不创建新对象(扩容除外)。
  • 非线程安全:方法未加synchronized同步锁,多线程环境下可能出现数据不一致,线程安全场景需用StringBuffer(性能略低)。
  • 高效性

    • 避免频繁创建临时对象;
    • 扩容机制:当字符长度超出当前容量时,自动扩容为“当前容量×2 + 2”(默认初始容量16),减少扩容次数。
  • 初始容量

    • 无参构造:默认16个字符(new StringBuilder());
    • 指定容量:new StringBuilder(100)(初始容量100,适合已知大致长度的场景);
    • 基于字符串:new StringBuilder("Hello")(初始容量=字符串长度+16=5+16=21)。

3. StringBuilder的构造方法

构造方法说明
public StringBuilder()无参构造,初始容量16
public StringBuilder(int capacity)指定初始容量,避免频繁扩容
public StringBuilder(String str)基于字符串初始化,初始容量=字符串长度+16

4. StringBuilder的核心方法

StringBuilder的方法支持链式调用(方法返回this),使用更简洁,核心方法如下:

方法作用示例
append(...)末尾追加任意类型数据(String、int、char等)sb.append("Hello").append(123)(结果"Hello123"
insert(int offset, ...)在指定索引offset处插入数据sb.insert(5, "World")"Hello""HelloWorld"
replace(int start, int end, String str)替换[start, end)范围的字符为strsb.replace(0,5,"Hi")"HelloWorld""HiWorld"
delete(int start, int end)删除[start, end)范围的字符sb.delete(2,7)"HiWorld""Hi"
deleteCharAt(int index)删除指定索引index处的字符sb.deleteCharAt(1)"Hi""H"
reverse()反转字符序列sb.append("abc").reverse()(结果"cba"
charAt(int index)获取指定索引的字符sb.charAt(1)"abc"'b'
setCharAt(int index, char c)修改指定索引的字符为csb.setCharAt(1,'x')"abc""axc"
length()返回当前字符个数(实际存储的字符数)sb.append("abc").length()(结果3)
capacity()返回当前容量(数组的长度)new StringBuilder().capacity()(结果16)

5. StringBuilder实战案例(附代码与思路)

案例1:对称字符串判断

题目需求

键盘接受一个字符串,判断该字符串是否为对称字符串(正读和反读一致,如"123321""111"是对称字符串,"123123"不是),并打印“是”或“不是”。

解题思路
  1. 键盘录入字符串;
  2. 创建StringBuilder对象,传入该字符串并调用reverse()方法反转;
  3. 将反转后的StringBuilder转换为String,与原字符串用equals()比较;
  4. 若相等则为对称字符串,否则不是。
核心代码
import java.util.Scanner;

public class SymmetricStringDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入字符串:");
        String str = sc.next();

        // 1. 反转字符串(用StringBuilder)
        StringBuilder sb = new StringBuilder(str);
        String reversedStr = sb.reverse().toString(); // 反转后转为String

        // 2. 比较原字符串与反转后的字符串
        if (str.equals(reversedStr)) {
            System.out.println("是对称字符串");
        } else {
            System.out.println("不是对称字符串");
        }

        sc.close();
    }
}
代码解释
  • sb.reverse():直接反转StringBuilder中的字符序列,无需手动遍历交换;
  • sb.toString():将StringBuilder转换为String,才能与原字符串用equals()比较;
  • 该方法逻辑简洁,避免了手动遍历字符串首尾字符的繁琐操作。

案例2:数组拼接字符串

题目需求

定义一个方法,将int数组中的数据按照指定格式(如[1, 2, 3, 4, 5])拼接成一个字符串返回;调用该方法并在控制台输出结果(示例:数组{1,2,3,4,5}→输出"[1, 2, 3, 4, 5]")。

解题思路
  1. 定义方法arrayToString(int[] arr),返回值为String
  2. 在方法内创建StringBuilder对象,先追加"["
  3. 遍历数组,对每个元素:

    • 若不是最后一个元素:追加元素 + ", "
    • 若是最后一个元素:仅追加元素;
  4. 遍历结束后,追加"]",将StringBuilder转换为String并返回;
  5. main方法中创建数组,调用方法并打印结果。
核心代码
public class ArrayToStringDemo {
    public static void main(String[] args) {
        // 1. 定义int数组
        int[] arr = {1, 2, 3, 4, 5};
        // 2. 调用方法拼接字符串
        String result = arrayToString(arr);
        // 3. 打印结果
        System.out.println(result); // 输出:[1, 2, 3, 4, 5]
    }

    // 定义方法:将int数组拼接为指定格式的字符串
    public static String arrayToString(int[] arr) {
        // 处理数组为null的情况(可选)
        if (arr == null) {
            return "";
        }
        // 1. 创建StringBuilder对象
        StringBuilder sb = new StringBuilder();
        sb.append("["); // 先追加左括号

        // 2. 遍历数组
        for (int i = 0; i < arr.length; i++) {
            if (i == arr.length - 1) {
                // 最后一个元素:仅追加元素
                sb.append(arr[i]);
            } else {
                // 非最后一个元素:追加元素 + ", "
                sb.append(arr[i]).append(", ");
            }
        }

        // 3. 追加右括号
        sb.append("]");

        // 4. 转换为String并返回
        return sb.toString();
    }
}
代码解释
  • StringBuilderappend()链式调用,避免String拼接产生临时对象,效率更高;
  • 遍历中通过i == arr.length - 1判断是否为最后一个元素,避免末尾多一个", "
  • 增加arr == null的判断,提升方法的健壮性(避免空指针异常)。

四、String与StringBuilder的对比与选择

1. 内存差异(核心区别)

场景String(+拼接)StringBuilder(append())
内存开销每次+创建StringBuilderString对象(如"a"+"b"+"c"创建2个StringBuilder、2个String仅创建1个StringBuilder对象,直接修改底层数组
效率低(频繁创建对象,GC回收开销大)高(无临时对象,直接操作数组)

2. 适用场景选择

  • 使用String

    • 字符串内容无需修改(如存储常量、配置信息);
    • 仅需少量拼接(如"Hello" + "World",编译期优化后无性能问题);
    • 多线程环境下共享字符串(不可变性保证线程安全)。
  • 使用StringBuilder

    • 频繁修改字符串(如循环拼接、插入、删除、反转);
    • 单线程环境(非线程安全);
    • 已知字符串大致长度,可指定初始容量减少扩容(如new StringBuilder(100))。

五、总结

  • String是不可变字符串类,依赖常量池优化,适合存储静态字符串;
  • StringBuilder是可变字符串类,底层基于char[]数组,适合频繁修改字符串;
  • 核心方法需熟练掌握(Stringequals()substring()split()StringBuilderappend()reverse());
  • 实战中需根据“是否修改字符串”选择合适的类,兼顾性能与安全性。

本文覆盖了StringStringBuilder的核心知识点及实战案例,可作为日常开发的参考手册,也可用于面试前的复习梳理。