Skip to content

Java String与StringBuilder核心指南 ​

一、核心知识点 ​

1. String 类概述 ​

1.1 String 的特点 ​

三大核心特性:

  1. 字符串字面值都是 String 对象

    • Java 程序中的所有字符串字面值(如 "abc")都作为 String 类的实例实现
    • 凡是带双引号的都是 String 的对象
  2. 不可变性

    • 字符串是常量,它们的值在创建之后不能更改
    • String 对象是不可变的,所以可以共享
  3. 字符串共享

    • 由于 String 对象不可变,所以可以共享
    • 相同内容的字符串字面值会指向同一个对象

示例:

java
String s1 = "abc";
String s2 = "abc";
// s1 和 s2 指向同一个对象

String s3 = "hello";
s3 += "world";
// 实际上创建了新的对象,原对象不变

String不可变性

String共享特性

1.2 String 的底层实现原理 ​

核心原理:

  • String 底层是一个被 final 修饰的数组
  • 数组一旦被 final 修饰,地址值直接锁死,不能改变
  • 这就是 String 不可变的根本原因

JDK 版本差异:

JDK 版本底层数组类型内存占用
JDK 8 及之前private final char[] value每个 char 占 2 字节
JDK 9 及之后private final byte[] value每个 byte 占 1 字节

优化说明: JDK 9 之后将 char[] 改为 byte[],节省了内存空间。

2. String 的创建方式 ​

2.1 构造方法 ​

构造方法说明示例
String()创建空字符串对象String s = new String();
String(String str)根据字符串创建对象String s = new String("abc");
String(byte[] bytes)根据 byte 数组创建对象String s = new String(bytes);
String(char[] chars)根据 char 数组创建对象String s = new String(chars);
直接赋值最常用的方式String s = "abc";

2.2 扩展构造方法 ​

java
// 将 byte 数组的一部分转成 String
String(byte[] bytes, int offset, int count)
// bytes: 数组
// offset: 从哪个索引开始转
// count: 转多少个

// 将 char 数组的一部分转成 String
String(char[] chars, int offset, int length)

示例:

java
byte[] bytes = {97, 98, 99, 100, 101, 102};
String s1 = new String(bytes, 0, 3); // "abc"

char[] chars = {'a', 'b', 'c', 'd', 'e', 'f'};
String s2 = new String(chars, 0, 3); // "abc"

3. String 的常用方法 ​

3.1 判断方法 ​

方法说明
boolean equals(Object obj)判断字符串内容是否一样
boolean equalsIgnoreCase(String str)判断字符串内容是否一样,忽略大小写

重要提示: 推荐使用 "abc".equals(s) 而不是 s.equals("abc"),避免空指针异常。

3.2 获取方法 ​

方法说明
String concat(String str)字符串拼接,返回新的字符串
char charAt(int index)获取指定索引位置上的字符
int indexOf(String str)获取指定字符在字符串中第一次出现的索引位置
String substring(int beginIndex)从指定索引开始截取字符串到最后
String substring(int beginIndex, int endIndex)从指定索引位置开始截取到 endIndex(含头不含尾)
int length()获取字符串长度

3.3 转换方法 ​

方法说明
char[] toCharArray()将字符串转成 char 数组
byte[] getBytes()将字符串转成 byte 数组
byte[] getBytes(String charsetName)根据指定的编码集将字符串转成 byte 数组
String replace(CharSequence target, CharSequence replacement)将前面参数替换成后面的参数

编码说明:

编码集说明汉字占用
ASCII主要针对英文-
GBK中文字符集(国标码)2 字节
UTF-8万国码3 字节

3.4 分割方法 ​

java
String[] split(String regex) // 按照指定的规则分割字符串

注意: 如果分割符是 .,需要使用 \\. 进行转义。

3.5 其他常用方法 ​

方法说明
boolean contains(String s)判断字符串中是否包含指定的子串
boolean endsWith(String s)判断是否以指定的字符串结尾
boolean startsWith(String s)判断是否以指定的字符串开头
String toLowerCase()将字母转成小写
String toUpperCase()将字母转成大写
String trim()去掉字符串两端空格

4. String 新特性 - 文本块(Java 15+) ​

4.1 文本块的作用 ​

  • 简化跨越多行的字符串,避免对换行等特殊字符进行转义
  • 增强 Java 程序中字符串的可读性

4.2 文本块语法 ​

  • 开始分隔符:""" 后跟零个或多个空格,最终以行终止符结束
  • 结束分隔符:"""
  • 文本块内容以开始分隔符的行终止符后的第一个字符开始

传统方式 vs 文本块:

java
// 传统方式
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, 尚硅谷</p>\n" +
              "    </body>\n" +
              "</html>\n";

