JavaSE之String 与 StringBuilder 全面解析(附实例代码)

JavaSE之String 与 StringBuilder 全面解析(附实例代码)

在 Java 开发中,字符串操作是高频场景,StringStringBuilder 作为处理字符串的核心类,理解其特性与差异对写出高效代码至关重要。本文将从类的本质、实现原理、常用方法到实战场景,系统梳理两者的核心知识点,附完整代码示例供直接使用。

一、String 类详解

1. String 类介绍

1.1 核心概述

String 类用于表示字符串,Java 中所有带双引号的字符串字面值(如 "abc"),本质上都是 String 类的实例。

例如:String s = "abc" 中,s 是对象名,"abc"String 类的具体实例。

1.2 三大核心特点
  • 不可变性 :字符串一旦创建,其值无法修改(底层数组被 final 修饰,地址锁死)。
  • 可共享性 :相同内容的字符串字面值会复用常量池中的同一个对象,减少内存消耗。
    例:String s1 = "abc"; String s2 = "abc"; 中,s1s2 指向常量池同一个对象。
  • 字面值即实例 :所有双引号包裹的字符串,默认都是 String 实例,无需手动 new 创建。

2. String 实现原理

2.1 底层存储结构(JDK 差异)

String 的底层是final 修饰的数组,但数组类型随 JDK 版本变化:

JDK 版本 底层数组类型 占用内存(单个元素)
JDK 8 及之前 private final char[] value 2 字节(char 类型)
JDK 9 及之后 private final byte[] value 1 字节(byte 类型)
2.2 为什么从 char[] 改为 byte[]

核心目的是节省内存

  • 大部分场景下,字符串由 ASCII 字符(如英文字母、数字)组成,用 1 字节的 byte 即可存储,而 char 固定占 2 字节,会造成内存浪费。
  • 对中文等非 ASCII 字符,byte[] 会通过编码(如 UTF-8)动态调整字节数,兼顾内存与兼容性。
2.3 不可变性的本质

底层数组被 final 修饰 → 数组地址无法修改,且 String 类未提供修改数组内容的方法(如 set 方法),因此字符串创建后值无法改变。

3. String 对象的创建方式

3.1 五种常见构造方法
构造方法 作用描述
String() 创建空字符串对象(无内容,长度为 0)
String(String str) 基于已有字符串创建新对象(如 new String("你好")
String(char[] chars) 将字符数组转为字符串(如 new String(new char[]{'a','b'})
String(byte[] bytes) 将字节数组按平台默认编码(如 GBK)转为字符串
String(byte[] bytes, int offset, int length) 截取字节数组的一部分(从 offset 索引开始,取 length 个元素)转为字符串
3.2 简化创建方式

直接通过字面值赋值:String 变量名 = "字符串内容",例如 String s = "abc",此方式会优先使用常量池,避免重复创建对象。

3.3 代码示例(含扩展构造)
java 复制代码
package com.code.day13;

public class Day13String {
    public static void main(String[] args) {
        // 1. 无参构造
        String s1 = new String();
        System.out.println("s1=" + s1); // 输出:s1=

        // 2. 基于已有字符串构造
        String s2 = new String("你好");
        System.out.println("s2=" + s2); // 输出:s2=你好

        // 3. 基于字符数组构造
        char[] chars = new char[]{'a', 'b', '3'};
        String s3 = new String(chars);
        System.out.println("s3=" + s3); // 输出:s3=ab3

        // 4. 基于字节数组构造(默认编码)
        byte[] bytes = new byte[]{97, 98, 99}; // ASCII 码:97=a,98=b,99=c
        String s4 = new String(bytes);
        System.out.println("s4=" + s4); // 输出:s4=abc

        // 5. 扩展构造:截取字节数组的一部分(从索引0开始,取2个元素)
        String s6 = new String(bytes, 0, 2);
        System.out.println("s6=" + s6); // 输出:s6=ab

        // 6. 扩展构造:截取字符数组的一部分(从索引1开始,取2个元素)
        String s7 = new String(chars, 1, 2);
        System.out.println("s7=" + s7); // 输出:s7=b3
    }
}
3.4 经典面试题:new String("abc") 创建几个对象?
  • 答案 :1 个或 2 个。
    • 若常量池中已存在 "abc",则仅创建 1 个对象(new 关键字在堆中创建的对象)。
    • 若常量池中不存在 "abc",则创建 2 个对象(1 个在常量池,1 个在堆中)。
3.5 字符串拼接的底层逻辑(面试高频)
java 复制代码
public class Demo05String {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "world";
        String s3 = "helloworld";
        String s4 = "hello" + "world"; // 字面值拼接,复用常量池
        String s5 = s1 + s2;           // 变量拼接,底层 new StringBuilder
        String s6 = s1 + "world";      // 含变量,底层 new StringBuilder

        System.out.println(s3 == s4); // true(均指向常量池 "helloworld")
        System.out.println(s3 == s5); // false(s5 是堆中新建对象)
        System.out.println(s3 == s6); // false(s6 是堆中新建对象)
    }
}

结论

  • 仅字面值拼接(如 "a"+"b"):复用常量池,不产生新对象。
  • 含变量的拼接(如 s1+"b"):底层通过 new StringBuilder 实现,会在堆中新建对象。

4. String 常用方法(分类详解)

4.1 判断类方法(对比内容)
方法 作用描述 场景示例
boolean equals(Object obj) 严格比较内容(区分大小写) 密码验证
boolean equalsIgnoreCase(String s) 比较内容(忽略大小写) 验证码验证(如 "Abc" 和 "abc")

代码示例

java 复制代码
public class Day13StringEquals {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "Hello";
        
        // 严格比较(区分大小写)
        boolean result = s1.equals(s2);
        System.out.println(result); // 输出:false
        
        // 忽略大小写比较
        boolean result1 = s1.equalsIgnoreCase(s2);
        System.out.println(result1); // 输出:true
    }
}

