【我与Java的成长记】之String类详解

【我与Java的成长记】之String类详解

系列文章目录

能看懂文字就能明白系列
C语言笔记传送门
java笔记传送门
🌟 个人主页:古德猫宁-

🌈 信念如阳光,照亮前行的每一步



前言

String 类是 Java 中用于表示字符串的核心类之一。它提供了丰富的方法来操作字符串,包括连接、拆分、替换、查找、截取等,使得字符串处理变得简单和高效。

本节重点:

  • 理解String为什么具有不可变性
  • StringBuffer、StringBuilder和String的区别
  • 为什么StringBuilder比StringBuffer总是快了一毫秒
  • 模拟简单String类方法的实现

一、字符串构造

常用的字符串构造有以下三种:

public static void main(String[] args) {
        //new一个String对象
        String s1 = new String("hello");
        
        //使用常量串进行构造
        String s2 = "hello";
        
        //使用字符数组进行构造
        char[] arr = {'h','e','l','l','o'};
        String s3 = new String(arr);
    }

在这里特别注意的是:

  1. s1,s2,s3存储的是一个地址,由地址指向所引用的对象,可是当我们打印s1,s2,s3的时候,却发现输出的结果是“hello”

    原因是String调用了toString的构造方法,我们打开String的源码看看



    最后是print输出“hello”。

  2. String类是引用类型,内部并不存储字符串本身,在String类的实现源码中,String类实例变量如下:

    我们可以看到,String类中有两个成员,一个是数组value[],一个是整型hash(默认为0),当我们调试的时候,我们可以看到是value[]存储的“hello”

    如下图所示,我们可以这样理解:

  3. 字符串常量后面没有以\0标记结尾,我们可以调用length看下字符串的长度。

二、String类的特性

String 对象具有不可变性的,即一旦创建就不能被修改。这种不可变性使得字符串在多线程环境下更安全,也可以被用作常量,有助于提高代码的可读性和可维护性。
我们先来看以下代码:

public static void main(String[] args) {
        String s = "hello";
        s = s.concat(" java");
        System.out.println(s);
    }

输出结果:

这里很多人可能就有疑问了,不是说String对象具有不可变性吗,为什么这里还可以被改变呢?
虽然字符串的内容看起来变了,从“hello”变成了“hello java”,但实际上,原来的字符串还是没有改变,而是得到了一个新的字符串了,它的内容是“hello java”。

具体如图所示:

为什么字符串具有不可变性呢?
一方面,String类中的字符实际保存在内部维护的value字符数组中,另一方面,String类被final修饰,表明该类不能被继承,而且value也被final修饰,表明value自身的值不能改变,既不能引用其他字符数组,但是其引用空间中的内容可以修改。

特别注意的是:字符串不可变是因为其内部保存字符的数组被final修饰了,因此不能改变,这句话是错误的,不是因为String类自身,或者其内部value被final修饰而不能被修改。

而是因为value[]被private修饰,我们无法拿到value,自然不能通过value去修改值。

为什么String要设计成不可变的?

  1. 方便实现字符串对象池,如果String可变,那么对象池就需要考虑写深拷贝的问题了
  2. 不可变对象是线程安全的
  3. 不可变对象更方便缓存hash code,作为key时可以更高效的保存到HashMap中

三、StringBuilder和StringBuffer

注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低。
所以为了方便字符串的修改,java中又提供了StringBuilder和StringBuffer类。

StringBuffer和StringBuilder虽然也封装了一个字符数组,但与String不同,其定义如下:

char[] value;

与String不同的是,它并不是被final所修饰,所以是可以修改的。

我们可以做个简单的测试:

public static void main(String[] args) {
        long start = System.currentTimeMillis();
        String s = "";
        for(int i = 0; i < 10000; ++i){
            s += i;
     }
        long end = System.currentTimeMillis();
        System.out.println("String:"+(end - start));

        start = System.currentTimeMillis();
        StringBuffer sbf = new StringBuffer("");
        for(int i = 0; i < 10000; ++i){
                sbf.append(i);
     }
        end = System.currentTimeMillis();
        System.out.println("StringBuffer:"+(end - start));

        start = System.currentTimeMillis();
        StringBuilder sbd = new StringBuilder();
        for(int i = 0; i < 10000; ++i){
            sbd.append(i);
    }
        end = System.currentTimeMillis();
        System.out.println("StringBuilder:"+(end - start));
    }


从结果可以看出,在对String类进行修改时,效率是非常慢的,因此:尽量避免对String的直接修改,如果要修改尽量使用StringBuffer或者StringBuilder。

注意:String和StringBuilder类不能直接转换。如果要想互相转换,可以采用如下原则:

  • String变为StringBuilder:利用StringBuilder的构造方法或者append()方法。
  • StringBuilder变为String:利用toString()方法。

总结

为什么StringBuilder和StringBuffer比String快那么多呢?

简单来说String 是不可变的,每次对 String 进行操作(如连接、拼接、替换等),都会生成一个新的 String 对象,原始的 String 对象不变。这样会频繁地创建新的对象,产生大量的临时对象,导致内存开销和垃圾回收压力增大。
StringBuffer 和 StringBuilder 是可变的,它们提供了修改字符串内容的方法,而不是创建新的对象。这样可以减少对象的创建和销毁,提高了性能。
StringBuffer 和 StringBuilder 内部都是使用可变长度的字符数组(char[])来存储字符串内容。它们的方法都是基于字符数组的操作,如扩容、复制、移动等。这种直接操作字符数组的方式可以提高性能,避免了额外的对象创建和拷贝操作。

为什么StringBuilder比StringBuffer总是快了一毫秒呢?

原因是StringBuilder 是非线程安全的,而StringBuffer 是线程安全的,StringBuffer 中的方法是同步的,即它们被设计为可以安全地在多线程环境中使用。这是通过在每个方法上使用 synchronized 关键字来实现的,以确保同一时刻只有一个线程可以访问 StringBuffer 的方法。

当方法被 synchronized 修饰时,就意味着该方法在同一时间只能被一个线程执行,其他线程需要等待。

三、StringBuilder一些常见的方法

由于StringBuilder和StringBuffer两大类大部分功能是相同的,这里简单介绍StringBuilder一些常用的方法,其他方法可自行浏览:StringBuilder在线文档

方法 说明
StringBuilder append(String str) 在尾部追加,相当于String的+=,可以追加:boolean,char,char[],double,float,int,long,Object,String,StringBuilder的变量
char charAt(int index) 获取index位置的字符
int length() 获取字符串的长度
int capacity() 获取底层保存字符串空间总的大小
void ensureCapacity(int mininmunCapacity) 扩容
void setChar(int index,char ch) 将index位置的字符设置为ch
int indexOf(String str) 返回str第一次出现的位置
int indexOf(String str,int fromIndex) 从fromIndex位置开始查找str第一次出现的位置
StringBuffer deleteCharAt(int index) 删除index位置的字符
StringBuffer delete(int start,int end) 删除[start,end)区间内的字符
StringBuffer replace(int start,int end,String str) 将[start,end)位置的字符替换为str
String substring(int start) 从star开始一直到末尾的字符以String的形式返回
StringBuffer reverse() 反转字符串

转载请说明出处内容投诉
CSS教程_站长资源网 » 【我与Java的成长记】之String类详解

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买