资讯动态

ava String 详解:一张「写死就拆不掉」的纸条

发布时间:2026/9/26 4:05:37 来源:尧图企业网站定制
引言开篇为什么 String 值得单独讲清楚在 Java 里几乎每行业务代码都会碰到字符串用户名、订单号、URL、JSON、日志、配置项……String 看起来「太基础」却是面试高频题、线上性能坑、编码乱码的重灾区。很多人能写出 hello却答不上- 为什么改不动已有字符串- a b 和循环里 s x 差在哪- 和 equals() 到底比的是什么本文以 **Java 17** 为主示例版本涉及 Java 8 / 9 / 11 差异时会明确标注。目标不是堆 API而是帮我以及各位同志建立一套可复述、可落地的心智模型。一、String类的基本概念1.1生活画面写死就拆不掉的纸条| 生活中的画面 | 编程里的对应 ||-------------|-------------|| 纸条上的字写完就定稿 | 字符串内不可变|| 想改字只能重写一张新纸条 | 任何「修改」其实都是返回新 String|| 纸条材质、格式由工厂统一规定 | String 是final 类不能被继承篡改 || 多个人可以拿同一张「公共告示」看 | 字面量可进常量池复用 |一句话String不可变的字符序列。你以为在【改它】其实是在【换一张】。1.2为什么是final类String被声明为final意味着1.不能被继承—— 别人没法写个EvilString extends String去覆写equal、hashCode破环安全与缓存约定2.配合不可变实现——JVM/类库可以对字符串做常量池复用、哈希缓存等优化前提是【行为可信、内容不变】。注意final类 不等于对象不可变。final只保证类不能被继承不可变还要靠内部字段不被外部改写String的内部是私有的且没有对外暴露可变试图。1.3不可变性有什么好处安全作为方法参数、Map的key、网络传输中的标识时内容不会被半路改掉线程友好多个线程同时读同一个String一般无需额外同步【内容被改】的问题可缓存/可共享常量池、哈希缓存hash字段才有意义适合做HashMap的key一旦放入哈希值不会因为内容变化而错乱。public class ImmutabilityDemo { public static void main(String[] args) { String s Java; String t s.concat(17);//返回新串不改动 s System.out.println(s);//Java System.out.println(t);//Java17 System.out.println(st);//false 根本就是两个对象 } }易错点concat、replace、substring等都不会修改原字符串忘记接返回值等于白做。1.4String与CharSequenceCharSequence是【字符序列】接口String、StringBuilder、StringBuffer都实现了它。实际意义API设计时参数类型用CharSequence调用方可传String或StringBuilder更灵活。例如StringJoiner、部分正则API都接受CharSequence。二、String的创建方式2.1字面量vsnew String()public static void main(String[] args) { String a hello;//字面量优先走常量池 String b hello;//通常与a指向同一池中对象 String c new String(hello);//至少在堆上新建一个String 对象 String d c.intern();//返回池中已有的”passion“ System.out.println(a); System.out.println(b); System.out.println(c); System.out.println(d); }对比输出常见结果便于理解具体对象身份依赖JVM但教学结论稳定public static void main(String[] args) { String a hello;//字面量优先走常量池 String b hello;//通常与a指向同一池中对象 String c new String(hello); System.out.println(ab);//字面量共享 System.out.println(ac);//falsec是堆上新对象 System.out.println(a.equals(c));//true内容相同 System.out.println(ac.intern());//trueintern回到池中 }最佳实践日常直接写字面量”xxx“即可不要无谓new String”xxx“。2.2.字符串常量池干什么用常量池的核心作用是相同字面量尽量复用同一份内容节省内存。编译期能确定的字符串字面量会进入常量池相关机制大量重复的短字符串状态码、枚举式标记因此更省空间。2.3编译期常量vs 运行期拼接package Exercise.blog; public class ConcatCompileDemo { public static void main(String[] args) { String s1 hel lo;//编译期可折叠成”hello“ String s2 hello; System.out.println(s1s2);//true(常见结果 String x hel; String s3 x lo;//运行期拼接通常产生新对象 System.out.println(s3 s2);//false(常见结果 System.out.println(s3.equals(s2));//true } }记忆口诀两边都是编译期常量-编译器可能直接合成一个字面量有一边是变量-运行期再拼别指望”“为true说明Java9 对”“的实现细节如 invokedynamic / StringConcatFactory与Java8的StringBuilder展开并不完全相同但【循环里大量不推荐】这一工程结论仍然成立。2.4intern()用途与注意事项intern()会尝试把字符串放入常量池若已存在则返回池中引用。适用大量重复、生命周期长、确实需要靠引用相等或强复用的场景少见。风险滥用会让池膨胀增加内存压力代码可读性变差用equals通常更清晰。注意不要把intern()当成普遍优化开关随手开。三、String的比较3.1vs equals()| 比较方式 | 比什么 | 日常该不该用 ||---------|--------|-------------|| | 引用是否指向同一对象 | 内容比较时**不要用** || equals() | 字符内容是否相同 | **首选** |package Exercise.blog; public class CompreDemo { public static void main(String[] args) { String a new String(Java); String b new String(Java); System.out.println(a b);//false System.out.println(a.equals(b));//true } }3.2 equalsIgnoreCase()忽略大小写比较适合验证码、HTTP头名弱匹配、某些配置开关等public static void main(String[] args) { String input Yes; boolean ok yes.equalsIgnoreCase(input); System.out.println(ok);//true }3.3如何安全比较可能为null的字符串错误示范String name null; if (name.equals(admin)){ //NPE }正确示范常量在前 / Objects.equalspublic static void main(String[] args) { String name null; // 写法1字面量在前 if (admin.equals(name)){ System.out.println(是管理员); } // 写法2Objects.equals(Java7) if (Objects.equals(name,admin)){ System.out.println(是管理员); }else { System.out.println(不是管理员或name 为null); } }最佳实践比较时让不会为null的一方调用equals或统一用Objects.equals。四、String常用API先记一条总原则这些方法几乎都不改原串而是返回新结果或基本类型。4.1长度与空白length/isEmpty/isBlankpublic static void main(String[] args) { String s ; System.out.println(s.length()); System.out.println(s.isEmpty());//false:长度不为0 System.out.println(s.isBlank());//trueJava11空白也算blank }| 方法 | 含义 | 版本提示 ||------|------|---------|| length() | 字符长度UTF-16 code unit 计数 | 一直都有 || isEmpty() | length() 0 | 一直都有 || isBlank() | 空或全是空白字符 | **Java 11** |4.2取字符与截取charAt/substringpublic static void main(String[] args) { String s Java17; char c s.charAt(0);//J String sub s.substring(0,4);//Java含头不含尾 System.out.println(c); System.out.println(sub); }易错点substring越界会抛IndexOutOfBoundsException【含头不含尾】要牢记。4.3 查找与判断indexOf / contains / startsWith / endsWithpublic static void main(String[] args) { String url https://www.baidu.com; System.out.println(url.startsWith(https));//true System.out.println(url.endsWith(.com));//true System.out.println(url.contains(baidu));//true System.out.println(url.indexOf(baidu));//12(找不到返回-1 }场景做简单前缀/后缀判断时优先使用这些方法不必上正则。4.4替换与拆分replace / replaceAll / splitpublic static void main(String[] args) { String s a.b.c; // replace按字符串序列替换不是正则 System.out.println(s.replace(.,-));//a-b-c //replAll参数是正则 System.out.println(s.replaceAll(\\.,-));//a-b-c //split参数是正则.在正则里表示任意字符必须转义 String[] parts s.split(\\.); System.out.println(parts.length);//3 }| 方法 | 是否正则 | 常见坑 ||------|---------|--------|| replace | 否 | 名字像正则其实不是 || replaceAll | 是 | 特殊字符要转义 || split | 是 | .、|、\\ 等必须正确转义 |4.5 trim() vs strip()public static void main(String[] args) { // 中间那个是Unicode空白如\u3000等场景中strip更稳妥 String s \u3000Java\u3000; System.out.println([s.trim()]); //trim主要处理 U0020的空白 System.out.println([ s.strip() ]);//Java11按Unicode空白处理 }结论实用向新代码Java11优先strip()/ stripLeading() / stripTrailing()维护老代码时再考虑trim().4.6 join 与 formatpublic static void main2(String[] args) { String csv String.join(,,a,b,c); System.out.println(csv); String msg String.format(用户 %s余额 %.2f元,张三,12.5); System.out.println(msg); }场景多个片段用同一分隔符连接-String.join/StringJoiner;需要占位格式化-formatted()(Java15或String.format。public static void main(String[] args) { //Java15 String tip 订单 %s 已完成.formatted(A1001); System.out.println(tip); }五、字符串拼接与性能5.1在干什么少量拼接日志一句、返回提示语用很清晰完全可以public static void main(String[] args) { String name Alice; String hello Hello,name !; System.out.println(hello); }但在循环里反复容易产生大量临时对象带来不必要的分配与拷贝压力。5.2 循环中请用 StringBuilderpublic static void main(String[] args) { //不推荐 String bad ; for (int i 0; i 5; i) { badi;//每次都可能产生新String } //推荐 StringBuilder sb new StringBuilder(); for (int i0;i 5;i){ sb.append(i); } String good sb.toString(); System.out.println(good);//01234 }5.3 StringBuilder vs StringBuffer| 类型 | 可变 | 线程安全 | 推荐场景 ||------|--------|---------|---------|| String | 否 | 内容不可变带来的天然友好 | 常量、返回值、Map key || StringBuilder | 是 | 否 | **单线程拼接默认首选** || StringBuffer | 是 | 是方法同步 | 多线程共享同一缓冲区少见 |最佳实践现代代码里拼接优先StringBuilder需要线程安全时更常见的是在外层同步或换并发结构而不是条件反射上 StringBuffer。5.4怎么选一张图记住六、字符编码6.1String与编码的关系内存里的StringJava用字符序列表示文本落到磁盘、网络、数据库必须变成字节这就要约定【字典】——字符集/编码。字典对不上就会乱码我们要按UTF-8写对方按GBK读。6.2正确用法显式指定字符集public static void main(String[] args) { String text 你好Java; byte[] bytes text.getBytes(StandardCharsets.UTF_8); String again new String(bytes,StandardCharsets.UTF_8); System.out.println(again);//你好Java System.out.println(bytes.length);// 13 UTF_8下中文通常3字节/字 }6.3为什么不要依赖系统默认字符集getBytes()无参版、部分老I/OAPI会使用JVM默认编码在不同操作系统上可能不同Windows常见GBK服务器常见UTF-8。同一段代码【在我电脑正常在服务器乱码往往就出在这里。最佳实践项目统一UTF-8代码里写死StandardCharsets.UTF_8或等价显式配置不要赌默认值。七、常见误区与最佳实践1.用比内容-用equals/Objects.equals。2.循环里疯狂 -改用StringBuilder。3.忽略null-const.equals(var)或Objects.equals。4.滥用intern()-除非有明确重复度与生命周期分析否则别用。5.split(.)直接拆-写成split(\\.)复杂分割优先想清楚是否该用正则。6.编码靠默认-统一StandardCharsets.UTF_8。7.敏感信息长期放String-String不可变且可能滞留在内存/池化相关路径中难以主动清零密码等更适合char【】、用完尽快清空。这不能替代加密与安全设计知识减少明文残留窗口的一种习惯。public static void main(String[] args) { char[] password {s,e,c,r,e,t}; try { //使用password做认证... }finally { java.util.Arrays.fill(password,\0);//尽快清掉 } }八、底层原理与版本差异8.1不可变 哈希缓存概念层String的哈希值会被缓存常见为实例字段hash第一次算完可复用这对大量以字符串为key的Map很有帮助。前提仍然是内容不变哈希才有意义。严谨说明字段名、具体缓存策略属于JDK实现细节不同版本源码可能调整请以你使用的JDK源码为准。8.2Java8vs加9内部存储| 版本 | 内部存储OpenJDK 常见实现 | 要点 ||------|------------------------------|------|| Java 8 | char[]UTF-16 | 每个 char 2 字节 || Java 9 | byte[] coder**Compact Strings** | Latin-1 可用 1 字节/字符压缩否则 UTF-16 |这对我们写业务代码的直接影响通常不大但有助于理解为什么同样是【字符串】内存占用会随内容与JDK版本变化length()仍是按UTF-16code unit语义对外呈现和【占了多少字节】不是一回事。注意以上描述对OpenJDK/Oracle JDK常见实现。若使用其他JVM请核实其文档不要把实现细节当成规范本身。九、综合示例日志清洗与用户信息拼接下面这个小例子串联了安全equals、空白处理、替换、拼接性能、显式编码。package Exercise.string; import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.Objects; public class UserProfileReport { public static String buildReport(String rawName,String rawRole, String... tags){ //1)null安全 String name Objects.requireNonNullElse(rawName,anonymous).strip();//Java11 String role Objects.requireNonNullElse(rawRole,guest).strip(); //2)忽略大小写判断角色 boolean admin admin.equalsIgnoreCase(role); //3)循环拼接用StringBuilder StringBuilder tagPart new StringBuilder(); for (int i 0; i tags.length; i) { if (i 0){ tagPart.append(|); } //简单清洗去掉逗号避免破环格式 tagPart.append(tags[i].replace(,, )); } //4)格式化输出 String report String.format( Locale.ROOT, user%s;role%s;admin%s;tags%s, name, role.toLowerCase(Locale.ROOT), admin, tagPart ); //5)如需落盘/上网显式UTF-8 byte[] bytes report.getBytes(StandardCharsets.UTF_8); return new String(bytes,StandardCharsets.UTF_8); } public static void main(String[] args) { String out buildReport( Alice ,ADMIN,vip,beta,test); System.out.println(out); // 预期类似Alice;roleadmin;admintrue;tagsvip|beta test } }十、常见面试题/思考题1.为什么String设计成不可变答安全、可共享、可缓存哈希、适合作为Map key也让常量池复用更可靠。2.String s new String(abc)会创建几个对象答常见说法式字面量”abc“在常量池侧有一份new在堆上再创建一个String对象。若池中已有”abc“则不会重复创建池中那份。面试应说明【取决于池中是否已经存在】以及【对象个数讨论常针对典型HotSpot教学模型】。3.和equals的区别答比引用equals比内容。字符串内容比较用equals。4.循环里用拼接有什么问题更好做法答可能产生大量临时String增加分配与拷贝。使用StringBuilder.append()最后toString()。5.StringBuilder和StringBuffer怎么选答单线程用StringBuilderStringBuffer方法带同步现代代码较少作为默认选择。6.trim和strip区别答stripJava11按Unicode空白处理通常更适合新代码trim主要处理较早定义的空白范围7.为什么不建议依赖默认字符集答不同环境默认编码可能不同导致乱码。应显式使用StandardCharsets.UTF_8。十一、最佳实践清单内容比较用equals/Objects.equals,不用。可能为null时字面量在前或用Objects.equals。少量拼接可用循环拼接用StringBuilder。Java11判断空白优先isBlank/strip。replace不是正则replaceAll/split才是注意转义。编码一律显式StandardCharsets.UTF_8。不要无谓new String(...)慎用intern().密码等敏感数据优先char[],用完清零并配合整体安全方案。需要可变字符串时用StringBuffer把String留给【定稿文本】。十二、总结1。String时封口的纸条——看起来像在改其实是在换新的所以API几乎都返回新串。2.比内容用equals比是不是同一张纸才用常量池让【同一张告示】能被多人共享。3.拼得少用拼得多用StringBuilder出内存进网络闲谈UTF-8。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