// 文本块方式(Java 15+)
String html = """
    <html>
        <body>
            <p>Hello, 尚硅谷</p>
        </body>
    </html>
    """;

4.3 文本块规则 ​

  1. 开始分隔符后必须有行终止符

    java
    String err = """"""; // 错误:开始分隔符后没有行终止符
  2. 可以自由使用双引号

    java
    String story = """
        Elly said,"Maybe I was a bird in another life."
        Noah said,"If you're a bird, I'm a bird."
        """;
  3. 可以使用转义字符

    java
    String html = """
        <html>\n
          <body>\n
            <p>Hello, world</p>\n
          </body>\n
        </html>\n
        """;

5. StringBuilder 类 ​

5.1 StringBuilder 的引入 ​

问题: String 每拼接一次都会产生一个新的字符串对象,如果多次拼接,会占用内存,效率低。

解决方案: 使用 StringBuilder 或 StringBuffer。

5.2 StringBuilder 的特点 ​

  1. 底层缓冲区

    • 底层有一个缓冲区(没有被 final 修饰的 byte 数组)
    • 拼接的字符串内容会直接放到数组中,不会随意产生新的字符串对象
    • 底层始终只有一个缓冲区
  2. 默认长度

    • 底层数组默认长度为 16
  3. 自动扩容

    • 如果超出数组的长度,会自动扩容(使用 Arrays.copyOf)
    • 如果一次性拼接的字符没有超出默认扩容的范围:默认是 2 倍 + 2
    • 如果一次性拼接的字符超出了默认扩容范围,就按照实际拼接的字符个数扩容

5.3 StringBuilder 的构造方法 ​

构造方法说明
StringBuilder()创建空的 StringBuilder 对象
StringBuilder(String str)根据字符串创建 StringBuilder 对象

5.4 StringBuilder 的常用方法 ​

方法说明
StringBuilder append(任意类型)拼接,返回的是 StringBuilder 自己
StringBuilder reverse()字符串翻转,返回的是 StringBuilder 自己
String toString()将 StringBuilder 转成 String

链式调用示例:

java
StringBuilder sb = new StringBuilder("张无忌");
sb.append("赵敏").append("小昭").append("周芷若").append("蛛儿");
System.out.println(sb); // 张无忌赵敏小昭周芷若蛛儿

sb.reverse();
System.out.println(sb); // 儿若芷周昭小敏赵忌无张

二、常见面试题 ​

⭐ 基础题 ​

1. String 为什么是不可变的? ​

答案:

  1. 底层实现: String 底层是一个被 final 修饰的数组(JDK 8 是 char[],JDK 9+ 是 byte[])
  2. final 关键字: 数组一旦被 final 修饰,地址值直接锁死,不能改变
  3. 不可变的好处:
    • 字符串常量池可以共享字符串对象,节省内存
    • 避免了多线程环境下的同步问题
    • 保证 hashCode 的稳定性,适合作为 HashMap 的 key

2. String 和 StringBuilder 的区别? ​

答案:

对比项StringStringBuilder
可变性不可变可变
线程安全安全(不可变)不安全
性能频繁拼接性能差频繁拼接性能好
使用场景字符串不频繁修改频繁拼接、修改字符串

3. String 的 equals 方法和 == 的区别? ​

答案:

  • ==:比较的是地址值(引用是否指向同一个对象)
  • equals():比较的是字符串内容是否相同

示例:

java
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");

System.out.println(s1 == s2); // true(指向常量池中同一个对象)
System.out.println(s1 == s3); // false(s3 是 new 出来的新对象)
System.out.println(s1.equals(s3)); // true(内容相同)

⭐⭐ 进阶题 ​

4. String 创建对象的方式有哪些?有什么区别? ​

答案:

方式一:直接赋值

java
String s1 = "abc";
  • 先在字符串常量池中查找是否存在 "abc"
  • 如果存在,直接返回引用
  • 如果不存在,在常量池中创建对象并返回引用

方式二:new 构造方法

java
String s2 = new String("abc");
  • 在堆内存中创建一个新对象
  • 如果常量池中没有 "abc",也会在常量池中创建一个对象
  • 所以可能创建 1 或 2 个对象

5. 如何高效地进行字符串拼接? ​

答案:

不推荐:使用 String 直接拼接

java
String s = "";
for (int i = 0; i < 1000; i++) {
    s += i; // 每次循环都创建新对象,性能差
}

推荐:使用 StringBuilder

java
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i); // 在原对象上修改,性能好
}
String s = sb.toString();

6. StringBuilder 和 StringBuffer 的区别? ​

答案:

对比项StringBuilderStringBuffer
线程安全不安全安全(synchronized)
性能高较低
出现版本JDK 1.5JDK 1.0
使用场景单线程环境多线程环境

