当前位置: 首页 > news >正文

《Java 程序设计》第 6 章 - 字符串

引言

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

本章知识思维导图

6.1 String 类

  String类是 Java 中处理字符串的核心类,位于java.lang包下,无需手动导入即可使用。字符串本质上是字符序列,在 Java 中被定义为char类型的数组(JDK9 + 后改为byte数组优化存储)。

6.1.1 创建 String 类对象

Java 中创建String对象有两种常用方式,两者在内存存储上有本质区别:

方式 1:直接赋值(推荐)
String str1 = "Hello Java"; // 字符串常量,存储在常量池
方式 2:通过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(内容相同)}
}

6.1.2 字符串基本操作

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}
}

6.1.3 字符串查找

常用查找方法用于定位子字符串或字符的位置

方法名功能描述
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); // 输出:trueSystem.out.println("以fun!结尾:" + endWithFun); // 输出:true}
}

6.1.4 字符串转换为数组

通过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}
}

6.1.5 字符串比较

字符串比较是高频操作,需区分引用比较和内容比较

比较方式说明
==比较对象引用地址(是否为同一对象)
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(相等)}
}

6.1.6 字符串的拆分与组合

  • 拆分: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);}}
}

6.1.7 String 对象的不变性

    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();}}
}

注意:反射修改字符串是破坏封装的危险操作,实际开发中严禁使用!

6.1.8 命令行参数

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);}
}

运行方法

  1. 编译:javac CommandLineArgs.java
  2. 运行并传参:java CommandLineArgs 10 20 30 abc 40
  3. 输出:参数和为 100(忽略无效参数 abc)

6.2 格式化输出

Java 提供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); // 左对齐(加-)}
}

输出结果

6.3 StringBuilder 类和 StringBuffer 类

         由于String的不变性,频繁修改字符串会产生大量临时对象,效率低下。StringBuilderStringBuffer是可变字符串类,适合频繁修改场景。

类图(PlantUML)

@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

核心区别

特性StringStringBuilderStringBuffer
可变性不可变可变可变
线程安全非线程安全线程安全(方法加synchronized
效率低(频繁修改时)中(同步开销)
适用场景少量修改 / 常量字符串单线程频繁修改多线程频繁修改

6.3.1 创建 StringBuilder 对象

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); // 输出:HelloSystem.out.println("当前长度:" + sb3.length()); // 输出:5System.out.println("当前容量:" + sb3.capacity()); // 输出:21(默认容量=初始字符串长度+16)}
}

6.3.2 StringBuilder 的访问和修改

常用方法:

  • 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// 转换为StringString result = sb.toString();System.out.println("最终String:" + result);}
}

6.3.3 运算符 “+” 的重载

Java 中String+运算符是语法糖,编译后会被转换为StringBuilderappend操作(但有例外)。

示例解析

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");// 推荐:循环中显式用StringBuilderstart = 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,避免性能问题!

6.4 小结

  1. String 类:不可变字符串,适合内容固定的场景,核心方法包括创建、查找、比较、拆分等。
  2. String 的不变性:修改字符串会创建新对象,频繁修改需谨慎
  3. 格式化输出printf方法支持多种格式占位符,便于格式化展示数据。
  4. 可变字符串
    • StringBuilder:非线程安全,效率高,适合单线程
    • StringBuffer:线程安全,效率较低,适合多线程
  5. 性能优化:频繁修改字符串优先用StringBuilder,避免用String+运算符。

编程练习

练习 1:字符串反转工具

实现一个工具类,提供字符串反转功能,支持多种输入方式。

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));}
}

练习 2:统计字符出现次数

统计字符串中指定字符(或所有字符)的出现次数。

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,否则初始化为1countMap.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 字符串的核心操作和最佳实践,掌握StringStringBuilderStringBuffer的特性和适用场景,能显著提升字符串处理的效率和代码质量。建议多动手练习文中的示例,加深对字符串操作的理解!

如果有任何问题或建议,欢迎在评论区留言交流~

http://www.dtcms.com/a/299492.html

相关文章:

  • 智慧城市多目标追踪精度↑32%:陌讯动态融合算法实战解析
  • 【Canvas与旗帜】条纹版大明三辰旗
  • 神经网络中的反向传播原理:驱动智能的核心引擎
  • k8s:将打包好的 Kubernetes 集群镜像推送到Harbor私有镜像仓库
  • 电子电气架构 --- 高阶智能驾驶对E/E架构的新要求
  • Java操作Excel文档
  • Spring的深入浅出(6)--使用AOP的思想改造转账案例
  • 人形机器人指南(八)操作
  • 手动开发一个串口调试工具(二):Qt 串口类基本认识与使用
  • 基于 ThinkPHP 开发的垂直化网址导航
  • Linux进程地址空间:深入探索其结构与机制
  • 元宇宙新基建:重塑数字市场的“超大陆”边界
  • 【Android】内容提供器
  • 7️⃣ 递归函数
  • 【AcWing 835题解】滑动窗口
  • 数据结构 双向链表
  • greenhills编译出错问题
  • C++学习之深入学习模板(进阶)
  • SAPUI5 树形表格TreeTable示例
  • Spring AI(14)——文本分块优化
  • java之23种设计模式
  • 设计模式:Memento 模式详解
  • 简单实现支付密码的页面及输入效果
  • 面条式代码(Spaghetti Code)
  • Java高级之基于Java Attach与Byte-Buddy实现SQL语句增强
  • JWT安全机制与最佳实践详解
  • Linux 系统调用详解:操作文件的常用系统调用
  • Vulnhub jangow-01-1.0.1靶机渗透攻略详解
  • 自定义定时任务功能详解
  • MySQL 表的约束