Java String 吃透这篇就够了!

上篇文章我们讲解了基本类型低层的那些事,今天我们来聊一聊日常开发中用的最多的String。

一、String是什么?

String 是 Java 中的一个引用类型,用于表示文本字符串,它具有不可变性,一旦创建,就无法修改。

二、源码探究

java21源码:

arduino 复制代码
// 存储字符串的字节数组(不可变)
private final byte[] value;

// 编码标识:0=LATIN1,1=UTF16(JDK 9+ 新增)
private final byte coder;

// 哈希值缓存(懒加载)
private int hash;

// 哈希值是否为 0 的标记(优化判断)
private boolean hashIsZero;

// 控制是否启用紧凑字符串(JVM 注入,默认 true)
static final boolean COMPACT_STRINGS;

1、Java 9 之前,String 内部是用一个 char[] 来存储字符串的,char 类型占2个字节。

2、Java 9之后用byte[]存储,同时 引入了 Compact Strings ,核心思想是:根据字符串的内容,选择不同的编码方式来存储,以达到节省空间的目的

3、如果字符串只包含 Latin-1 字符,那么 value 数组的长度就等于字符串的长度。与之前的 char[] 相比,内存占用直接减半。

4、如果字符串包含了非 Latin-1 的字符(例如中文、日文、特殊符号等),那么它会使用 UTF-16 编码来存储,value 数组的长度是字符串 UTF-16 编码后字节数的总和。

5、valuecoderCOMPACT_STRINGS均被final修饰,保证了字符串的不可变性。

三、为什么 String 是不可变的?

1. 安全性

  • 字符串常被用作参数(如数据库连接、网络请求 URL),如果可变,容易被恶意篡改。
  • 多线程环境下,不可变对象天然线程安全。

2. 缓存哈希值

  • StringhashCode() 被频繁使用(如 HashMap 的 key)。不可变意味着哈希值可缓存,提升性能。

3. 字符串常量池优化

  • JVM 会复用相同的字符串字面量,节省内存。

四、字符串常量池

JVM 维护的一块特殊区域,避免重复创建相同内容的字符串。

ini 复制代码
String a = "hello";
String b = "hello";
System.out.println(a == b); // true!因为都指向常量池中的同一个对象

String c = new String("hello");
String d = new String("hello");
System.out.println(c == d); // false!new 创建的是堆中的新对象
ini 复制代码
//intern()的作用:确保返回一个与当前字符串内容相同的、来自字符串常量池的引用。
String e = new String("hello").intern();
System.out.println(a == e); // true!.intern() 会将字符串加入常量池并返回引用---

五、== 和 equals()的区别

这是新手最容易踩的坑!

  • ==:比较引用地址(是否是同一个对象)
  • equals():比较内容是否相等
ini 复制代码
String x = "java";
String y = new String("java");

System.out.println(x == y);        // false
System.out.println(x.equals(y));   // true

✅ 最佳实践:永远用 .equals() 比较字符串内容!

六、String、StringBuilder 与 StringBuffer三者的区别

高频面试点:

类型 可变性 线程安全 性能 适用场景
String 不可变 低(频繁拼接会创建大量对象) 少量字符串操作
StringBuilder 可变 单线程下大量拼接
StringBuffer 可变 多线程下拼接
ini 复制代码
// 错误示范(性能差):
String s = "";
for (int i = 0; i < 10000; i++) {
    s += "a"; // 每次都新建对象!
}

// 正确做法:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append("a");
}
String result = sb.toString();

七、手撸简版String类

java 复制代码
package org.example.javase;

public class MyString {

    // 简化版,我们仍用char[]存储,内部存储字符的数组,final 保证引用不可变
    private final char[] value;

    /**
     * 构造函数
     * @param value 字符数组
     */
    public MyString(char[] value) {
        // 进行防御性拷贝,防止外部数组修改影响内部状态
        this.value = new char[value.length];
        System.arraycopy(value, 0, this.value, 0, value.length);
    }

    /**
     * 返回字符串长度
     * @return 长度
     */
    public int length() {
        return value.length;
    }

    /**
     * 返回指定索引处的字符
     * @param index 索引
     * @return 字符
     * @throws StringIndexOutOfBoundsException 如果索引越界
     */
    public char charAt(int index) {
        if (index < 0 || index >= value.length) {
            throw new StringIndexOutOfBoundsException("Index: " + index + ", Length: " + value.length);
        }
        return value[index];
    }