使用建议: 单线程环境下优先使用 StringBuilder,性能更好。

⭐⭐⭐ 高级题 ​

7. String 的 substring 方法在 JDK 6 和 JDK 7+ 有什么区别? ​

答案:

JDK 6:

  • substring 方法会创建一个新的 String 对象,但底层仍然指向原 char 数组
  • 如果原字符串很大,substring 后的小字符串会持有大字符串的引用,可能导致内存泄漏

JDK 7+:

  • substring 方法会创建一个新的 String 对象,并复制需要的字符到新数组
  • 避免了内存泄漏问题,但会占用更多内存

8. 为什么 JDK 9 将 String 底层从 char[] 改为 byte[]? ​

答案:

  1. 节省内存:

    • char 类型占 2 字节,byte 类型占 1 字节
    • 大多数字符串只包含 Latin-1 字符(1 字节即可表示)
    • 可以节省约 50% 的内存
  2. 编码优化:

    • 引入了 coder 字段来标识编码方式
    • Latin-1 编码使用 1 字节,UTF-16 编码使用 2 字节
    • 根据字符串内容自动选择最优编码
  3. 性能提升:

    • 减少了内存占用,提高了缓存命中率
    • 减少了 GC 压力

三、实用代码片段 ​

1. 字符串遍历 ​

java
public static void traverseString(String str) {
    // 方式一:使用 charAt
    for (int i = 0; i < str.length(); i++) {
        System.out.println(str.charAt(i));
    }
    
    // 方式二:使用 toCharArray
    char[] chars = str.toCharArray();
    for (char c : chars) {
        System.out.println(c);
    }
}

2. 字符串统计 ​

java
public static void countCharacters(String str) {
    int big = 0;    // 大写字母个数
    int small = 0;  // 小写字母个数
    int number = 0; // 数字个数
    
    char[] chars = str.toCharArray();
    for (char c : chars) {
        if (c >= 'A' && c <= 'Z') {
            big++;
        } else if (c >= 'a' && c <= 'z') {
            small++;
        } else if (c >= '0' && c <= '9') {
            number++;
        }
    }
    
    System.out.println("大写字母个数:" + big);
    System.out.println("小写字母个数:" + small);
    System.out.println("数字个数:" + number);
}

3. 字符串反转 ​

java
public static String reverseString(String str) {
    // 方式一:使用 StringBuilder
    return new StringBuilder(str).reverse().toString();
    
    // 方式二:手动反转
    char[] chars = str.toCharArray();
    for (int i = 0, j = chars.length - 1; i < j; i++, j--) {
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }
    return new String(chars);
}

4. 判断字符串是否为空 ​

java
public static boolean isEmpty(String str) {
    // 判断 null 和 空字符串
    return str == null || str.length() == 0;
}

public static boolean isBlank(String str) {
    // 判断 null、空字符串和纯空格字符串
    return str == null || str.trim().length() == 0;
}

5. 字符串分割与拼接 ​

java
public static void stringSplitAndJoin() {
    // 分割
    String str = "abc,java,test";
    String[] parts = str.split(",");
    
    // 拼接
    String joined = String.join("-", parts); // "abc-java-test"
    
    // 使用 StringBuilder 拼接
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < parts.length; i++) {
        if (i > 0) {
            sb.append("-");
        }
        sb.append(parts[i]);
    }
    String result = sb.toString();
}

四、经典练习题 ​

练习 1:遍历字符串 ​

题目: 遍历字符串并打印每个字符。

要求:

  • 使用两种方式实现
  • 方式一:使用 charAt 方法
  • 方式二:使用 toCharArray 方法

参考答案:

java
public class StringTraversal {
    public static void main(String[] args) {
        String str = "abcdefg";
        
        // 方式一:使用 charAt
        System.out.println("方式一:使用 charAt");
        for (int i = 0; i < str.length(); i++) {
            System.out.println(str.charAt(i));
        }
        
        // 方式二:使用 toCharArray
        System.out.println("方式二:使用 toCharArray");
        char[] chars = str.toCharArray();
        for (char c : chars) {
            System.out.println(c);
        }
    }
}

练习 2:统计字符个数 ​

题目: 随便给一个字符串,统计该字符串中大写字母字符、小写字母字符、数字字符出现的次数(不考虑其他字符)。

要求:

  • 定义三个变量分别统计大写字母、小写字母、数字的个数
  • 遍历字符串进行统计
  • 输出统计结果

参考答案:

