Java String 全面解析:从源码到常量池,再到面试高频题

目录

一、源码分析(JDK8)

成员变量

构造函数

常见方法

二、String常量池

[String 常量池到底是什么?](#String 常量池到底是什么?)

什么字符串会进常量池?

常量池的核心规则

[new String() 为什么不会进池?](#new String() 为什么不会进池?)

[JDK 7 以后常量池的重大变化](#JDK 7 以后常量池的重大变化)

[intern() 到底做了什么?](#intern() 到底做了什么?)

字符串拼接进不进池?

总结

三、常见面试点

[String 核心](#String 核心)

[创建 String 的两种方式](#创建 String 的两种方式)

[== 和 equals 的区别](#== 和 equals 的区别)

字符串常量池重点

字符串拼接重点

[String 常用方法核心特点](#String 常用方法核心特点)

[String 为什么不可变?](#String 为什么不可变?)

[StringBuilder 与 StringBuffer 区别](#StringBuilder 与 StringBuffer 区别)

String拼接原理

String常量池、运行时常量池、Class常量池区别

四、常见面试题

[1、创建对象 & 常量池](#1、创建对象 & 常量池)

2、字符串拼接

一、源码分析(JDK8)

成员变量

java 复制代码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    private final char value[];

    private int hash; // Default to 0

}
  • public final class String

    • public:任何地方都能用
    • finalString 不能被继承,不能写个子类继承它
    • class String:字符串类
  • implements

    • 实现了三个接口:
      • Serializable:可以序列化(网络传输 / 存文件)
      • Comparable<String>:可以比较大小(a.compareTo (b))
      • CharSequence:字符串的标准接口
java 复制代码
private final char value[];
  • private:外部不能访问、不能修改
  • final数组地址一旦赋值就不能改
  • char value[]:真正存字符串内容的字符数组
java 复制代码
private int hash; // Default to 0
  • 缓存哈希值
  • 第一次调用 hashCode() 时计算一次,之后直接用,不用重复算
  • 提升 HashMap 性能

构造函数

非常的多,我们之说下面的常见几种

1. 空参构造:public String()

java 复制代码
public String() {
    this.value = "".value;
}
  • 作用 :创建一个空字符串 ""
  • 底层 :直接复用常量池里空字符串的 char[],不新建数组
  • 使用String s = new String(); → 等价于 String s = ""
  • 注意 :直接写 "" 更高效

2. 字符串参数构造:public String(String original)

java 复制代码
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
  • 作用 :根据已有字符串创建一个新 String 对象
  • 底层直接复用原字符串的 char 数组(不复制),只新建 String 外壳
  • 使用String s = new String("abc");
  • 重点
    • 常量池字符串 + new创建两个对象(常量池 1 个 + 堆 1 个)
    • 日常开发不要这么写 ,直接 String s = "abc"; 最优

3. char 数组完整构造:public String(char value[])

java 复制代码
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
  • 作用 :把整个 char 数组转成字符串

  • 底层复制一份新数组(保护原数组不被修改)

  • 使用

    java 复制代码
    char[] arr = {'a','b','c'};
    String s = new String(arr);

4. char 数组截取构造:public String(char value[], int offset, int count)

java 复制代码
public String(char value[], int offset, int count) {
    // 边界校验:offset、count 不能为负、不能越界
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}
  • 作用 :从 char 数组中截取一段生成字符串

  • 参数

    • offset:起始下标
    • count:截取长度
  • 底层:复制截取范围的数组,严格校验越界

  • 使用

    java 复制代码
    char[] arr = {'a','b','c','d'};
    String s = new String(arr, 1, 2); // 从下标1开始,取2个 → "bc"

5. StringBuilder 构造:public String(StringBuilder builder)

java 复制代码
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
  • 作用 :把 StringBuilder 转成 String(最常用

  • 底层:无锁,直接复制数组,效率高

  • 使用场景 :拼接字符串后转 String

    java 复制代码
    StringBuilder sb = new StringBuilder();
    sb.append("a").append("b");
    String s = new String(sb); // → "ab"

对比

构造方法 核心用途 特点
new String() 空字符串 等价 ""
new String(String) 复制字符串 会创建新对象
new String(char[]) char 数组转字符串 复制数组,安全
new String(char[],off,len) 截取 char 数组 常用
new String(StringBuilder) 拼接后转字符串 常用

常见方法

基础信息获取方法

这些方法直接操作 value 字符数组,最简单高效

java 复制代码
// 返回字符串长度 = 字符数组长度
public int length() {
    return value.length;
}

// 判断是否为空:长度为0
public boolean isEmpty() {
    return value.length == 0;
}

// 获取指定索引的字符,越界抛异常
public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

字符 / 字节拷贝方法

底层用 System.arraycopynative 本地方法,速度极快)实现数据拷贝

java 复制代码
// 拷贝字符串到目标字符数组
public void getChars(...) {
    System.arraycopy(value, 源起始, 目标数组, 目标起始, 长度);
}

// 字符串转字节数组(支持编码)
public byte[] getBytes(String charsetName) {
    return StringCoding.encode(...);
}

字符串比较方法

1. equals ()

判断两个字符串内容是否完全相同

  1. 先判断引用地址 是否相同(==),相同直接返回 true
  2. 再判断类型是否是 String
  3. 最后逐字符比较
java 复制代码
public boolean equals(Object anObject) {
    if (this == anObject) return true; // 地址相同,直接相等
    if (anObject instanceof String) {
        String another = (String) anObject;
        int n = value.length;
        if (n == another.value.length) { // 长度不同,直接不等
            char[] v1 = value;
            char[] v2 = another.value;
            int i = 0;
            while (n-- != 0) { // 逐字符比较
                if (v1[i] != v2[i]) return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
2. compareTo()

字典序比较:逐字符比较 ASCII 码值,返回差值

3. 其他比较
  • equalsIgnoreCase():忽略大小写比较
  • startsWith()/endsWith():判断开头 / 结尾
  • regionMatches():比较指定区域字符

hashCode () 方法

String 的哈希算法:31 倍哈希法(经典高效)

java 复制代码
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        // 公式:h = 31 * h + val[i]
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

为什么用 31?

  1. 31 是质数,减少哈希冲突
  2. 31 * i = (i << 5) - i,JVM 会自动优化,运算极快

查找字符 / 子串方法

1. indexOf ()

朴素字符串匹配算法:先找首字符,再匹配剩余字符,效率满足日常使用

java 复制代码
static int indexOf(...) {
    char first = 目标首字符;
    // 遍历源数组,找到首字符后逐位匹配
}
2. lastIndexOf()

反向查找,逻辑和 indexOf 一致

截取、拼接、替换方法

1. substring()

不修改原字符串,创建新 String 对象返回:

java 复制代码
public String substring(int begin, int end) {
    return new String(value, begin, 长度);
}
2. concat()

字符串拼接:拷贝数组 → 追加内容 → 返回新字符串

3. replace()

替换字符:先找到第一个匹配字符,再统一替换,返回新字符串

大小写、去空格方法

  1. toLowerCase()/toUpperCase():考虑地区、特殊字符(如希腊字母)的大小写转换
  2. trim()删除首尾 <= 空格的字符(空格、制表符、换行符)

valueOf 系列

静态方法,将任意类型转为字符串:

java 复制代码
public static String valueOf(int i) { return Integer.toString(i); }
public static String valueOf(Object obj) {
    return obj == null ? "null" : obj.toString();
}

安全转换,避免空指针

intern () 本地方法

java 复制代码
public native String intern();

字符串常量池核心方法:

  • 调用时,将字符串放入常量池
  • 返回常量池中的引用 → 用于节省内存,实现字符串复用

二、String常量池

String 常量池到底是什么?

一句话: String 常量池 = 一块专门放字符串的缓存区域 目的:复用字符串,少创建对象,省内存,提高效率

它是 JVM 专门给 String 做的优化

什么字符串会进常量池?

双引号括起来的字符串字面量,会自动进常量池

java 复制代码
String s = "abc"; // 自动进池 

下面这些 绝对不会自动进池

  • new String("abc")
  • 字符串拼接 a + b
  • 从文件、配置、数据库、网络读取
  • 方法返回的字符串

这些都在 里,不进池

常量池的核心规则

  1. 创建字符串前,先去池里找有没有相同内容
  2. 有 → 直接复用池里对象,不新建
  3. 没有 → 创建后放进池里

所以:

java 复制代码
String s1 = "abc";
String s2 = "abc";

s1 == s2 → true

因为复用了同一个对象

new String() 为什么不会进池?

java 复制代码
String s = new String("abc");

执行过程:

  1. "abc" → 进常量池
  2. new String(...)在堆里创建一个新对象
  3. 堆对象 ≠ 池对象
java 复制代码
"abc" == new String("abc") → false

JDK 7 以后常量池的重大变化

JDK 6:

  • 常量池在 永久代(PermGen)
  • 空间小,容易 OOM
  • intern() 会把字符串复制到常量池

JDK 7+(包括 8、11、17):

  • 常量池移到 堆(Heap)
  • intern() 不再复制对象
  • 池里存的是堆对象的引用

这就是为什么:

java 复制代码
String s = new String("1") + new String("1");
s.intern();
String s2 = "11";
s == s2 → true(JDK8)

intern() 到底做了什么?

java 复制代码
s.intern();

作用: 把当前字符串手动加入常量池

规则:

  1. 池中有相同内容 → 返回池中的对象
  2. 池中没有 → 把当前对象存入池,返回自己

intern () 就是让堆字符串也能享受常量池复用

字符串拼接进不进池?

1. 纯常量拼接(进池)

java 复制代码
String s = "a" + "b" + "c";

编译器优化成 "abc"进池

2. 变量拼接(不进池)

java 复制代码
String s = a + b;

底层 new StringBuilder()堆对象,不进池

总结

"abc" → 常量池
new String("abc") → 堆
a + b → 堆
读取文件/配置/DB → 堆
常量池对象 == 常量池对象 → true
堆对象 == 常量池对象 → false
堆对象 == 堆对象 → false

  1. 常量池 = 字符串缓存,用来复用对象
  2. 双引号字面量自动进池
  3. new / 拼接 / 读取 → 不进池
  4. intern () 手动把堆字符串丢进池
  5. == 比地址,equals 比内容
  6. JDK7+ 常量池在堆里,intern 不复制,存引用

三、常见面试点

String 核心

  1. String 是不可变类(Immutable)
  2. 底层是 private final char[] value(JDK9 是 byte \[\])
  3. 所有字符串操作(截取、替换、拼接)都不会修改原字符串,只会返回新字符串
  4. 不可变 = 线程安全 = 可以安全缓存 = 可以常量池共享

String 一旦创建,内容永远不能改,改了就是新对象

创建 String 的两种方式

1. 字面量创建

java 复制代码
String s = "abc";

特点:

  • 自动进入字符串常量池
  • 重复创建会复用池里对象,不新建
  • 内存最省、最快

2. new 创建

java 复制代码
String s = new String("abc");

特点:

  • 一定在堆里创建新对象
  • 不会自动入池
  • 即使内容一样,== 也不相等

== 和 equals 的区别

  • == :比较地址
  • equals() :比较内容

规则:

  • 字面量之间 == 可以用
  • 只要有一个是堆对象(new / 读取 / 拼接),== 大概率 false
  • 比较字符串内容永远用 equals
java 复制代码
"abc" == "abc"        → true
new String("abc") == "abc" → false

字符串常量池重点

作用

复用字符串对象,减少内存,提高速度

什么时候自动入池?

只有代码里写的双引号字面量会自动入池

什么不会自动入池?

  • new String()
  • 字符串拼接
  • 配置文件读取
  • 数据库读取
  • 网络读取
  • 文件读取

手动入池:intern ()

  • 把堆字符串丢进常量池
  • 内容相同则复用池里对象
  • 目的:省内存 + 让 == 可以用

字符串拼接重点

java 复制代码
String s = "a" + "b" + "c";
  • 编译期优化 → 直接变成 "abc" → 入池
java 复制代码
String s = a + b;
  • 运行期拼接
  • 底层 new StringBuilder ()
  • 结果一定是堆对象,不入池

String 常用方法核心特点

  • substring() → 不修改原串,返回新串
  • replace() → 不修改原串,返回新串
  • trim() → 不修改原串,返回新串
  • toLowerCase() → 不修改原串,返回新串
  • hashCode() → 使用 31 倍哈希算法,缓存起来不重复计算

String 为什么不可变?

好处:

  1. 线程安全
  2. 可以缓存 hashCode
  3. 可以安全用作 HashMap key
  4. 可以进常量池共享,省内存
  5. 安全(防止被意外篡改)

StringBuilder 与 StringBuffer 区别

String

s += "a";

本质 创建新对象 性能差

StringBuilder

可变

java 复制代码
StringBuilder sb = new StringBuilder();

源码:char\[\] value;

append:直接修改数组

无锁,线程不安全,性能最高

StringBuffer

java 复制代码
public synchronized

大量同步锁,虽然保证了线程安全,但是性能低于Builder

单线程 使用StringBuilder,多线程使用StringBuffer

String拼接原理

java 复制代码
String s = a + b + c;

编译后结果

java 复制代码
new StringBuilder()
.append(a)
.append(b)
.append(c)
.toString();

反编译结果

java 复制代码
StringBuilder sb =
    new StringBuilder();

sb.append(a);
sb.append(b);
sb.append(c);

return sb.toString();

String常量池、运行时常量池、Class常量池区别

名称 存储内容
Class常量池 编译后的字面量和符号引用
运行时常量池 JVM加载类后产生
String常量池 专门缓存字符串对象

四、常见面试题

1、创建对象 & 常量池

java 复制代码
String s1 = "java";
String s2 = "java";
String s3 = new String("java");
String s4 = new String("java");

1.1、s1 == s2s1 == s3s3 == s4 结果是 true/false?

s1 == s2
java 复制代码
System.out.println(s1 == s2);

结果:true

原因:

java 复制代码
String s1 = "java";
String s2 = "java";

字符串字面量:

java 复制代码
"java"

会进入字符串常量池

执行 s1 时:

常量池不存在 "java"

创建 "java"

s1 指向常量池对象

执行 s2 时:

发现常量池已有 "java"

直接复用

s2 指向同一个对象

复制代码
s1 == s2

s1 == s2比较的是地址:所以结果是true

s1 == s3
java 复制代码
System.out.println(s1 == s3);

结果:false

分析如下

java 复制代码
String s3 = new String("java");

执行过程:

第一步

检查常量池:"java"已经存在

第二步

new String()在堆中创建新的 String 对象

源码:

java 复制代码
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

会创建一个新的 String 外壳对象

虽然s1.equals(s3)为true,因为比较的是值

但是s1 == s3比较的是引用地址,两个对象,所以结果为false

s3 == s4
java 复制代码
System.out.println(s3 == s4);

结果:false

执行:

java 复制代码
String s3 = new String("java");
String s4 = new String("java");

每次new String(...)都会创建新的堆对象

因此s3 == s4

比较的是:StringA的地址和StringB的地址

所以结果是false

1.2、一共创建了几个对象?

答案:3个对象

对象1:常量池中

"java"

对象2:产生的堆对象(s3)

java 复制代码
new String("java")

对象3:产生的堆对象(s4)

java 复制代码
new String("java")
java 复制代码
String Pool : 1个

Heap :
    s3对应对象 1个
    s4对应对象 1个

总计:
3个对象

2、字符串拼接

java 复制代码
// 代码片段1
String a = "ab" + "cd";

// 代码片段2
String x = "ab";
String y = "cd";
String b = x + y;

两段代码最终 ab 是否进入常量池?a == "abcd"b == "abcd" 结果分别是什么?

解释:+ 拼接字符串,编译期常量变量拼接 底层实现有什么区别?

java 复制代码
String a = "ab" + "cd";

结果

java 复制代码
a == "abcd"   // true

编译期发生了什么

因为:"ab"、"cd"都是字面量常量

编译器在编译阶段直接优化为

java 复制代码
String a = "abcd";

这叫:常量折叠(Constant Folding)

相当于字节码:

复制代码
LDC "abcd"

直接从常量池加载

java 复制代码
a == "abcd"

比较的是同一个常量池对象

结果是true

java 复制代码
String x = "ab";
String y = "cd";

String b = x + y;

结果:

java 复制代码
b == "abcd"   // false

为什么?

虽然:x 内容是"ab"

但是x是变量

编译器无法保证运行时一定还是:"ab"

所以不能做常量折叠

编译器会生成:

java 复制代码
String b =
    new StringBuilder()
        .append(x)
        .append(y)
        .toString();

JDK8 反编译基本类似

java 复制代码
StringBuilder sb =
    new StringBuilder();

sb.append(x);
sb.append(y);

String b = sb.toString();

执行时:

java 复制代码
String Pool

"ab"
"cd"
"abcd"
java 复制代码
Heap

new StringBuilder()
        ↓
new String("abcd")

注意:

java 复制代码
StringBuilder.toString()

返回的是:new String(...)新的堆对象,不会自动进入常量池

因此:

java 复制代码
b == "abcd"

变成了堆对象==常量池对象

结果是false

相关推荐
weixin_BYSJ19871 小时前
springboot鹿邑县旅游网站99312(源码+文档)
java·javascript·spring boot·python·django·flask·php
七夜zippoe1 小时前
DolphinDB异常检测引擎:实时告警
java·服务器·网络·异常·告警·dolphindb
Ws_1 小时前
WPF 面试题 + 参考答案,偏 C# 桌面端开发高频。
开发语言·c#·wpf
程序猿编码1 小时前
如何把远程文件变化“骗“成本地inotify事件:一个LD_PRELOAD钩子
c语言·开发语言·网络·tcp/ip·安全
星空椰10 小时前
Python 面向对象高级:继承与类定义详解
开发语言·python
橙淮10 小时前
并发编程(六)
java·jvm
拽着尾巴的鱼儿10 小时前
springboot openfeign 自定义feign 接口重试机制
java·spring boot·后端
白露与泡影10 小时前
2026大厂Java面试题大全!牛客网最新版
java·开发语言
凯瑟琳.奥古斯特10 小时前
高阶子查询题目精炼
开发语言·数据库·python·职场和发展·数据库开发