避坑提示

若比较时可能出现 null(如用户输入),需将确定非 null 的字符串放前面,避免空指针异常:
"abc".equals(s)(正确) vs s.equals("abc")(若 s 为 null 则报错)。

也可使用 Objects.equals("abc", s)(Java 7+),自动处理 null。

实战练习:登录验证

java 复制代码
package com.code.day13;

import java.util.Scanner;

public class Day13LianXi {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 正确账号密码
        String username = "root";
        String password = "root";
        
        // 最多3次登录机会
        for (int i = 0; i < 3; i++) {
            System.out.println("请输入用户名:");
            String name = sc.next();
            System.out.println("请输入密码:");
            String pwd = sc.next();
            
            // 验证(避免null)
            if (username.equals(name) && password.equals(pwd)) {
                System.out.println("登陆成功");
                break;
            } else {
                if (i == 2) {
                    System.out.println("账号冻结");
                } else {
                    System.out.println("用户名或密码错误");
                }
            }
        }
        sc.close();
    }
}
4.2 获取类方法(提取内容/长度)
方法 作用描述
String concat(String str) 拼接字符串,返回新串(如 "a".concat("b") → "ab")
char charAt(int index) 根据索引获取字符(索引从 0 开始)
int indexOf(String str) 获取子串第一次出现的索引(无则返回 -1)
String substring(int beginIndex) beginIndex 截取到末尾,返回新串
String substring(int begin, int end) 截取 [begin, end) 区间(含头不含尾)
int length() 获取字符串长度(字符个数)

代码示例

java 复制代码
public class Day13StringGet {
    public static void main(String[] args) {
        String s = "abcdefg";
        
        // 拼接
        String newStr1 = s.concat("hahaha");
        System.out.println(newStr1); // 输出:abcdefghahaha
        
        // 按索引取字符
        char data = s.charAt(2);
        System.out.println(data); // 输出:c(索引2对应第3个字符)
        
        // 子串索引
        System.out.println(s.indexOf("c")); // 输出:2
        
        // 截取(从索引2到末尾)
        System.out.println(s.substring(2)); // 输出:cdefg
        
        // 截取(索引2到4,含2不含4)
        System.out.println(s.substring(2, 4)); // 输出:cd
        
        // 长度
        System.out.println(s.length()); // 输出:7
    }
}

实战练习:遍历字符串

java 复制代码
public class Day13StringGetLianXi {
    public static void main(String[] args) {
        String s = "abcdefg";
        // 遍历每一个字符
        for (int i = 0; i < s.length(); i++) {
            System.out.println(s.charAt(i));
        }
    }
}
4.3 转换类方法(格式转换)
方法 作用描述
char[] toCharArray() 将字符串转为字符数组
byte[] getBytes() 按默认编码转为字节数组
byte[] getBytes(String charset) 按指定编码(如 "GBK")转为字节数组
String replace(CharSequence old, CharSequence new) 替换子串(如 "a".replace("a","b") → "b")