java
public class CharacterCount {
    public static void main(String[] args) {
        String str = "sdfaWERWERWE11231";
        
        int big = 0;    // 大写字母个数
        int small = 0;  // 小写字母个数
        int number = 0; // 数字个数
        
        // 遍历字符串
        char[] chars = str.toCharArray();
        for (char c : chars) {
            if (c >= 'A' && c <= 'Z') {
                big++;
            } else if (c >= 'a' && c <= 'z') {
                small++;
            } else if (c >= '0' && c <= '9') {
                number++;
            }
        }
        
        System.out.println("大写字母个数 = " + big);
        System.out.println("小写字母个数 = " + small);
        System.out.println("数字个数 = " + number);
    }
}

输出结果:

大写字母个数 = 6
小写字母个数 = 4
数字个数 = 5

练习 3:字符串反转 ​

题目: 将字符串进行反转。

要求:

  • 使用 StringBuilder 实现
  • 使用数组手动实现

参考答案:

java
public class StringReverse {
    public static void main(String[] args) {
        String str = "abcdefg";
        
        // 方式一:使用 StringBuilder
        String reversed1 = new StringBuilder(str).reverse().toString();
        System.out.println("StringBuilder 方式:" + reversed1);
        
        // 方式二:使用数组手动反转
        char[] chars = str.toCharArray();
        for (int i = 0, j = chars.length - 1; i < j; i++, j--) {
            char temp = chars[i];
            chars[i] = chars[j];
            chars[j] = temp;
        }
        String reversed2 = new String(chars);
        System.out.println("数组方式:" + reversed2);
    }
}

练习 4:字符串拼接性能对比 ​

题目: 对比 String 和 StringBuilder 在大量拼接时的性能差异。

要求:

  • 分别使用 String 和 StringBuilder 拼接 10000 次字符串
  • 记录并对比执行时间

参考答案:

java
public class StringConcatPerformance {
    public static void main(String[] args) {
        int count = 10000;
        
        // 使用 String 拼接
        long start1 = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < count; i++) {
            str += i;
        }
        long end1 = System.currentTimeMillis();
        System.out.println("String 拼接耗时:" + (end1 - start1) + "ms");
        
        // 使用 StringBuilder 拼接
        long start2 = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            sb.append(i);
        }
        String result = sb.toString();
        long end2 = System.currentTimeMillis();
        System.out.println("StringBuilder 拼接耗时:" + (end2 - start2) + "ms");
    }
}

预期结果:

String 拼接耗时:XXXms
StringBuilder 拼接耗时:Xms

StringBuilder 的性能明显优于 String。

五、学习建议 ​

1. 理解核心概念 ​

  • 不可变性: 理解 String 为什么不可变,以及不可变带来的好处
  • 常量池: 理解字符串常量池的工作原理
  • 性能差异: 理解 String 和 StringBuilder 的性能差异

2. 掌握常用方法 ​

  • 判断方法: equals, equalsIgnoreCase
  • 获取方法: charAt, indexOf, substring, length
  • 转换方法: toCharArray, getBytes, replace
  • 分割方法: split
  • StringBuilder 方法: append, reverse, toString

3. 避免常见错误 ​

  • 空指针异常: 使用 "abc".equals(s) 而不是 s.equals("abc")
  • 性能问题: 频繁拼接字符串时使用 StringBuilder
  • 编码问题: 注意 getBytes() 的编码问题

4. 实践建议 ​

  • 多写代码练习字符串操作
  • 理解并掌握字符串的底层原理
  • 关注 JDK 新特性(如文本块)
  • 在实际项目中选择合适的字符串处理方式

六、知识扩展 ​

1. String.intern() 方法 ​

作用: 将字符串对象加入字符串常量池。

java
String s1 = new String("abc");
String s2 = s1.intern();
String s3 = "abc";

System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true

2. 字符串常量池的位置 ​

JDK 版本常量池位置
JDK 6 及之前方法区(永久代)
JDK 7堆内存
JDK 8 及之后元空间(方法区的实现)

3. StringJoiner 类(JDK 8+) ​

作用: 更方便地进行字符串拼接,支持分隔符、前缀和后缀。

java
StringJoiner sj = new StringJoiner(",", "[", "]");
sj.add("a").add("b").add("c");
String result = sj.toString(); // "[a,b,c]"

4. String 的不可变性的应用场景 ​

  1. 作为 HashMap 的 key

    • 不可变性保证了 hashCode 的稳定性
    • 避免了键值对丢失的问题
  2. 多线程环境下的安全性

    • 不可变对象天生是线程安全的
    • 不需要额外的同步措施
  3. 安全性

    • 不可变对象不能被恶意修改
    • 适合存储敏感信息(如密码,但建议使用 char[])

学习提示: 本文档涵盖了 String 和 StringBuilder 的核心知识点、常见面试题、实用代码示例和练习题。建议结合实际编码练习,深入理解字符串的底层原理和使用技巧。