第14章 String 与 StringBuilder

📅 2026/8/21 12:42:14
第14章 String 与 StringBuilder
第14章 String 与 StringBuilder字符串是编程中使用频率最高的类型。本节讲透 String 的特性不可变、常量池、equals 比较以及 StringBuilder 的高效拼接原理。一、String 的两种创建方式// 方式一字面量Strings1hello;// 方式二new 对象Strings2newString(hello);这两种方式有本质区别涉及字符串常量池字面量创建先去字符串常量池找没有就放入常量池返回池中地址new 创建直接在堆内存 new 一个对象跟池里有没有无关Strings1hello;Strings2hello;Strings3newString(hello);System.out.println(s1s2);// trues1 和 s2 指向常量池中同一个对象System.out.println(s1s3);// falses3 是堆中另一个对象System.out.println(s1.equals(s3));// true内容相同**为什么会有常量池**字符串用得太频繁每次写hello都新建对象内存会浪费严重。常量池的机制是相同的字面量只存一份大家共用JDK 8 起常量池位于堆内存中JDK 11、JDK 17 行为一致——内容不会变才敢放心共享。连编译期能确定的拼接结果也直接复用常量池Stringxhello;// 编译期就算出 hello直接入池Stringyhello;System.out.println(xy);// true同一个对象二、String 是不可变的String 对象创建后内容不可改变底层 char[] 数组被 final 修饰Stringsabc;ssd;// 不是在原对象上改而是创建了一个新的 abcd 对象拼接并没有修改原对象只是让变量指向新对象原来的 “abc” 还静静待在常量池里Stringsabc;Stringuppers.toUpperCase();// 返回新对象 ABCSystem.out.println(s);// abc原对象没有被改System.out.println(upper);// ABCStringts.concat(def);// 拼接同样产生新对象System.out.println(t);// abcdef拼接字符串时本质是Stringsab;// 等价于StringsnewStringBuilder(a).append(b).toString();循环中用 拼接会反复创建 StringBuilder 和中间字符串性能极差// ❌ 每次循环都 new 两个对象O(n²) 复杂度Strings;for(inti0;i10000;i){si;}// ✅ 全程只有一个 StringBuilder 对象StringBuildersbnewStringBuilder();for(inti0;i10000;i){sb.append(i);}Stringresultsb.toString();注意拼接次数很少几次时用没问题编译器会自动优化成 StringBuilder真正要避免的是循环里用。三、String 的常用方法Strings Hello Java World ;s.length();// 20字符个数含首尾各 2 个空格s.charAt(0);// 按下标取字符s.indexOf(Java);// 8子串首次出现位置没有返回 -1s.substring(8,12);// Java截取 [8, 12)含头不含尾s.toLowerCase();// 转小写s.toUpperCase();// 转大写s.trim();// 去掉首尾空格Hello Java Worlds.replace(Java,Python);// 替换所有匹配s.contains(Java);// true是否包含s.startsWith(He);// false有前导空格s.endsWith(d);// falses.split( );// 按空格拆分返回 String[]s.equals(abc);// 比较内容s.isEmpty();// 是否为空这些方法大多不修改原字符串而是返回新字符串——这正是不可变的体现。带输出的实测Strings Hello Java World ;System.out.println(s.length());// 20System.out.println(s.indexOf(Java));// 8System.out.println(s.substring(8,12));// Java含头不含尾System.out.println(s.trim());// Hello Java WorldSystem.out.println(s.contains(Java));// true拆分示例Stringcsv张三,18,男;String[]partscsv.split(,);System.out.println(parts[0]);// 张三System.out.println(parts[1]);// 18四、equals 与 高频考点StringanewString(abc);StringbnewString(abc);ab;// false比较的是地址a.equals(b);// trueString 重写了 equals比较内容比较基本类型值 / 引用类型地址equals()比较内容前提是类重写了 equals**为什么 String 要重写 equals**因为 Object 的默认 equals 也是比地址——两个不同对象装同样的内容应该视为相等所以 String 按字符逐个比较。没重写 equals 的类和equals就都是比地址classPoint{intx;Point(intx){this.xx;}}Pointp1newPoint(1);Pointp2newPoint(1);System.out.println(p1p2);// false地址不同System.out.println(p1.equals(p2));// false没重写 equals也是比地址再注意一个容易混淆的点字面量相同反而是 true因为它们共用常量池对象Stringcabc;Stringdabc;System.out.println(cd);// true字面量复用常量池对象本来就是同一个字符串比较必须用equals()尤其注意把常量放前面避免空指针Stringnamenull;// name.equals(admin); // 空指针异常admin.equals(name);// 安全返回 false五、StringBuilder 与 StringBuffer高频拼接场景用 StringBuilderStringBuildersbnewStringBuilder();sb.append(姓名).append(张三);// 链式调用sb.append(年龄).append(18);sb.insert(2,【);// 指定位置插入Stringresultsb.toString();// 转成 String除了 append/insert常用的还有这些StringBuildersbnewStringBuilder(hello world);sb.deleteCharAt(0);// 删除下标 0 的字符ello worldsb.insert(0,H);// 在下标 0 插入字符Hello worldsb.setCharAt(5,-);// 把下标 5 的字符改成 -Hello-worldsb.replace(6,11,JAVA);// 替换区间 [6, 11)Hello-JAVASystem.out.println(sb);// Hello-JAVA// 反转字符串一行搞定System.out.println(newStringBuilder(abcde).reverse());// edcba对比类型可变性线程安全性能String不可变安全不可变天然安全拼接最慢StringBuilder可变不安全快StringBuffer可变安全方法加锁较慢单线程拼接 →StringBuilder最常用多线程环境共享拼接 → StringBuffer面试常问String 为什么慢因为不可变每次拼接都创建新对象StringBuilder 在同一个对象上修改 char[] 数组StringBuilder 底层是可变字符数组容量不够时自动扩容默认初始容量 16扩容到约 2 倍 2StringBuildersbnewStringBuilder();// 初始容量 16System.out.println(sb.capacity());// 16sb.append(0123456789abcdef);// 正好 16 个字符System.out.println(sb.capacity());// 16没超sb.append(!);// 超了自动扩容System.out.println(sb.capacity());// 34六、String 常用工具方法// 判断字符串是否为空或全空白Strings ;s.isBlank();// true全空白返回 true// JDK 11 新增的字符串便捷方法 Java .strip();// 去掉首尾空白比 trim 更彻底能处理 Unicode 空白ab.repeat(3);// 重复拼接abababa\nb\nc.lines();// 按换行拆成 StreamString逐行处理// 数字与字符串转换StringnumString.valueOf(123);// 123intnInteger.parseInt(123);// 123doubledDouble.parseDouble(3.14);// 3.14// 格式化StringmsgString.format(你好%s你今年 %d 岁,张三,18);// 你好张三你今年 18 岁// 字符串拼接多个StringjoinedString.join(-,a,b,c);// a-b-cisEmpty()和 JDK 11 的isBlank()别搞混Strings ;System.out.println(s.isEmpty());// false有空格字符不算空System.out.println(s.isBlank());// true全是空白JDK 11注意isBlank()、strip()、repeat()、lines()都是 JDK 11 才加入的方法JDK 8 里没有用 JDK 11 或 JDK 17 的项目可以直接调用还在维护 JDK 8 老项目的话这些方法就用不了。七、扩展知识7.1 字符串常量池与 intern()常量池的核心价值相同的字面量只存一份重复出现的字符串直接复用省内存。像he llo这种编译期就能算出来的拼接结果同样是池中的 “hello”。new String(hello)出来的对象在堆里可以手动调用intern()把它送进常量池Strings1newString(hello);// 堆中对象hello 字面量本身也已在池中Strings2s1.intern();// 返回常量池中的 helloStrings3hello;System.out.println(s1s2);// falses1 是堆对象System.out.println(s2s3);// trueintern 返回了池中的同一个对象什么时候用 intern()比如从文件、数据库读入大量重复字符串几万个相同的 ID入池能显著省内存。普通业务代码一般用不到了解机制即可不要到处乱调。7.2 String 不可变的好处不可变听起来是限制其实是优点线程安全不可变对象天然线程安全多个线程共享同一个 String 不会出问题可安全缓存hashCode 只用算一次HashMap 用 String 做 key 性能好安全文件名、URL、密码等敏感字符串不会被别处偷偷改掉常量池复用成为可能正因为内容不会变才能放心共享同一个对象。用 String 做 HashMap 的 key 也因此非常安全——对 key 调toUpperCase()只返回新对象原 key 毫发无损。7.3 StringBuilder 与 StringBuffer 深入对比StringBuffer方法加了synchronized锁多线程下安全但每次调用都有锁开销慢StringBuilder无锁单线程下更快实际开发首选原则局部变量拼接单线程用 StringBuilder只有 StringBuffer 作为成员变量被多个线程共享时才需要一句话记住区别String 拼接造新扔旧StringBuilder 是原地施工。7.4 JDK 11 新方法实战// 需 JDK 11 才能运行// 场景 1生成分隔线System.out.println(-.repeat(20));// --------------------// 场景 2逐行读取多行文本Stringtext姓名:张三\n姓名:李四\n;text.lines().forEach(line-System.out.println(读取line));// 读取姓名:张三// 读取姓名:李四八、易错点易错 1用 比较字符串内容StringanewString(abc);StringbnewString(abc);// ❌ 永远 false 比的是地址两个对象地址不同// if (a b) { }// ✅ 比内容用 equalsif(a.equals(b)){System.out.println(内容相同);// 会输出}易错 2循环里用 拼接字符串// ❌ 1 万次循环 ≈ 创建 2 万个对象程序明显变慢Strings;for(inti0;i10000;i){si;}// ✅ 用 StringBuilder全程一个对象StringBuildersbnewStringBuilder();for(inti0;i10000;i){sb.append(i);}易错 3以为 String 可以原地修改// ❌ 误区以为下面这行把 s 改成了 abcdStringsabc;ssd;// 实际原 abc 没变s 只是指向了新对象 abcd// ✅ 真想原地改用 StringBuilderStringBuildersbnewStringBuilder(abc);sb.append(d);易错 4null 与空字符串混淆Stringanull;// 没有对象Stringb;// 有对象内容是空// ❌ 空指针异常// if (a.isEmpty()) { }// if (a.equals()) { }// ✅ 先判 null再判空if(anull||a.isEmpty()){}// ✅ 或者常量放前面天然防空指针if(.equals(a)){}易错 5StringBuilder 忘记 toString()StringBuildersbnewStringBuilder(hello);// ❌ 编译错误StringBuilder 不是 String不能直接赋值// String s sb;// ✅ 显式转成 StringStringssb.toString();// hello// 提示System.out.println(sb) 能直接打印因为 println 会自动调用 toString()九、小结字面量存常量池可复用new 一定新对象内容比较用equalsString 不可变所以线程安全、可缓存哈希但拼接慢循环拼接用 StringBuilder单线程或 StringBuffer多线程常量字符串放 equals 左边防空指针判空先判 nullJDK 11 新增 isBlank、strip、repeat、lines 等便捷方法intern() 手动入池了解即可下一节学习异常处理。下一篇第15章 异常处理待发布