    /**
     * 比较字符串内容是否相等
     * @param anObject 要比较的对象
     * @return 如果相等则为 true,否则为 false
     */
    @Override
    public boolean equals(Object anObject) {
        // 1. 引用相等,直接返回 true
        if (this == anObject) {
            return true;
        }
        // 2. 类型不同,返回 false
        if (!(anObject instanceof MyString)) {
            return false;
        }
        // 3. 类型相同,比较内容
        MyString anotherString = (MyString) anObject;
        int n = value.length;
        // 先比较长度,长度不同则内容一定不同
        if (n != anotherString.value.length) {
            return false;
        }
        // 逐字符比较
        for (int i = 0; i < n; i++) {
            if (value[i] != anotherString.value[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * 计算哈希值
     * 算法参考:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * @return 哈希值
     */
    @Override
    public int hashCode() {
        int h = 0;
        if (value.length > 0) {
            char[] val = value;
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
        }
        return h;
    }

    /**
     * 返回字符串本身
     * @return 字符串
     */
    @Override
    public String toString() {
        // 直接利用 Java 原生 String 的构造函数来创建一个字符串并返回
        return new String(value);
    }

    /**
     * 拼接字符串
     * @param str 要拼接的字符串
     * @return 拼接后的新字符串
     */
    public MyString concat(MyString str) {
        if (str == null || str.length() == 0) {
            return this; // 如果拼接的是空字符串,返回原对象
        }
        int len1 = this.value.length;
        int len2 = str.value.length;
        // 创建一个新的字符数组来存储拼接后的结果
        char[] buf = new char[len1 + len2];
        // 复制当前字符串的内容
        System.arraycopy(this.value, 0, buf, 0, len1);
        // 复制要拼接的字符串的内容
        System.arraycopy(str.value, 0, buf, len1, len2);
        // 返回一个新的 MyString 对象
        return new MyString(buf);
    }

    /**
     * 截取子字符串
     * @param beginIndex 开始索引(包含)
     * @return 截取后的新字符串
     * @throws StringIndexOutOfBoundsException 如果索引越界
     */
    public MyString substring(int beginIndex) {
        if (beginIndex < 0 || beginIndex > value.length) {
            throw new StringIndexOutOfBoundsException("beginIndex: " + beginIndex);
        }
        int subLen = value.length - beginIndex;
        // 如果从0开始截取,且长度不变,直接返回原对象
        if (beginIndex == 0 && subLen == value.length) {
            return this;
        }
        // 创建新的字符数组
        char[] subValue = new char[subLen];
        System.arraycopy(value, beginIndex, subValue, 0, subLen);
        return new MyString(subValue);
    }

    public static void main(String[] args) {
        char[] chars1 = {'H', 'e', 'l', 'l', 'o'};
        MyString str1 = new MyString(chars1);

        System.out.println("str1: " + str1.toString());
        System.out.println("str1.length(): " + str1.length());
        System.out.println("str1.charAt(1): " + str1.charAt(1));

        char[] chars2 = {'W', 'o', 'r', 'l', 'd'};
        MyString str2 = new MyString(chars2);
        MyString concatenated = str1.concat(str2);
        System.out.println("str1.concat(str2): " + concatenated.toString());

        MyString substr = concatenated.substring(5);
        System.out.println("concatenated.substring(5): " + substr.toString());

        MyString str3 = new MyString(chars1);
        System.out.println("str1.equals(str3): " + str1.equals(str3));
        System.out.println("str1.equals(str2): " + str1.equals(str2));

        System.out.println("str1.hashCode(): " + str1.hashCode());
        System.out.println("str3.hashCode(): " + str3.hashCode());

        // 尝试修改原字符数组,验证不可变性
        chars1[0] = 'X';
        System.out.println("After modifying chars1, str1: " + str1.toString());
    }
}

七、思考题

下边的代码输出什么?评论区说出你的答案。

ini 复制代码
String s1 = "ab";
String s2 = "a" + "b";
System.out.println(s1 == s2); // ?

String a = "a";
String s2 = a + "b"; // 运行时拼接
System.out.println(s1 == s2); // ?

觉得写的不错的小伙伴 ,点个关注,更新不迷路

相关推荐
靠沿3 小时前
Java数据结构初阶——LinkedList
java·开发语言·数据结构
qq_12498707533 小时前
基于springboot的建筑业数据管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·毕业设计
一 乐4 小时前
宠物管理|宠物共享|基于Java+vue的宠物共享管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·springboot·宠物
小时前端4 小时前
“能说说事件循环吗?”—— 我从候选人回答中看到的浏览器与Node.js核心差异
前端·面试·浏览器
a crazy day4 小时前
Spring相关知识点【详细版】
java·spring·rpc
白露与泡影4 小时前
MySQL中的12个良好SQL编写习惯
java·数据库·面试
foundbug9994 小时前
配置Spring框架以连接SQL Server数据库
java·数据库·spring
凯酱4 小时前
@JsonSerialize
java
悦悦子a啊4 小时前
项目案例作业(选做):使用文件改造已有信息系统
java·开发语言·算法
lkbhua莱克瓦244 小时前
Java项目——斗地主小游戏(控制台版)
java·开发语言·windows·斗地主项目