代码示例

java 复制代码
import java.util.Arrays;
import java.io.UnsupportedEncodingException;

public class Day13StringZhuan {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String s = "abcdefg";
        
        // 1. 转字符数组
        char[] charArray = s.toCharArray();
        System.out.println(charArray); // 输出:abcdefg(print直接输出数组内容)
        
        // 2. 转字节数组(默认编码 UTF-8)
        byte[] bytes = s.getBytes();
        System.out.println(Arrays.toString(bytes)); // 输出:[97, 98, 99, 100, 101, 102, 103]
        
        // 3. 替换子串
        String newStr = s.replace("a", "z");
        System.out.println(newStr); // 输出:zbcdefg
        
        // 4. 按指定编码转字节数组(GBK)
        byte[] gbks = "你".getBytes("GBK"); // 中文 GBK 占 2 字节
        System.out.println(Arrays.toString(gbks)); // 输出:[-60, -29]
    }
}

实战练习:统计字符类型次数

java 复制代码
import java.util.Scanner;

public class Day13StringZhuanLianXi {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String str = scanner.next();
        
        // 统计变量
        int big = 0;   // 大写字母
        int small = 0; // 小写字母
        int number = 0;// 数字
        
        // 转字符数组遍历
        char[] charArray = str.toCharArray();
        for (char c : charArray) {
            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);
        scanner.close();
    }
}
4.4 分割类方法(拆分字符串)

String[] split(String regex):按指定规则(正则表达式)拆分字符串,返回字符串数组。

代码示例

java 复制代码
import java.util.Arrays;

public class Day13StringSplit {
    public static void main(String[] args) {
        // 1. 按逗号分割
        String s = "a,java,python";
        String[] arr = s.split(",");
        System.out.println(Arrays.toString(arr)); // 输出:[a, java, python]
        
        // 增强for遍历
        for (String element : arr) {
            System.out.println(element);
        }
        
        // 2. 按点分割(注意:. 在正则中代表任意字符,需转义为 \\.)
        String s1 = "java.txt";
        String[] split = s1.split("\\.");
        for (String el : split) {
            System.out.println(el); // 输出:java、txt
        }
    }
}
4.5 其他常用方法
方法 作用描述
boolean contains(String s) 判断是否包含子串(如 "abc".contains("ab") → true)
boolean startsWith(String s) 判断是否以子串开头(如 "abc".startsWith("a") → true)
boolean endsWith(String s) 判断是否以子串结尾(如 "abc".endsWith("c") → true)
String toLowerCase() 转为小写(如 "ABC".toLowerCase() → "abc")
String toUpperCase() 转为大写(如 "abc".toUpperCase() → "ABC")
String trim() 去除两端空格(中间空格保留,如 " a b " → "a b")

代码示例

