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 的核心知识点、常见面试题、实用代码示例和练习题。建议结合实际编码练习,深入理解字符串的底层原理和使用技巧。