
字符串是 Java 编程中最常用的数据类型之一,无论是用户输入处理、数据展示还是日志记录,都离不开字符串操作。本章将系统讲解 Java 中字符串的核心类(String、StringBuilder、StringBuffer)的使用方法、特性及实战技巧,帮助你掌握字符串处理的精髓。

String类是 Java 中处理字符串的核心类,位于java.lang包下,无需手动导入即可使用。字符串本质上是字符序列,在 Java 中被定义为char类型的数组(JDK9 + 后改为byte数组优化存储)。
Java 中创建String对象有两种常用方式,两者在内存存储上有本质区别:
String str1 = "Hello Java"; // 字符串常量,存储在常量池new关键字创建String str2 = new String("Hello Java"); // 对象存储在堆内存,字符串内容在常量池 两者区别:直接赋值会优先检查常量池,若存在相同字符串则直接引用,避免重复创建;new关键字会强制在堆中创建新对象,即使常量池已有相同内容。
代码示例:
public class StringCreateDemo {
public static void main(String[] args) {
// 方式1:直接赋值(常量池)
String s1 = "Java";
String s2 = "Java";
// 方式2:new关键字(堆内存)
String s3 = new String("Java");
String s4 = new String("Java");
// 比较引用地址(==)
System.out.println("s1 == s2: " + (s1 == s2)); // true(同常量池引用)
System.out.println("s3 == s4: " + (s3 == s4)); // false(不同堆对象)
System.out.println("s1 == s3: " + (s1 == s3)); // false(常量池vs堆)
// 比较内容(equals())
System.out.println("s1.equals(s3): " + s1.equals(s3)); // true(内容相同)
}
}
String类提供了丰富的方法用于字符串基本操作,常用的有:
方法名 | 功能描述 |
|---|---|
length() | 获取字符串长度 |
charAt(int index) | 获取指定索引的字符 |
concat(String str) | 拼接字符串(等价于+) |
isEmpty() | 判断字符串是否为空 |
代码示例:
public class StringBasicOps {
public static void main(String[] args) {
String str = "Hello World";
// 获取长度
System.out.println("字符串长度:" + str.length()); // 输出:11
// 获取指定索引字符(索引从0开始)
char c = str.charAt(6);
System.out.println("索引6的字符:" + c); // 输出:W
// 字符串拼接
String newStr = str.concat("!").concat(" Java is great");
System.out.println("拼接后:" + newStr); // 输出:Hello World! Java is great
// 判断是否为空
String emptyStr = "";
System.out.println("emptyStr是否为空:" + emptyStr.isEmpty()); // 输出:true
}
}
常用查找方法用于定位子字符串或字符的位置:
方法名 | 功能描述 |
|---|---|
indexOf(String str) | 返回子串首次出现的索引,无则返回 - 1 |
lastIndexOf(String str) | 返回子串最后出现的索引,无则返回 - 1 |
startsWith(String prefix) | 判断是否以指定前缀开头 |
endsWith(String suffix) | 判断是否以指定后缀结尾 |
代码示例:
public class StringSearch {
public static void main(String[] args) {
String str = "Java Programming: Java is fun!";
// 查找子串首次出现位置
int firstPos = str.indexOf("Java");
System.out.println("Java首次出现位置:" + firstPos); // 输出:0
// 从索引5开始查找
int posFrom5 = str.indexOf("Java", 5);
System.out.println("从索引5开始Java出现位置:" + posFrom5); // 输出:19
// 查找最后出现位置
int lastPos = str.lastIndexOf("Java");
System.out.println("Java最后出现位置:" + lastPos); // 输出:19
// 判断前缀和后缀
boolean startWithJava = str.startsWith("Java");
boolean endWithFun = str.endsWith("fun!");
System.out.println("以Java开头:" + startWithJava); // 输出:true
System.out.println("以fun!结尾:" + endWithFun); // 输出:true
}
}
通过toCharArray()方法可将字符串转换为字符数组,便于逐个操作字符:
代码示例:
public class StringToArray {
public static void main(String[] args) {
String str = "Hello";
// 字符串转字符数组
char[] chars = str.toCharArray();
// 遍历字符数组
System.out.println("字符数组内容:");
for (int i = 0; i < chars.length; i++) {
System.out.println("索引" + i + ":" + chars[i]);
}
// 字符数组转字符串
String newStr = new String(chars);
System.out.println("字符数组转回字符串:" + newStr); // 输出:Hello
}
}
字符串比较是高频操作,需区分引用比较和内容比较:
比较方式 | 说明 |
|---|---|
== | 比较对象引用地址(是否为同一对象) |
equals() | 比较字符串内容是否相同(区分大小写) |
equalsIgnoreCase() | 比较内容是否相同(不区分大小写) |
compareTo() | 按字典顺序比较,返回差值(正数 / 负数 / 0) |
代码示例:
public class StringCompare {
public static void main(String[] args) {
String s1 = "apple";
String s2 = "apple";
String s3 = new String("apple");
String s4 = "APPLE";
// 引用比较(==)
System.out.println("s1 == s2: " + (s1 == s2)); // true(同常量池对象)
System.out.println("s1 == s3: " + (s1 == s3)); // false(不同对象)
// 内容比较(equals())
System.out.println("s1.equals(s3): " + s1.equals(s3)); // true(内容相同)
System.out.println("s1.equals(s4): " + s1.equals(s4)); // false(大小写不同)
// 忽略大小写比较
System.out.println("s1.equalsIgnoreCase(s4): " + s1.equalsIgnoreCase(s4)); // true
// 字典顺序比较(compareTo)
System.out.println("s1.compareTo(s4): " + s1.compareTo(s4)); // 32('a'比'A'大32)
System.out.println("s1.compareTo(s2): " + s1.compareTo(s2)); // 0(相等)
}
}
split(String regex) 按正则表达式拆分字符串为数组String.join(CharSequence delimiter, CharSequence... elements) 按分隔符拼接数组代码示例:
public class StringSplitJoin {
public static void main(String[] args) {
// 字符串拆分
String str = "啊阿狸不会拉杆,20,男,Java开发";
String[] parts = str.split(","); // 按逗号拆分
System.out.println("拆分结果:");
for (String part : parts) {
System.out.println(part);
}
// 字符串组合
String[] fruits = {"苹果", "香蕉", "橙子"};
String fruitStr = String.join(" | ", fruits); // 用" | "拼接
System.out.println("组合结果:" + fruitStr); // 输出:苹果 | 香蕉 | 橙子
// 复杂拆分(按数字拆分)
String text = "Hello123World456Java";
String[] textParts = text.split("\\d+"); // 按1个以上数字拆分(注意转义)
System.out.println("按数字拆分结果:");
for (String p : textParts) {
System.out.println(p);
}
}
}
String对象一旦创建,其内容不可修改!所有看似修改的操作(如拼接、替换)都会创建新的String对象。
原理:String类内部的字符数组被final修饰,无法修改引用指向的数组;且没有提供修改数组元素的方法。
代码演示不变性:
public class StringImmutable {
public static void main(String[] args) {
String str = "Hello";
System.out.println("原字符串地址:" + System.identityHashCode(str)); // 原地址
// 看似修改,实际创建新对象
str = str + " World";
System.out.println("修改后字符串地址:" + System.identityHashCode(str)); // 新地址
// 验证:通过反射强制修改(仅做教学演示,生产环境严禁使用)
try {
// 获取String类的value字段(JDK 9+为byte[],JDK 8及以下为char[])
java.lang.reflect.Field field = String.class.getDeclaredField("value");
field.setAccessible(true); // 暴力访问私有字段
// 根据JDK版本处理不同的内部存储结构
if (field.getType() == byte[].class) {
// JDK 9+使用byte[]存储(UTF-8编码)
byte[] value = (byte[]) field.get(str);
// 原字符串是"Hello World",索引5的位置是空格(ASCII码32)
value[5] = (byte) '!'; // 将空格替换为感叹号
} else {
// JDK 8及以下使用char[]存储
char[] value = (char[]) field.get(str);
value[5] = '!';
}
System.out.println("反射修改后字符串:" + str); // 输出:Hello!World
} catch (NoSuchFieldException e) {
System.err.println("未找到value字段:" + e.getMessage());
} catch (IllegalAccessException e) {
System.err.println("访问被拒绝,请检查VM参数是否正确配置:");
System.err.println("需要添加JVM参数:--add-opens java.base/java.lang=ALL-UNNAMED");
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:反射修改字符串是破坏封装的危险操作,实际开发中严禁使用!
main方法的String[] args参数用于接收命令行传递的参数,可实现程序动态输入。
代码示例:
public class CommandLineArgs {
public static void main(String[] args) {
// 输出参数数量
System.out.println("参数个数:" + args.length);
// 遍历参数
for (int i = 0; i < args.length; i++) {
System.out.println("参数" + i + ":" + args[i]);
}
// 示例:计算参数中数字的和(若参数为数字)
if (args.length == 0) {
System.out.println("请传递数字参数,例如:java CommandLineArgs 10 20 30");
return;
}
int sum = 0;
for (String arg : args) {
try {
sum += Integer.parseInt(arg);
} catch (NumberFormatException e) {
System.out.println("参数'" + arg + "'不是有效数字,已忽略");
}
}
System.out.println("数字参数的和:" + sum);
}
}
运行方法:
javac CommandLineArgs.javajava CommandLineArgs 10 20 30 abc 40Java 提供printf方法和Formatter类实现格式化输出,支持多种数据类型的格式化。
占位符 | 描述 | 示例 |
|---|---|---|
%d | 整数 | %5d(占 5 位,右对齐) |
%f | 浮点数 | %.2f(保留 2 位小数) |
%s | 字符串 | %10s(占 10 位,右对齐) |
%c | 字符 | %c |
%b | 布尔值 | %b |
%tF | 日期(年 - 月 - 日) | %tF |
代码示例:
import java.util.Date;
public class FormatOutput {
public static void main(String[] args) {
String name = "啊阿狸不会拉杆";
int age = 20;
double salary = 9876.5432;
boolean isMarried = false;
Date today = new Date();
// 使用printf格式化输出
System.out.printf("姓名:%s,年龄:%d岁%n", name, age);
System.out.printf("薪资:%.2f元/月(保留2位小数)%n", salary);
System.out.printf("是否已婚:%b%n", isMarried);
System.out.printf("当前日期:%tF%n", today); // %tF 格式为年-月-日
// 控制宽度和对齐
System.out.println("\n===== 对齐演示 =====");
System.out.printf("|%10s|%5d|%10.2f|%n", "雷军", 25, 9876.54); // 右对齐
System.out.printf("|%-10s|%-5d|%-10.2f|%n", "萧炎", 30, 12345.67); // 左对齐(加-)
}
}输出结果:

由于String的不变性,频繁修改字符串会产生大量临时对象,效率低下。StringBuilder和StringBuffer是可变字符串类,适合频繁修改场景。
@startuml
class Object {
+ equals()
+ hashCode()
+ toString()
}
class CharSequence {
+ length(): int
+ charAt(int): char
+ subSequence(int, int): CharSequence
+ toString(): String
}
class String {
- final char[] value
+ length(): int
+ equals(): boolean
+ concat(): String
+ indexOf(): int
}
class AbstractStringBuilder {
- char[] value
- int count
+ length(): int
+ append(): AbstractStringBuilder
+ insert(): AbstractStringBuilder
+ delete(): AbstractStringBuilder
}
class StringBuilder {
+ StringBuilder()
+ append(): StringBuilder
+ insert(): StringBuilder
+ delete(): StringBuilder
+ reverse(): StringBuilder
}
class StringBuffer {
+ StringBuffer()
+ synchronized append(): StringBuffer
+ synchronized insert(): StringBuffer
+ synchronized delete(): StringBuffer
+ synchronized reverse(): StringBuffer
}
Object <|-- String
Object <|-- AbstractStringBuilder
CharSequence <|-- String
CharSequence <|-- StringBuilder
CharSequence <|-- StringBuffer
AbstractStringBuilder <|-- StringBuilder
AbstractStringBuilder <|-- StringBuffer
@enduml
特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
可变性 | 不可变 | 可变 | 可变 |
线程安全 | 无 | 非线程安全 | 线程安全(方法加synchronized) |
效率 | 低(频繁修改时) | 高 | 中(同步开销) |
适用场景 | 少量修改 / 常量字符串 | 单线程频繁修改 | 多线程频繁修改 |
public class StringBuilderCreate {
public static void main(String[] args) {
// 方式1:创建空对象
StringBuilder sb1 = new StringBuilder();
// 方式2:指定初始容量(推荐,避免扩容开销)
StringBuilder sb2 = new StringBuilder(100); // 初始容量100
// 方式3:用字符串初始化
StringBuilder sb3 = new StringBuilder("Hello");
System.out.println("sb3初始内容:" + sb3); // 输出:Hello
System.out.println("当前长度:" + sb3.length()); // 输出:5
System.out.println("当前容量:" + sb3.capacity()); // 输出:21(默认容量=初始字符串长度+16)
}
}
常用方法:
append():追加内容(支持所有数据类型)insert(int offset, ...):在指定位置插入内容delete(int start, int end):删除指定范围内容reverse():反转字符串toString():转换为String对象代码示例:
public class StringBuilderOps {
public static void main(String[] args) {
// 初始化
StringBuilder sb = new StringBuilder("Java");
System.out.println("初始内容:" + sb); // 输出:Java
// 追加内容
sb.append(" Programming");
sb.append(" is fun!");
System.out.println("追加后:" + sb); // 输出:Java Programming is fun!
// 插入内容
sb.insert(5, "SE 8 "); // 在索引5处插入"SE 8 "
System.out.println("插入后:" + sb); // 输出:Java SE 8 Programming is fun!
// 修改指定位置字符
sb.setCharAt(5, 's'); // 将索引5的'S'改为's'
System.out.println("修改字符后:" + sb); // 输出:Java se 8 Programming is fun!
// 删除内容(删除" is fun!")
int start = sb.indexOf(" is fun!");
if (start != -1) {
sb.delete(start, start + " is fun!".length());
}
System.out.println("删除后:" + sb); // 输出:Java se 8 Programming
// 反转
sb.reverse();
System.out.println("反转后:" + sb); // 输出:gnimmargorP 8 es avaJ
// 转换为String
String result = sb.toString();
System.out.println("最终String:" + result);
}
}
Java 中String的+运算符是语法糖,编译后会被转换为StringBuilder的append操作(但有例外)。
示例解析:
public class StringPlusOverload {
public static void main(String[] args) {
// 编译前
String a = "Hello";
String b = "World";
String c = a + b + "!";
// 编译后等价于
String d = new StringBuilder().append(a).append(b).append("!").toString();
System.out.println(c); // 输出:HelloWorld!
System.out.println(d); // 输出:HelloWorld!
// 注意:循环中使用+会创建多个StringBuilder,效率低
long start = System.currentTimeMillis();
String loopStr = "";
for (int i = 0; i < 10000; i++) {
loopStr += i; // 每次循环创建新StringBuilder
}
System.out.println("循环用+耗时:" + (System.currentTimeMillis() - start) + "ms");
// 推荐:循环中显式用StringBuilder
start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String loopStr2 = sb.toString();
System.out.println("循环用StringBuilder耗时:" + (System.currentTimeMillis() - start) + "ms");
}
}输出结果:

结论:循环中拼接字符串必须用StringBuilder,避免性能问题!
printf方法支持多种格式占位符,便于格式化展示数据。StringBuilder:非线程安全,效率高,适合单线程。StringBuffer:线程安全,效率较低,适合多线程。StringBuilder,避免用String的+运算符。实现一个工具类,提供字符串反转功能,支持多种输入方式。
import java.util.Scanner;
/**
* 字符串反转工具类
*/
public class StringReverseTool {
/**
* 反转字符串(StringBuilder实现)
* @param str 待反转字符串
* @return 反转后的字符串
*/
public static String reverseWithBuilder(String str) {
if (str == null) {
return null;
}
return new StringBuilder(str).reverse().toString();
}
/**
* 反转字符串(手动实现)
* @param str 待反转字符串
* @return 反转后的字符串
*/
public static String reverseManual(String str) {
if (str == null) {
return null;
}
char[] chars = str.toCharArray();
int left = 0;
int right = chars.length - 1;
// 交换左右指针的字符
while (left < right) {
char temp = chars[left];
chars[left] = chars[right];
chars[right] = temp;
left++;
right--;
}
return new String(chars);
}
public static void main(String[] args) {
// 方式1:从命令行参数获取
if (args.length > 0) {
String input = String.join(" ", args);
System.out.println("命令行输入反转:" + reverseWithBuilder(input));
}
// 方式2:从控制台输入
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要反转的字符串:");
String userInput = scanner.nextLine();
scanner.close();
System.out.println("Builder反转结果:" + reverseWithBuilder(userInput));
System.out.println("手动反转结果:" + reverseManual(userInput));
}
}
统计字符串中指定字符(或所有字符)的出现次数。
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* 字符出现次数统计工具
*/
public class CharCountTool {
/**
* 统计字符串中所有字符的出现次数
* @param str 目标字符串
* @return 字符-次数映射表
*/
public static Map<Character, Integer> countAllChars(String str) {
Map<Character, Integer> countMap = new HashMap<>();
if (str == null || str.isEmpty()) {
return countMap;
}
for (char c : str.toCharArray()) {
// 若已存在则次数+1,否则初始化为1
countMap.put(c, countMap.getOrDefault(c, 0) + 1);
}
return countMap;
}
/**
* 统计指定字符的出现次数
* @param str 目标字符串
* @param target 目标字符
* @return 出现次数
*/
public static int countTargetChar(String str, char target) {
if (str == null || str.isEmpty()) {
return 0;
}
int count = 0;
for (char c : str.toCharArray()) {
if (c == target) {
count++;
}
}
return count;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入字符串:");
String input = scanner.nextLine();
// 统计所有字符
Map<Character, Integer> allCounts = countAllChars(input);
System.out.println("\n所有字符出现次数:");
for (Map.Entry<Character, Integer> entry : allCounts.entrySet()) {
System.out.println("字符 '" + entry.getKey() + "':" + entry.getValue() + "次");
}
// 统计指定字符
System.out.print("\n请输入要查询的字符:");
char target = scanner.nextLine().charAt(0); // 简化处理,假设输入单个字符
int targetCount = countTargetChar(input, target);
System.out.println("字符 '" + target + "' 出现次数:" + targetCount + "次");
scanner.close();
}
}
本章详细讲解了 Java 字符串的核心操作和最佳实践,掌握String、StringBuilder、StringBuffer的特性和适用场景,能显著提升字符串处理的效率和代码质量。建议多动手练习文中的示例,加深对字符串操作的理解!
如果有任何问题或建议,欢迎在评论区留言交流~