深入理解 String 类的不可变性及其内部数据结构

目录

String不可变的特性主要体现在以下几个方面:

String类的内部数据结构以及该数据结构的特点

String类方法示例:内容修改不操作内部数组,而是生成新字符串对象

总结


String不可变的特性主要体现在以下几个方面:

内容无法修改

举一个很简单的栗子~

在 Java 中,concat是 String类的方法,用于拼接字符串。但是运行结果还是cat,因为String
对象一旦被创建,其字符序列就不能被改变!

  String str1="cat";
        str1.concat("mao");
        System.out.println(str1);
        //运行结果:cat

方法操作产生新对象

如果想把mao拼接在cat的后面,那是不是就得重新创建String对象用来输出拼接后的结果呢,

如下:

 String str="cat";
        String result=str.concat("mao");
        System.out.println(result);
        //运行结构:catmao

内存地址固定

由于String内容不可变,在内存中他会被分配一个固定的地址。如果多个变量引用同一个字符串常量,它们都会指向同一个内存地址。如下:

String str1="cat";
        String str2="cat";
        System.out.println(str1==str2);
        //运行结果:true

哈希值固定

字符串内容不可变,所以其哈希值在创建后也固定不变。这使得String对象在作为HashMap的键或HashSet的元素时,能够高效地进行存储和查找。如下:

String作为HashMap的键

  //创建HashMap
        HashMap<String,Integer> map=new HashMap<>();
        map.put("橘猫",3);
        map.put("白猫",4);
        map.put("奶牛猫",10);

        //查找”奶牛猫“对应的值
        int num=map.get("奶牛猫");
        System.out.println(num);
        //运行结果:10

在这个例子中,由于String的不可变性保证了其哈希值的固定,HashMap可以根据键的哈希值迅速定位到对应的值,实现高效查找

有可能大家根据上述案例不能直观的感受到String的不可变性——哈希值固定,利用String类的hashCode方法再给大家举一个简单的栗子~

      String str1="矮脚曼基康";
      String str2="矮脚曼基康";
      int hash1=str1.hashCode();
      int hash2=str2.hashCode();
      System.out.println(hash1==hash2);
      //运行结果:true

因为内容相同所以哈希值也相同,都存储在字符串常量池中,str1 和 str2 实际上指向同一个字符串对象,所以运行结果为true

线程安全

多个线程共享 String对象

由于 String 对象不可变,多个线程可以安全地共享同一个 String 对象,而无需担心数据被其他线程修改。举个栗子~

public class StringThreadSafety {
    private static String sharedString = "shared data";

    public static void main(String[] args) {
        // 线程 1
        Thread thread1 = new Thread(() -> {
            System.out.println("Thread 1 reads: " + sharedString);
        });

        // 线程 2
        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2 reads: " + sharedString);
        });

        thread1.start();
        thread2.start();
    }
} 
//运行结果:Thread 1 reads: shared data
//         Thread 2 reads: shared data

String类的内部数据结构以及该数据结构的特点

内部数据结构

String类的核心数据结构是一个char类型的数组,定义如下:

private final char value[];
  • value是一个char数组,用于存储字符串中的每个字符。

  • final关键字表示该数组引用不可变,即value数组的引用不能被修改。

数据结构的特点

 不可变性(Immutability)

  • String对象是不可变的,一旦创建,其内容就不能被修改。

  • value数组被声明为final,意味着value数组的引用不能被重新赋值。

  • 任何对String的修改操作(如拼接、替换等)都会创建一个新的String对象,而不是修改原有对象。

字符编码

  • value数组存储的是Unicode字符,每个字符占用2个字节(16位),支持国际字符集。

  • Java使用UTF-16编码来表示字符。

 高效性

  • 由于String是不可变的,它可以被安全地共享和缓存,例如在字符串常量池中。

  • 不可变性也使得String对象在多线程环境下是线程安全的,无需额外的同步机制。

内存优化

  • Java对字符串常量进行了优化,相同的字符串字面量会被存储在字符串常量池中,以减少内存开销。

  • 例如,String s1 = "hello";String s2 = "hello";会指向常量池中的同一个对象。

性能考虑

  • 由于String的不可变性,频繁的字符串拼接操作(如使用+)可能会导致性能问题,因为每次拼接都会创建新的对象。

  • 对于频繁修改字符串的场景,建议使用StringBuilderStringBuffer,它们是可变的字符序列,性能更高。

String类方法示例:内容修改不操作内部数组,而是生成新字符串对象

这段代码是 Java 中 String 类的 substring(int beginIndex) 方法的实现。它的作用是从当前字符串中截取从 beginIndex 开始到字符串末尾的子字符串。

 public String substring(int beginIndex) {

        //检查起始索引的合法性
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }

        //计算子字符串的长度
        int subLen = value.length - beginIndex;

        //检查子字符串的合法性
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }

        //返回子字符串
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

我们着重来看该代码最后一句new String(value, beginIndex, subLen)

  • 创建一个新的字符串对象。

  • 实现细节:

    • 从原字符串的 value 数组中复制从 beginIndex 开始、长度为 subLen 的字符到新字符串对象中。

    • 新字符串对象有自己的 char[] 数组,与原字符串的 value 数组是独立的。

  • 意义:

    • 原字符串的内容没有被修改。

    • 新字符串对象是独立存在的,与原字符串对象无关。

总结

  • 不可变性String 是不可变的,任何对字符串内容的修改操作都会生成一个新的字符串对象。

  • 方法行为substringconcat 等方法不会修改原字符串的内部数组,而是通过复制字符数据生成新的字符串对象。

  • 性能与优化

    • 不可变性带来了线程安全、内存优化等优点。

    • 频繁的字符串修改操作可能会导致性能问题,建议使用 StringBuilder 或 StringBuffer

肥嘟嘟左卫门就讲到这里啦,麻烦大家一键三连!!!🐽

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值