Java中String类型详解(2026)

一、String 类型的核心本质

String 是 Java 中最常用的引用数据类型,用于表示字符串常量,其核心特性需重点掌握:

  1. 不可变性(Immutable):String 对象一旦创建,其底层字符数组(char [],JDK9 后改为 byte [])的内容无法修改。所有看似修改的操作(如substring、replace)都会创建新的 String 对象,原对象保持不变。
String str = "abc";

str = str + "d"; // 原"abc"对象未改变,创建新对象"abcd"并让str指向它
  1. 常量池优化:Java 为 String 提供字符串常量池(String Pool),位于方法区(JDK8 后是元空间)。当使用字面量创建 String(如String s = "abc")时,JVM 会先检查常量池是否存在该字符串,若存在则直接引用,否则创建新对象存入常量池,避免重复创建相同字符串,节省内存。
  1. 实现接口:String 实现了Serializable(支持序列化)、Comparable)、CharSequence(字符序列接口,提供charAt、length` 等方法)三个核心接口,这也是其具备诸多实用功能的基础。

二、String 的创建方式与内存差异

String 有两种常见创建方式,其内存分配和引用逻辑截然不同,是面试高频考点:

1. 字面量创建(String s = "abc")
  • 执行流程:JVM 先查询字符串常量池,若存在 "abc" 则直接将引用赋值给 s;若不存在,在常量池创建 "abc" 对象,再将引用赋值给 s。
  • 内存位置:对象存储在常量池,s 指向常量池中的对象。
2. 构造方法创建(String s = new String("abc"))
  • 执行流程:无论常量池是否存在 "abc",都会先在堆内存中创建一个 String 对象,再检查常量池:若常量池无 "abc",则在常量池也创建一个 "abc" 对象;最终 s 指向堆内存中的对象。
  • 代码验证:
String s1 = "abc";

String s2 = new String("abc");

System.out.println(s1 == s2); // false(==比较引用地址,s1指向常量池,s2指向堆)

System.out.println(s1.equals(s2)); // true(equals比较字符串内容)
3. intern () 方法的作用

intern()方法用于手动将 String 对象存入常量池(JDK7 后优化,常量池移至堆中):

  • 若常量池已存在该字符串,返回常量池中的引用;
  • 若不存在,将当前对象的引用存入常量池(而非创建新对象),返回该引用。
  • 代码示例:
String s3 = new String("xyz").intern();

String s4 = "xyz";

System.out.println(s3 == s4); // true(s3通过intern()指向常量池,s4直接指向常量池)

三、String 的常用方法(实战必备)

String 类提供了大量实用方法,以下是开发中高频使用的核心方法,结合场景说明:

方法签名

功能描述

示例

int length()

获取字符串长度

"abc".length() → 3

char charAt(int index)

获取指定索引的字符(索引从 0 开始)

"abc".charAt(1) → 'b'

boolean equals(Object obj)

比较字符串内容(区分大小写)

"Abc".equals("abc") → false

boolean equalsIgnoreCase(String str)

忽略大小写比较内容

"Abc".equalsIgnoreCase("abc") → true

String concat(String str)

拼接字符串(等价于+,但效率更高)

"a".concat("b").concat("c") → "abc"

String substring(int beginIndex)

从指定索引截取子串(含 beginIndex)

"abcd".substring(1) → "bcd"

String substring(int beginIndex, int endIndex)

截取 [beginIndex, endIndex) 区间的子串

"abcd".substring(1,3) → "bc"

boolean contains(CharSequence s)

判断是否包含指定字符序列

"abcde".contains("bc") → true

boolean startsWith(String prefix)

判断是否以指定前缀开头

"http://www".startsWith("http") → true

boolean endsWith(String suffix)

判断是否以指定后缀结尾

"test.txt".endsWith(".txt") → true

String replace(char oldChar, char newChar)

替换所有指定字符

"abac".replace('a','x') → "xbxc"

String replace(CharSequence target, CharSequence replacement)

替换指定字符序列

"a-b-c".replace("-","_") → "a_b_c"

String trim()

去除首尾空白字符(空格、制表符等)

" abc ".trim() → "abc"(JDK11 后可用strip(),支持更多空白字符)

String[] split(String regex)

按指定正则表达式分割字符串

"a,b,c".split(",") → ["a","b","c"]

static String valueOf(Object obj)

将任意类型转为字符串(静态方法)

String.valueOf(123) → "123"

int compareTo(String str)

按字典序比较(返回差值,0 表示相等)

"a".compareTo("b") → -1,"c".compareTo("a") → 2

关键注意点:
  • split(regex)中若分割符是正则特殊字符(如.、|),需转义(如split("\\."));
  • substring在 JDK6 中会创建新字符数组,JDK7 后直接引用原数组的指定区间,避免内存浪费;
  • 字符串拼接优先使用StringBuilder/StringBuffer(尤其是循环拼接),效率远高于+和concat(+会频繁创建新 String 对象)。

四、String、StringBuilder、StringBuffer 的区别(面试必问)

开发中字符串拼接场景频繁,三者的选择直接影响性能,核心区别如下:

特性

String

StringBuilder

StringBuffer

可变性

不可变(修改创建新对象)

可变(直接操作底层数组)

可变(直接操作底层数组)

线程安全

线程安全(无修改操作)

线程不安全(效率高)

线程安全(方法加synchronized,效率低)

性能

最低(频繁修改时创建大量对象)

最高(单线程场景首选)

中等(多线程场景使用)

适用场景

字符串不频繁修改(如常量定义)

单线程下字符串频繁拼接、修改

多线程下字符串频繁拼接、修改

性能测试示例(循环拼接 10000 次):
long start = System.currentTimeMillis();

// String拼接(最慢)

String s = "";

for (int i = 0; i 10000; i++) {

s += i;

}

System.out.println("String耗时:" + (System.currentTimeMillis() - start) + "ms");

// StringBuilder拼接(最快)

start = System.currentTimeMillis();

StringBuilder sb = new StringBuilder();

for (int i = 0; i 0000; i++) {

sb.append(i);

}

System.out.println("StringBuilder耗时:" + (System.currentTimeMillis() - start) + "ms");

// StringBuffer拼接(中等)

start = System.currentTimeMillis();

StringBuffer sbf = new StringBuffer();

for (int i = 0; i 0; i++) {

sbf.append(i);

}

System.out.println("StringBuffer耗时:" + (System.currentTimeMillis() - start) + "ms");

输出结果(参考):String 耗时约 500ms,StringBuilder 耗时约 1ms,StringBuffer 耗时约 3ms,差异显著。

五、String 的常见误区与避坑指南

  1. 误区 1:认为String s = nullString s = ""等价
    • 区别:s = null表示 s 未指向任何对象(引用为空);s = ""表示 s 指向一个长度为 0 的字符串对象(引用非空)。
    • 避坑:调用null的 String 方法会抛出NullPointerException,需先判空:
// 错误写法(若s为null会报错)

if (s.equals("abc")) { ... }

// 正确写法(先判非空,或用常量在前)

if ("abc".equals(s)) { ... } // 即使s为null,也不会报错(返回false)
  1. 误区 2:循环中使用+拼接字符串
    • 问题:每次+都会创建新的 String 对象,循环 10000 次会创建大量对象,导致内存浪费和性能下降。
    • 解决:使用StringBuilder(单线程)或StringBuffer(多线程):

  1. 误区 3:String.intern()一定能节省内存
    • 注意:JDK7 后intern()会将对象引用存入常量池,但若字符串是动态生成的(如用户输入),且重复率低,使用intern()反而会占用常量池内存,适得其反。
    • 适用场景:字符串重复率高(如配置项、固定枚举值)时,使用intern()可复用对象,节省内存。
  1. 误区 4:忽略 String 的空字符串判断
    • 判空推荐写法(兼顾null和空字符串):
// 方法1:使用StringUtils(Apache Commons Lang工具类,推荐)

if (StringUtils.isNotBlank(s)) { ... } // 排除null、""、" "等情况

// 方法2:原生写法

if (s != null && !s.trim().isEmpty()) { ... }

六、总结

String 作为 Java 中最核心的类型之一,其不可变性常量池优化是理解的基础,而创建方式、内存分配、常用方法及与 StringBuilder/StringBuffer 的区别,是开发和面试中的重点。掌握以下核心要点,即可应对大部分场景:

  1. 字面量创建走常量池,new创建走堆,intern()可手动入池;
  1. 不可变性导致修改操作创建新对象,频繁修改优先用StringBuilder;
  1. 线程安全用StringBuffer,单线程用StringBuilder,字符串常量用String;
  1. 判空、拼接、截取时注意避坑,优先使用工具类和高效方法。

如果需要进一步深入 String 的底层源码(如hashCode实现、replace方法原理)或更多实战场景案例,可以留言补充说明!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值