java 复制代码
public class Day13StringOtherMethod {
    public static void main(String[] args) {
        String s = "abcdefg";
        
        // 包含子串
        System.out.println(s.contains("abcd")); // 输出:true
        
        // 开头/结尾判断
        System.out.println(s.startsWith("ab")); // 输出:true
        System.out.println(s.endsWith("fg"));   // 输出:true
        
        // 大小写转换
        String s1 = "ABCDEFG";
        System.out.println(s1.toLowerCase()); // 输出:abcdefg
        System.out.println(s.toUpperCase());  // 输出:ABCDEFG
        
        // 去空格
        String s2 = "   a  b  c   ";
        System.out.println(s2.trim());        // 输出:a  b  c(两端空格去除)
        System.out.println(s2.replace(" ", "")); // 输出:abc(所有空格去除)#### 4.6 String 新特性:文本块(Java 15+ 正式特性)
在 Java 15 之前,编写多行字符串(如 HTML、JSON、SQL)需手动拼接换行符(`\n`)和转义引号(`\"`),代码可读性差且易出错。**文本块**通过三引号(`"""`)解决此问题,支持多行字符串自动转义,保留格式。

##### 核心优势
- 无需手动转义换行符、双引号;
- 直接保留字符串原始格式,代码更易读;
- 支持嵌套双引号(无需转义)。

##### 代码示例
```java
public class Day13StringKuai {
    public static void main(String[] args) {
        // 传统方式:手动拼接换行符和转义引号
        String oldHtml = "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>Title</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "    <p>刘大胆</p>\n" +
                "</body>\n" +
                "</html>";
        System.out.println("传统方式输出:");
        System.out.println(oldHtml);

        // 文本块方式:三引号包裹,保留原始格式
        String newHtml = """
                <!DOCTYPE html>
                <html lang="en">
                <head>
                    <meta charset="UTF-8">
                    <title>Title</title>
                </head>
                <body>
                    <p>刘大胆</p>
                </body>
                </html>
                """;
        System.out.println("\n文本块方式输出:");
        System.out.println(newHtml);

        // 嵌套双引号示例
        String story = """
                Elly said,"Maybe I was a bird in another life."
                Noah said,"If you're a bird , I'm a bird."
                """;
        System.out.println("\n嵌套双引号文本块:");
        System.out.println(story);
    }
}
输出结果(格式完全一致)
html 复制代码
传统方式输出:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>刘大胆</p>
</body>
</html>

文本块方式输出:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>刘大胆</p>
</body>
</html>

嵌套双引号文本块:
Elly said,"Maybe I was a bird in another life."
Noah said,"If you're a bird , I'm a bird."

二、StringBuilder 类详解

1. StringBuilder 类介绍

1.1 核心概述

StringBuilder可变字符序列 ,底层基于未被 final 修饰的字节数组(缓冲区)实现,核心作用是高效拼接字符串 ,解决 String 拼接时频繁创建对象的问题。

1.2 为什么需要 StringBuilder?

String 不可变,每次拼接都会生成新对象(如 s = s + "a" 会创建 2 个新对象),频繁拼接时:

  • 内存占用高(大量临时对象);
  • 拼接效率低(对象创建+垃圾回收耗时)。

StringBuilder 通过缓冲区复用解决此问题:拼接内容直接存入底层数组,仅在数组容量不足时扩容,不频繁创建新对象。

1.3 核心特点
  • 可变缓冲区 :底层是未被 final 修饰的 byte[],支持动态扩容;
  • 默认容量:初始缓冲区长度为 16(可通过构造方法指定初始容量);
  • 自动扩容机制
    1. 若需存储的内容长度 ≤ 原容量的 2 倍 + 2 → 按"2 倍 + 2"扩容;
    2. 若需存储的内容长度 > 原容量的 2 倍 + 2 → 按"实际需要的长度"扩容;
  • 效率高 :无线程安全锁(区别于 StringBuffer),拼接速度更快。

2. StringBuilder 的使用

2.1 构造方法
构造方法 作用描述
StringBuilder() 创建空的 StringBuilder,初始容量 16
StringBuilder(String str) 基于已有字符串创建,初始容量 = 16 + 字符串长度
2.2 核心方法(重点)
方法 作用描述 返回值类型
append(任意类型) 拼接内容到缓冲区(支持 int、String、char 等) StringBuilder(自身)
reverse() 反转缓冲区中的内容 StringBuilder(自身)
toString() StringBuilder 转为 String 类型 String
length() 获取缓冲区中有效字符的长度 int

关键特性:append()reverse() 方法返回自身对象 ,支持链式调用(如 sb.append("a").append("b"))。

2.3 代码示例
java 复制代码
public class Day13StringBuilder {
    public static void main(String[] args) {
        // 1. 空构造(初始容量 16)
        StringBuilder sb = new StringBuilder();
        System.out.println("空 StringBuilder:" + sb); // 输出:空 StringBuilder:

        // 2. 基于字符串构造(初始容量 16 + "Hello" 长度 5 = 21)
        StringBuilder sb1 = new StringBuilder("Hello");
        System.out.println("初始 StringBuilder:" + sb1); // 输出:初始 StringBuilder:Hello

        // 3. 链式拼接(append 返回自身,支持连续调用)
        sb1.append("刘大胆").append("大胃袋").append(123);
        System.out.println("拼接后:" + sb1); // 输出:拼接后:Hello刘大胆大胃袋123

        // 4. 反转内容
        sb1.reverse();
        System.out.println("反转后:" + sb1); // 输出:反转后:321袋胃大胆大刘olleH

        // 5. 转为 String 类型(后续可调用 String 方法)
        String result = sb1.toString();
        System.out.println("转为 String 后:" + result); // 输出:转为 String 后:321袋胃大胆大刘olleH

        // 6. 获取有效长度
        System.out.println("有效字符长度:" + sb1.length()); // 输出:有效字符长度:13
    }
}

3. StringBuilder 实战练习:判断回文

回文定义 :正读和反读一致的字符串(如"上海自来水来自海上""abcba")。
实现思路 :用 StringBuilder 反转字符串,再与原字符串比较。

代码示例
java 复制代码
import java.util.Scanner;

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

        // 1. 用 StringBuilder 反转字符串
        StringBuilder sb = new StringBuilder(original);
        StringBuilder reversedSb = sb.reverse(); // 反转后仍是 StringBuilder 类型

        // 2. 转为 String 后与原字符串比较(注意:必须转 String,否则类型不匹配)
        String reversedStr = reversedSb.toString();
        if (original.equals(reversedStr)) {
            System.out.println("该字符串是回文");
        } else {
            System.out.println("该字符串不是回文");
        }

        sc.close();
    }
}
测试结果
复制代码
// 输入:abcba
请输入字符串:
abcba
该字符串是回文

// 输入:abcd
请输入字符串:
abcd
该字符串不是回文

4. String、StringBuilder、StringBuffer 区别(面试重点)

三者核心功能都是处理字符串,但设计目标不同,关键差异集中在可变性线程安全效率上。

对比维度 String StringBuilder StringBuffer
可变性 不可变(底层 final 数组) 可变(底层非 final 数组) 可变(底层非 final 数组)
线程安全 安全(不可变天然线程安全) 不安全(无同步锁) 安全(方法加 synchronized 锁)
拼接效率 低(频繁创建新对象) 高(缓冲区复用,无锁) 中(缓冲区复用,有锁开销)
底层实现 final char[](JDK8)/final byte[](JDK9+) byte[](非 final) byte[](非 final)
适用场景 字符串不频繁修改(如常量定义) 单线程下频繁拼接(如普通业务逻辑) 多线程下频繁拼接(如并发日志打印)
选型建议
  1. 若字符串无需修改(如配置项、固定文本)→ 用 String
  2. 若单线程环境下需频繁拼接(如循环拼接、动态生成文本)→ 用 StringBuilder(优先选,效率最高);
  3. 若多线程环境下需频繁拼接(如多线程日志、并发生成数据)→ 用 StringBuffer

三、总结

  1. String:不可变字符串,适合存储固定内容,拼接效率低,需注意常量池复用机制;
  2. StringBuilder:可变字符序列,单线程高效拼接首选,底层缓冲区自动扩容,支持链式调用;
  3. 文本块(Java 15+):简化多行字符串编写,提升代码可读性,无需手动转义;
  4. 三者差异核心在"可变性"和"线程安全",需根据实际场景(是否修改、是否多线程)选择合适的类。

通过本文的梳理,相信你已掌握 String 与 StringBuilder 的核心用法与底层逻辑,在实际开发中能更高效地处理字符串操作!

相关推荐
Poppy .^0^3 小时前
Tomcat 全面指南:从目录结构到应用部署与高级配置
java·tomcat
Ares-Wang3 小时前
Javascript》》JS》》ES6》 Map、Set、WeakSet、WeakMap
开发语言·javascript·es6
XMYX-03 小时前
Jenkins 拉取 Git 仓库时报错:there are still refs under ‘refs/remotes/origin/release‘
git·elasticsearch·jenkins
一 乐3 小时前
在线宠物用品|基于vue的在线宠物用品交易网站(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·|在线宠物用品交易网站
shepherd1113 小时前
深入解析Flowable工作流引擎:从原理到实践
java·后端·工作流引擎
l5657583 小时前
第五十天(SpringBoot栈&Actuator&Swagger&HeapDump&提取自动化)
java·spring boot·spring
星梦清河3 小时前
宋红康 JVM 笔记 Day09|方法区
jvm·笔记
AI 嗯啦3 小时前
爬虫-----最全的爬虫库介绍(一篇文章让你成为爬虫大佬,爬你想爬)
开发语言·爬虫·python
沐宇熙83 小时前
交互式JVM运行过程可视化系统
jvm