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

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 StringBuffer

1. 拼接性能问题演示

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"; // 方式2:new 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(指向常量池同一个对象) // 场景2:new创建(强制新建对象) 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) { // 场景1:char数组构造的字符串(初始不在常量池) 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); // false(s3是堆对象) } }
实用场景

当你需要大量复用相同内容的字符串(如解析日志、处理海量文本),用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); // false(a在常量池,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码是74,'j'是106,74-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到6:hello 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 的 Key(HashMap 要求 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")); // 0('l'是第一个唯一字符) System.out.println(firstUniqChar("loveleetcode")); // 2('v') 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,固定拼接可直接用+

九、核心知识点总结(速记版)

  1. 创建方式:优先用字面量""(复用常量池),避免new String()(冗余对象);
  2. 比较规则:比较内容用equals()(常量在前防 NPE),比较地址用==,排序用compareTo()
  3. 核心特性:String 不可变(final 类 + final 数组),所有修改返回新对象;
  4. 性能优化:频繁拼接用StringBuilder(单线程)/StringBuffer(多线程);
  5. 面试核心:常量池原理、intern () 方法、不可变性原因、拼接性能对比。

掌握以上知识点,你不仅能解决日常开发中的所有 String 问题,还能轻松应对面试中的 String 考点。如果觉得本文有用,欢迎收藏转发,后续会持续更新 Java 核心知识点!

http://www.cnnetsun.cn/news/1366123.html

相关文章:

  • Claude HUD:开发者的智能开发驾驶舱
  • 具身智能与人形机器人:大模型到底改变了什么?
  • Win10利用端口转发突破公网SMB访问限制
  • Alpamayo-R1-10B开源镜像免配置:预装AlpaSim模拟器的端到端研发环境
  • 高效配置AGENTS.md开发环境:3个提升AI编码代理工作效率的最佳实践
  • 【智能革命】信使Web Builder:当AI遇见可视化,如何重塑网站开发范式
  • 如何利用DuckDB查询缓存:提升重复查询性能的终极指南
  • Qwen3-ForcedAligner-0.6B在在线教育平台的集成案例
  • 微信小程序即时通讯模板:基于WebSocket的完整解决方案
  • 如何建立客观的h2ogpt模型评估体系:完整指南与实践技巧
  • FreeCAD:开源参数化3D建模软件的终极指南与深度解析
  • Transformer架构实战:从零理解自注意力机制到BERT/GPT实现差异
  • 3个关键问题:如何用TT-NN动态量化实现高效混合精度推理
  • Oni编辑器宏录制与回放:终极自动化重复任务指南
  • WAN2.2文生视频多场景落地:律所法律条款→情景剧式普法短视频自动生成
  • LSTM与RMBG-2.0结合的图像序列处理方案
  • Swin2SR快速部署指南:3步搭建个人图片修复工具
  • 终极指南:如何为你的Gridea静态博客构建坚不可摧的安全防线
  • RexUniNLU效果惊艳展示:中文长文本中嵌套事件与多跳关系精准抽取
  • 如何实现Giscus评论系统的实时数据同步:GitHub讨论更新与前端刷新机制详解
  • S12SD紫外线传感器原理与ESP32嵌入式集成指南
  • 7个关键指标!Walrus存储节点监控完整指南:确保去中心化存储高可用性
  • GPTs项目维护指南:5个关键策略确保长期可持续发展
  • TGN超参数调优终极指南:提升模型性能的10个关键技巧
  • 如何实现无障碍支持?Semi Design ARIA属性技术解析
  • Maccy更新失败解决指南:3种手动升级方法详解
  • 终极指南:如何使用pypdf提升PDF文档在Google中的排名
  • 如何在Robo 3T中配置MongoDB Atlas文本搜索索引:完整指南
  • 掌握ipatool日志系统:高效调试与问题追踪的完整指南
  • 终极窗口置顶解决方案:这款开源工具让你的工作窗口永不“失踪”