Java入门(String类)

发布时间:2026/7/27 23:42:07

Java入门(String类) 目录一、为什么 Java 要单独设计 String 类1. 对比 C 语言面向对象的必然选择2. Java String 的设计目标二、String 的 4 种创建方式附内存分析1. 4 种核心创建方式完整可运行代码2. 底层存储说明JDK 9 重要优化三、字符串常量池JVM 的 “字符串缓存神器”1. 什么是字符串常量池2. 不同创建方式的内存差异面试核心核心结论务必记牢3. intern () 方法手动将字符串入池实用场景四、字符串的 4 种比较方式开发必避坑1. 4 种比较方式对比2. 实战代码示例3. 避坑提醒五、String 高频操作大全可直接复制使用1. 字符串查找定位字符 / 子串2. 类型转换字符串↔基本类型3. 替换、拆分、截取、去空格✅ 核心提醒六、String 的不可变性为什么不能改1. 不可变性的底层实现2. 不可变性的 3 个核心原因3. 不可变性的 4 个核心好处七、字符串拼接StringBuilder vs StringBuffer1. 拼接性能问题演示2. 高效拼接StringBuilder推荐3. StringBuilder vs StringBuffer核心区别最佳实践八、3 道经典 String 面试题附详细解析1. 字符串中第一个唯一字符题目要求解题思路完整代码2. 验证回文串题目要求解题思路完整代码3. 字符串拼接性能对比面试口述题问题标准答案九、核心知识点总结速记版在 Java 开发中String 是当之无愧的 “顶流” 类 —— 日常业务中 80% 以上的代码都会接触字符串操作面试中更是绕不开常量池、不可变性等核心考点。但看似简单的 String却藏着无数容易踩坑的细节为什么和equals()结果不一样循环拼接字符串为什么性能差常量池到底是怎么优化内存的本文从设计初衷→核心特性→实战用法→面试真题层层拆解 Java 字符串的所有关键知识点既有底层原理剖析又有可直接复用的代码示例帮你彻底搞懂 String告别踩坑。一、为什么 Java 要单独设计 String 类在理解 String 的特性前我们先搞懂一个基础问题为什么 Java 要为字符串专门设计一个类1. 对比 C 语言面向对象的必然选择C 语言中并没有专门的字符串类型只能通过字符数组 库函数实现字符串功能// C语言字符串数据字符数组和操作库函数分离 char str[] hello; // 调用库函数完成拼接需要手动管理内存 strcat(str, world);这种方式存在三大问题数据和操作分离完全违背面向对象OOP的封装思想需要手动管理内存容易出现数组越界、内存泄漏等问题字符串操作分散在各种库函数中使用不统一、不直观。2. Java String 的设计目标由于字符串是开发中使用最频繁的数据类型Java 专门设计java.lang.String类核心目标是✅ 封装把字符串的数据字符 / 字节和操作拼接、替换、查找封装在类中符合 OOP 思想✅ 安全避免手动操作内存减少空指针、数组越界等异常✅ 易用提供丰富的 API一站式解决所有字符串操作需求✅ 高效通过常量池、不可变性等设计优化性能和内存占用。一句话总结Java 中所有文本内容如字面量、用户输入、文件内容最终都会被封装成 String 对象。二、String 的 4 种创建方式附内存分析创建字符串远不止这一种写法不同创建方式对应不同的内存布局也是面试高频考点。1. 4 种核心创建方式完整可运行代码public class StringCreateDemo { public static void main(String[] args) { // 方式1字符串字面量最常用推荐 String s1 Java String; // 方式2new String()构造器不推荐会创建多余对象 String s2 new String(Java String); // 方式3字符数组构造适合从字符序列构建字符串 char[] charArray {J, a, v, a}; String s3 new String(charArray); // 方式4字节数组构造适合网络/文件IO场景 byte[] byteArray {97, 98, 99, 100}; // 对应ASCII码a,b,c,d String s4 new String(byteArray); // 输出结果验证 System.out.println(s1 s1); // s1 Java String System.out.println(s2 s2); // s2 Java String System.out.println(s3 s3); // s3 Java System.out.println(s4 s4); // s4 abcd } }2. 底层存储说明JDK 9 重要优化JDK 8 及之前String 底层用char[] value存储char占 2 字节JDK 9 及之后优化为byte[] value coder存储byte占 1 字节根据字符串编码UTF-8/ISO-8859-1自动选择存储方式内存占用减少 50%无论哪种版本value数组都被private final修饰这是 String 不可变的核心基础。三、字符串常量池JVM 的 “字符串缓存神器”常量池是 Java 对 String 的关键优化也是面试必问的核心知识点我们从 “是什么→为什么→怎么用” 三层拆解。1. 什么是字符串常量池字符串常量池StringTable是 JVM 方法区中一个特殊的HashTable 结构核心作用是缓存字符串字面量避免重复创建相同内容的 String 对象节省内存 提升性能。这是编程中经典的 “池化思想”类似线程池、连接池把常用的资源提前缓存重复使用时直接取不用重新创建。2. 不同创建方式的内存差异面试核心public class StringPoolDemo { public static void main(String[] args) { // 场景1字面量创建复用常量池 String str1 hello; String str2 hello; // 比较的是对象的内存地址 System.out.println(str1 str2); // true指向常量池同一个对象 // 场景2new创建强制新建对象 String str3 new String(hello); String str4 new String(hello); System.out.println(str3 str4); // false堆上两个不同对象 // 场景3字面量 vs new System.out.println(str1 str3); // false常量池 vs 堆 } }核心结论务必记牢创建方式内存位置是否复用常量池性能 / 内存String s abc常量池无则创建有则复用是最优new String(abc)堆新对象 常量池若不存在否堆对象唯一较差冗余对象关键提醒new String(abc)会创建1~2 个对象如果常量池已存在 abc仅在堆创建 1 个新对象如果常量池无 abc先在常量池创建 1 个再在堆创建 1 个共 2 个。3. intern () 方法手动将字符串入池intern()是 String 的 native 方法底层 C 实现作用是将当前字符串对象的内容放入常量池并返回常量池中的引用。public class StringInternDemo { public static void main(String[] args) { // 场景1char数组构造的字符串初始不在常量池 char[] arr {m, a, i, n}; String s1 new String(arr); // 堆对象常量池无main s1.intern(); // 手动将s1的内容main放入常量池 String s2 main; // 复用常量池中的main System.out.println(s1 s2); // JDK 1.7 返回true // 场景2先有字面量后intern复用已有常量 String s3 new String(test); // 常量池已创建test String s4 s3.intern(); // 返回常量池的test引用 String s5 test; System.out.println(s4 s5); // true System.out.println(s3 s5); // falses3是堆对象 } }实用场景当你需要大量复用相同内容的字符串如解析日志、处理海量文本用intern()可以大幅减少内存占用但注意常量池空间有限过度使用可能导致 OOM。四、字符串的 4 种比较方式开发必避坑字符串比较是最容易出错的场景之一核心原则比较内容用 equals ()比较地址用 我们用表格 代码讲清楚1. 4 种比较方式对比方法比较目标返回值适用场景对象的内存地址boolean判断是否为同一个对象equals()字符串内容区分大小写boolean常规内容比较开发最常用compareTo()字典序ASCII 码差值int正 / 负 / 0排序、比较大小compareToIgnoreCase()字典序忽略大小写int正 / 负 / 0不区分大小写的排序 / 比较2. 实战代码示例public class StringCompareDemo { public static void main(String[] args) { String a Java; String b java; String c new String(Java); // 1. 比较地址易错点 System.out.println(a c); // falsea在常量池c在堆 System.out.println(a b); // false内容不同地址不同 // 2. equals()比较内容推荐 System.out.println(a.equals(c)); // true内容相同 System.out.println(a.equals(b)); // false区分大小写 // 空指针安全写法推荐常量在前 System.out.println(Java.equals(a)); // true // 3. compareTo()字典序比较 // J的ASCII码是74j是10674-106-32 System.out.println(a.compareTo(b)); // -32 System.out.println(b.compareTo(a)); // 32 System.out.println(a.compareTo(c)); // 0内容相同 // 4. compareToIgnoreCase()忽略大小写 System.out.println(a.compareToIgnoreCase(b)); // 0 } }3. 避坑提醒❌ 错误if (str abc)即使内容相同地址可能不同✅ 正确if (abc.equals(str))常量在前避免 str 为 null 时抛空指针✅ 进阶JDK 1.7 可用Objects.equals(str, abc)自动处理 null。五、String 高频操作大全可直接复制使用String 提供了丰富的 API以下是开发中最常用的操作附完整示例和注意事项。1. 字符串查找定位字符 / 子串public class StringFindDemo { public static void main(String[] args) { String s hello-java-string; // 1. 获取指定下标字符下标从0开始 char ch s.charAt(6); System.out.println(下标6的字符 ch); // j // 2. 查找字符首次出现的下标无则返回-1 int firstJ s.indexOf(j); System.out.println(j首次出现下标 firstJ); // 6 // 3. 查找字符最后出现的下标 int lastT s.lastIndexOf(t); System.out.println(t最后出现下标 lastT); // 12 // 4. 查找子串首次出现的下标 int javaIndex s.indexOf(java); System.out.println(java子串下标 javaIndex); // 6 } }2. 类型转换字符串↔基本类型public class StringConvertDemo { public static void main(String[] args) { // 场景1基本类型 → 字符串推荐用String.valueOf避免null String numStr String.valueOf(1024); // 1024 String boolStr String.valueOf(true); // true // 场景2字符串 → 基本类型 int num Integer.parseInt(1024); // 1024 double d Double.parseDouble(3.14); // 3.14 boolean bool Boolean.parseBoolean(true); // true // 场景3大小写转换常用作统一格式 String upper java.toUpperCase(); // JAVA String lower JAVA.toLowerCase(); // java System.out.println(upper → lower); // JAVA → java // 注意转换失败会抛NumberFormatException需捕获 try { Integer.parseInt(abc); } catch (NumberFormatException e) { System.out.println(字符串无法转换为数字); } } }3. 替换、拆分、截取、去空格public class StringOptDemo { public static void main(String[] args) { String str hello-world-java ; // 1. 替换支持正则表达式 String replaceAll str.replaceAll(-, /); // hello/world/java String replaceFirst str.replaceFirst(-, /); // hello/world-java // 2. 拆分特殊字符需转义如.、|、\ String ip 192.168.1.1; String[] ipArr ip.split(\\.); // 转义.拆分结果[192,168,1,1] System.out.println(IP拆分后第1段 ipArr[0]); // 192 // 3. 截取左闭右开区间 [start, end) String sub1 str.substring(2, 7); // 从下标2到6hello String sub2 str.substring(7); // 从下标7到末尾-world-java // 4. 去空格开发常用处理用户输入、接口参数 String trimStr str.trim(); // 去首尾空格hello-world-java // JDK 11新增去所有空格 // String stripAll str.replaceAll(\\s, ); // hello-world-java // 输出验证 System.out.println(替换全部 replaceAll); System.out.println(截取 sub1); System.out.println(去空格 trimStr); } }✅ 核心提醒String 是不可变对象所有修改操作替换、截取、拼接都不会改变原字符串而是返回一个新的 String 对象。// 示例不可变性验证 String original hello; String modified original.concat( world); // 拼接 System.out.println(original); // hello原字符串不变 System.out.println(modified); // hello world新字符串六、String 的不可变性为什么不能改很多初学者疑惑明明可以写str a b为什么说 String 是不可变的我们从底层原理→不可变原因→核心好处三层讲透。1. 不可变性的底层实现String 的不可变性本质是由两个关键设计保证的// JDK 8 String核心源码关键部分 public final class String { // 存储字符串的数组private final修饰 private final char value[]; // 哈希值缓存不可变所以哈希值固定 private int hash; // 构造器外部无法直接修改value public String(String original) { this.value original.value; this.hash original.hash; } // 所有修改方法都返回新对象如concat、replace public String concat(String str) { // 省略实现本质是创建新的char数组复制原内容新内容 return new String(buf, true); } }2. 不可变性的 3 个核心原因设计点作用String类被final修饰禁止继承避免子类修改核心逻辑value数组private final外部无法访问且引用不可变数组本身不能被替换修改方法返回新对象所有 “修改” 都是创建新对象原对象不变3. 不可变性的 4 个核心好处✅线程安全多线程并发访问时无需加锁不会出现数据不一致✅哈希值稳定hashCode () 计算一次后缓存适合做 HashMap 的 KeyHashMap 要求 Key 的哈希值不变✅常量池复用不可变保证常量池中的字符串不会被篡改可安全复用✅安全性避免字符串被恶意修改如文件路径、数据库连接字符串。七、字符串拼接StringBuilder vs StringBuffer直接用拼接字符串在循环中会产生大量临时对象性能极差我们先看问题再讲解决方案。1. 拼接性能问题演示// 反例循环中用拼接性能极差 public class StringConcatBadDemo { public static void main(String[] args) { long start System.currentTimeMillis(); String s ; for (int i 0; i 100000; i) { s i; // 每次拼接都创建新String对象 } long end System.currentTimeMillis(); System.out.println(耗时 (end - start) ms); // 约5000ms视机器而定 } }2. 高效拼接StringBuilder推荐// 正例用StringBuilder拼接性能提升100倍 public class StringBuilderDemo { public static void main(String[] args) { long start System.currentTimeMillis(); StringBuilder sb new StringBuilder(); for (int i 0; i 100000; i) { sb.append(i); // 直接追加到内部数组不创建新对象 } String result sb.toString(); // 最终转为String long end System.currentTimeMillis(); System.out.println(耗时 (end - start) ms); // 约50ms // 常用扩展方法 sb.reverse(); // 反转字符串 sb.insert(0, prefix-); // 在指定位置插入 sb.delete(0, 7); // 删除指定区间字符 } }3. StringBuilder vs StringBuffer核心区别特性StringBuilderStringBuffer线程安全不安全无锁安全方法加 synchronized性能极高推荐稍低锁开销适用场景单线程90% 场景多线程如并发拼接核心方法append()/insert()/reverse()同左最佳实践90% 的业务场景单线程优先用StringBuilder多线程并发拼接如日志收集用StringBuffer少量固定拼接如hello world直接用编译器会优化为 StringBuilder。八、3 道经典 String 面试题附详细解析面试中 String 的题目常结合原理和实战以下 3 道是高频题附完整代码和思路解析。1. 字符串中第一个唯一字符题目要求给定一个字符串找到它的第一个不重复的字符并返回其索引。如果不存在则返回 -1。解题思路第一步遍历字符串用数组统计每个字符出现的次数ASCII 码范围 0-255数组效率高于 HashMap第二步再次遍历字符串找到第一个次数为 1 的字符返回其索引。完整代码public class FirstUniqCharDemo { public static int firstUniqChar(String s) { // 边界判断 if (s null || s.length() 0) { return -1; } // 统计字符出现次数ASCII码覆盖所有字符 int[] count new int[256]; for (int i 0; i s.length(); i) { count[s.charAt(i)]; } // 找第一个出现次数为1的字符 for (int i 0; i s.length(); i) { if (count[s.charAt(i)] 1) { return i; } } return -1; } public static void main(String[] args) { System.out.println(firstUniqChar(leetcode)); // 0l是第一个唯一字符 System.out.println(firstUniqChar(loveleetcode)); // 2v System.out.println(firstUniqChar(aabb)); // -1无唯一字符 } }2. 验证回文串题目要求给定一个字符串验证它是否是回文串正读和反读一样只考虑字母和数字忽略大小写。解题思路双指针法左指针从头部、右指针从尾部向中间移动跳过非字母 / 数字的字符比较对应位置的字符忽略大小写若所有对应字符都相等则是回文串。完整代码public class IsPalindromeDemo { public static boolean isPalindrome(String s) { // 边界判断 if (s null || s.length() 0) { return true; } // 统一转为小写简化比较 s s.toLowerCase(); int left 0; int right s.length() - 1; while (left right) { // 左指针跳过非字母/数字 if (!Character.isLetterOrDigit(s.charAt(left))) { left; } // 右指针跳过非字母/数字 else if (!Character.isLetterOrDigit(s.charAt(right))) { right--; } // 字符不相等直接返回false else if (s.charAt(left) ! s.charAt(right--)) { return false; } } return true; } public static void main(String[] args) { System.out.println(isPalindrome(A man, a plan, a canal: Panama)); // true System.out.println(isPalindrome(race a car)); // false System.out.println(isPalindrome(12321)); // true } }3. 字符串拼接性能对比面试口述题问题为什么循环中用拼接字符串性能差编译器对String s a b c有优化吗标准答案循环中拼接每次拼接都会创建新的 StringBuilder 和 String 对象大量临时对象导致 GC 频繁性能差固定拼接a b c编译器会优化为new StringBuilder().append(a).append(b).append(c).toString()只创建一个 StringBuilder 和一个 String 对象性能和手动写 StringBuilder 一致最佳实践循环拼接用 StringBuilder固定拼接可直接用。九、核心知识点总结速记版创建方式优先用字面量复用常量池避免new String()冗余对象比较规则比较内容用equals()常量在前防 NPE比较地址用排序用compareTo()核心特性String 不可变final 类 final 数组所有修改返回新对象性能优化频繁拼接用StringBuilder单线程/StringBuffer多线程面试核心常量池原理、intern () 方法、不可变性原因、拼接性能对比。掌握以上知识点你不仅能解决日常开发中的所有 String 问题还能轻松应对面试中的 String 考点。如果觉得本文有用欢迎收藏转发后续会持续更新 Java 核心知识点

相关新闻