Java String 类深入解析

Java String 类深入解析

一、前言

在 Java 开发中,String 字符串是使用频率最高的类之一,无论是日常业务开发、数据处理还是框架源码,都离不开 String。但很多开发者只停留在 new String()equals()concat() 等基础用法上,对其底层原理、内存机制、性能优化一知半解,极易写出 Bug 或低效代码。

本文将从源码结构、不可变性、内存存储、常用方法解析、字符串拼接、常见面试点六个维度,全面剖析 Java String 类,帮你从根源吃透字符串。

二、String 类基础定义与源码结构

1. 类定义

Stringjava.lang 包下的核心类,被 final 修饰,不能被继承,实现了三个核心接口:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // 源码核心部分
}
  • Serializable:支持序列化,方便网络传输、持久化存储
  • Comparable<String>:支持字符串自然排序
  • CharSequence:字符序列接口,与 StringBuilderStringBuffer 统一规范

2. 核心成员变量

// 用于存储字符串的字符数组,final 修饰,不可修改
private final char value[];
// 字符串的哈希码,缓存哈希值,避免重复计算
private int hash; 

关键结论String 的本质是一个不可变的 char 数组,所有字符串操作都不会修改原对象,而是生成新对象。

三、String 不可变性:核心特性与底层原理

1. 什么是不可变性?

一旦 String 对象被创建,其字符内容、长度、哈希值都无法被修改

任何对字符串的操作(截取、替换、拼接),都会返回新的 String 对象,原对象保持不变。

示例代码:

public class StringImmutableTest {
    public static void main(String[] args) {
        String str = "Hello";
        // 看似修改,实际返回新对象
        str = str.replace("H", "h"); 
        System.out.println(str); // 输出 hello
    }
}

执行 replace 后,原 "Hello" 对象并未改变,只是 str 引用指向了新的 "hello" 对象。

2. 不可变性的实现原因

  1. 类被 final 修饰:禁止继承,避免子类破坏不可变性
  2. 存储字符的 value 数组被 final 修饰:数组引用无法指向新内存
  3. value 数组被 private 修饰:外部无法直接访问修改数组元素
  4. 所有方法都不修改原数组:替换、截取等操作均新建数组 + 新对象

3. 不可变性的优势

  1. 线程安全:多线程环境下无需加锁,直接使用
  2. 字符串常量池复用:节省内存,提升性能
  3. 哈希值缓存HashMapHashSet 中作为 Key 效率极高
  4. 安全可靠:避免敏感字符串被意外 / 恶意修改

四、String 对象创建方式与内存存储

String 有两种创建方式,内存位置、性能、复用性完全不同,这是面试和开发必考点。

1. 字面量创建

String str1 = "Java";
  • 直接在字符串常量池(String Constant Pool) 中创建对象
  • 若常量池已存在 "Java",直接返回引用,不会新建对象
  • 内存复用,性能最优,无冗余对象

2. new String () 创建

String str2 = new String("Java");
  • 第一步:检查常量池,无 "Java" 则在常量池创建
  • 第二步:在堆内存中新建 String 对象,指向常量池的字符数组
  • 无论常量池是否有值,堆中一定会创建新对象
  • 产生冗余对象,不推荐使用

3. 两种创建方式对比

public class StringCreateTest {
    public static void main(String[] args) {
        String str1 = "Java";
        String str2 = "Java";
        String str3 = new String("Java");
        String str4 = new String("Java");

        // true  :常量池复用,同一引用
        System.out.println(str1 == str2); 
        // false :堆中不同对象
        System.out.println(str3 == str4); 
        // false :str1(常量池),str3(堆)
        System.out.println(str1 == str3); 
    }
}

== 与 equals () 区别

  • ==:基本类型比较值,引用类型比较内存地址
  • String.equals():重写了方法,比较字符串内容,开发中判断字符串相等必须用 equals()

五、String 常用方法源码 + 代码分析

1. equals () 方法:内容比较

// 源码简化版
public boolean equals(Object anObject) {
    // 1. 引用相同,直接返回 true
    if (this == anObject) {
        return true;
    }
    // 2. 判断是否为 String 类型
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // 3. 长度相同,再逐字符比较
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    // 4. 类型/长度/字符不同,返回 false
    return false;
}

使用示例

String a = "hello";
String b = new String("hello");
// true,比较内容而非地址
System.out.println(a.equals(b)); 

2. hashCode () 方法:哈希值计算

// 源码:哈希公式 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
  • 选用 31 :质数,减少哈希冲突,JVM 可优化(31*i = (i<<5)-i
  • 哈希值缓存:计算一次后永久缓存,提升 HashMap 性能

3. 其他高频常用方法

public class StringMethodTest {
    public static void main(String[] args) {
        String str = "  Hello Java  ";
        
        // 1. 去除首尾空格
        System.out.println(str.trim()); // Hello Java
        
        // 2. 获取长度
        System.out.println(str.length()); // 13
        
        // 3. 截取子串 [begin, end)
        System.out.println(str.substring(2, 7)); // Hello
        
        // 4. 转大写/小写
        System.out.println(str.toUpperCase()); //   HELLO JAVA  
        
        // 5. 判断是否包含子串
        System.out.println(str.contains("Java")); // true
        
        // 6. 字符串分割
        String[] arr = "a,b,c".split(",");
        // 7. 字符替换
        "Hello".replace('l', 'x');
    }
}

六、字符串拼接:原理与性能坑点

字符串拼接是开发中最常用操作,但不同方式性能天差地别

1. 直接 + 拼接

String a = "Hello" + " Java";
  • 编译期优化:直接合并为 "Hello Java",存入常量池
  • 变量拼接:a + b 底层会自动创建 StringBuilder 调用 append()

2. 循环中 + 拼接

// 低效代码:循环创建大量 StringBuilder 对象
String s = "";
for (int i = 0; i < 1000; i++) {
    s += i;
}

问题:每次循环都 new StringBuilder()append()toString(),产生大量临时对象,内存飙升、性能极差。

3. 最优拼接方案

  • 单线程 / 非线程安全StringBuilder(JDK 1.5+,性能最高)
  • 多线程 / 线程安全StringBuffer(方法加 synchronized

高效代码示例

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String result = sb.toString();

七、String、StringBuffer、StringBuilder 对比(面试必考)

特性 String StringBuffer StringBuilder
不可变性 不可变 可变 可变
线程安全 安全 安全(synchronized) 不安全
性能
使用场景 少量字符串操作 多线程拼接 单线程高频拼接

总结

  • 固定字符串:用 String
  • 高频拼接:单线程用 StringBuilder,多线程用 StringBuffer

八、总结

  1. String 是不可变类:底层是 final char 数组,所有操作生成新对象
  2. 创建方式:字面量(常量池,推荐)、new String()(堆,慎用)
  3. 比较相等:必须用 equals()== 比较地址
  4. 拼接性能:循环拼接用 StringBuilder,禁止用 +
  5. 核心价值:不可变性保证了安全、高效、线程安全,是 Java 字符串设计的精髓
posted @ 2026-05-02 23:09  阿尹想学会C++  阅读(17)  评论(0)    收藏  举报