二、Java中的String类、StringBuffer类、StringBuilder类

一、String类简介

在Java中String的对象可以通过直接赋值的形式进行实例化操作

public class Test {
    public static void main(String[] args) {
        // 通过直接赋值的形式为String对象进行实例化
        String str = "test";
        System.out.println(str);
    }
}

我们来看JDK中是怎么声明String类的:
#pic_center
从这里可以看出在String类中保存的是字符数组(作者用的是JDK1.8),而在JDK1.9以后是这样的:
#pic_center
在JDK1.9及以后的版本中String类中保存的是字节数组

String类本身属于一个系统类,除了上述可以通过直接赋值的形式进行对象的实例化之外,还可以用类本身提供的构造方法进行对象的实例化,构造方法的定义如下:

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

下面演示通过构造器实例化String类对象:

public class Test {
    public static void main(String[] args) {
        // 通过构造器为String对象进行实例化
        String str = new String("test");
        System.out.println(str);
    }
}

虽然上述两种方法的效果一样,但是两者之间是有本质区别的,关于这一点,将在后面进行说明。

二、字符串比较

基本数据类型的相等判断可以直接使用Java中提供的“= =”运算符来实现,直接进行数值的比较。如果在String类的对象上使用“==”运算符比较的并不是内容,而是字符串的堆内存地址

public class Test {
    public static void main(String[] args) {
        // 在 String 上使用==判断
        String str1 = "test"; //直接赋值定义字符串
        String str2 = new String("test"); //构造方法定义字符串
        String str3 = str2; //引用传递

        System.out.println(str1 == str2); //false
        System.out.println(str1 == str3); //false
        System.out.println(str2 == str3); //true
    }
}

对于字符串内容的判断,在String类中提供了相应的equals()方法,只需要通过String类的实例化对象调用即可,该方法定义如下:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            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;
            }
        }
        return false;
    }

这里不做深入讲解,当小伙伴们学完java基础部分之后可以回来看看这段源码,还是比较有意思的。
下边演示利用equals()方法实现字符串内容比较:

public class Test {
    public static void main(String[] args) {
        String str1 = "test"; //直接赋值定义字符串
        String str2 = new String("test"); //构造方法定义字符串

        System.out.println(str2.equals(str2)); //true
    }
}

equals()方法比较的是字符串的内容,上面的两个String对象虽然堆内存中的地址不同,但是他们的内容一样都是test。因此返回值为true。

“= =”和equals()方法的不同:

==是Java提供的关系运算符,是进行数值的相等判断。用来比较对象,就是比较对象的内存地址数值的比较
equals()方法是由String类提供的一个方法,负责进行字符串内容的比较

通俗的说,对于String类的对象来说,“==”比较的是对象在内存中的地址,而equals()方法比较的是字符串的内容是否一致。两者的侧重不同。

三、字符串常量

在程序中常量是不可被改变内容的统称,但是由于Java中的处理支持,可以直接用引号进行字符串常量的定义。而这种字符串的常量,严格意义上来讲是String类的匿名对象。

public class Test {
    public static void main(String[] args) {
        String str = "test"; //直接赋值定义字符串
        //字符串常量是String类的匿名对象,可以直接调用String类中的方法
        System.out.println("test".equals(str)); //字符串内容的比较,结果为true
    }
}

在实际的开发工作中字符串的比较操作:
由用户自己输入一个字符串,然后判断其是否与指定的内容相同,而这是用户可能不输入任何数据,即内容为null。

public class Test {
    public static void main(String[] args) {
        String str = null; //假设用户什么都没有输入
        if(str.equals("test")) //假设输入内容需要为test
        System.out.println("yes");
    }
}

结果为:

Exception in thread "main" java.lang.NullPointerException
	at com.zxl.Demo.Test.main(Test.java:7)

这时由于用户没有输入任何内容,即输入的内容为null,而null对象调用equals()将直接发生NullPointerException(空指针异常)。
我们可以通过一个小小的改动来避免这个异常的发生,代码如下:

public class Test {
    public static void main(String[] args) {
        String str = null; //假设用户什么都没有输入
        if("test".equals(str)) //假设输入内容需要为test
        System.out.println("yes");
    }
}

此时直接使用字符串常量来调用equals()方法,自然不会出现空指针异常。

四、两种实例化方法的比较

前边我们了解了两种String类的对象实例化的方法,那么这两种方法到底有什么区别呢?我们在实际卡发中到底应该用哪一种呢?现在我们就来说明这个问题。

1.分析直接实例化对象
public class Test {
    public static void main(String[] args) {
        String str = "test";
    }
}

此时在内存中会开辟一块堆内存,内存空间中保存有“test”字符串数据,并且栈内存将直接引用这个堆内存空间。即采用直接赋值的方式进行String类对象实例化,在内容相同的情况下不会开辟新的堆内存空间,而是直接指向已有的堆内存空间。

实际上,在JVM的底层存在一个对象池(String只是对象池中保存的一种数据类型,此外还有很多其他的类型),当代码中使用了直接赋值的方式进行String类对象实例化,会将此字符串对象所使用的匿名对象入池保存,如果之后还有其他以直接赋值的方式进行String类对象实例化,而且字符串内容相同时,将不会再开辟新的堆内存空间,而是使用已有的对象进行引用的分配,指向同一个对象。

2.分析构造方法实例化

如果使用构造方法进行String类对象的实例化操作,那么一定要使用关键字new,而使用关键字new就表示要开辟新的堆内存空间,这块堆内存空间的内容就是传入构造方法中的字符串数据。

public class Test {
    public static void main(String[] args) {
        String str = new String("test");
    }
}

因为每一个字符串都是一个String类的匿名对象,所以会先在堆内存中开辟一块空间保存字符串"test",而后又使用关键字new,开辟另一块堆内存空间,而真正使用的是用关键字new开辟的堆内存。之前定义的字符串常量开辟的堆内存空间将不会被任何的栈内存所指向,成为垃圾空间,并等待被GC回收。所以,使用构造方法的方式开辟的字符串对象,实际上会开辟两块空间,其中有一块空间将成为垃圾 。除了内存的浪费之外,如果使用了构造方法实例化String类对象,由于关键字new永远表示开辟新的堆内存空间,所以其内容不会保存在对象池中。

如果希望开辟的新内存数据也可以进入对象池保存,那么可以采用String类定义的一个手动入池的操作。

public class Test {
    public static void main(String[] args) {
        // String类对象手动入池
        String str1 = new String("test").intern(); // 开辟新对象并手动入池
        String str2 = "test"; // 直接赋值
        System.out.println(str1 == str2); //true
    }
}

由于使用了String类的intern()方法,所以会将指定的字符串对象保存在对象池中,随后如果使用直接赋值的方式将会自动引用已有的堆内存空间,所以地址判断的结果为true。

五、字符串常量池

Java中使用双引号就可以进行字符串实例化对象定义,如果处理不当就有可能为多个内容相同的实例化对象重复开辟堆内存空间,这样必然造成内存的浪费。在JVM中提供了一个字符串常量池(字符串对象池,本质是一个动态对象数组),所有通过直接赋值实例化的String类对象都可以自动保存在此常量池中。

在Java中字符串常量池一共分为两种:

静态常量池:是指程序(.class)在加载的时候会自动将此程序中保存的字符串、普通的常量、类和方法等信息,全部进行分配。
运行时常量池:当一个程序(
.class)加载之后,有一些字符串内容是通过String对象的形式保存后再实现字符串连接处理,由于String对象的内容可以改变,所以此时称为运行时常量池。

六、String类的常用方法

①equals():判断内容是否相等,区分大小写
②equalslgnoreCase():忽略大小写的判断内容是否相等
③length():获取字符的个数,即字符串的长度
④indexOf():获取字符在字符串中第1次出现的索引,索引从0开始,如果找不到,返回-1
⑤lastlndexOf():获取字符在字符串中最后1次出现的索引,索引从0开始,如找不到,返回-1
⑥substring():截取指定范围的子串
⑦trim():去掉字符串的前后空格
⑧charAt():获取某索引处的字符
⑨split():分割字符串,对于某些分割字符,我们需要转义
⑩format():格式字符串,%S字符串%c字符%d整型%.2f 浮点型

七、StringBuffer类

StringBuffer和String类似,底层也是用一个数组来存储字符串的值,数组的默认长度为16,即一个空的StringBuffer对象数组长度为16。实例化一个StringBuffer对象即创建了一个大小为16个字符的字符串缓冲区。
但是​当我们调用有参构造函数创建一个StringBuffer对象时,数组长度就不再是16了,而是根据当前对象的值来决定数组的长度,数组的长度为“当前对象的值的长+16”。所以一个 StringBuffer 创建完成之后,有16个字符的空间可以对其值进行修改。如果修改的值范围超出了16个字符,会先检查StringBuffer对象的原char数组的容量能不能装下新的字符串,如果装不下则会对 char 数组进行扩容。

那StringBuffer是怎样进行扩容的呢?
扩容的逻辑就是创建一个新的 char 数组,将现有容量扩大一倍再加上2,如果还是不够大则直接等于需要的容量大小。扩容完成之后,将原数组的内容复制到新数组,最后将指针指向新的 char 数组。

String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低;
StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上是更新字符串内容,但是没有每次更新地址,效率较高。

1.String类和StringBuffer类的转化
	//String --> StringBuffer
	String str = "hello";
	
	//方式 1 使用构造器
	//注意: 返回的才是 StringBuffer 对象,对 str 本身没有影响
	StringBuffer stringBuffer = new StringBuffer(str);
	
	//方式 2 使用的是 append 方法
	StringBuffer sb = new StringBuffer();
	stringBuffer1 = sb.append(str);
	//StringBuffer --> String
	StringBuffer stringBuffer = new StringBuffer("hello");
	
	//方式 1 使用 StringBuffer 提供的 toString 方法
	String str = stringBuffer3.toString();
	
	//方式 2: 使用构造器来搞定
	String str = new String(stringBuffer);
2.StringBuffer的常用方法:

StringBuffer append(xxx):拼接字符串
StringBuffer delete(int start,int end):删除指定范围的内容,左开右闭
StringBuffer replace(int start, int end, String str):替换指定范围的内容
StringBuffer insert(int offset, xxx):在指定位置插入指定的内容
StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str) : 返回指定子字符串在当前字符串中第一次出现处的索引
public String substring(int start,int end) :返回指定范围的子字符串
public int length() : 返回字符串的长度
public char charAt(int n ) : 获取指定索引处的字符
public void setCharAt(int n ,char ch) : 设置指定索引处的字符

八、StringBuilder类

StringBuilder定义了一个可变的字符序列。此类提供一个与StringBuffer兼容的API,但不保证同步(StringBuilder 不是线程安全的)。该类被设计用作 StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer要快。
StringBuilder是final类,不能被继承。

九、三种类的联系与区别

1.String、StringBuffer 和 StringBuilder 的比较
  1. StringBuilder和 StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
  2. String:不可变字符序列,效率低,但是复用率高。
  3. StringBuffer:可变字符序列、效率较高(增删)、线程安全
  4. StringBuilder:可变字符序列、效率最高、线程不安全
2.String、StringBuffer 和 StringBuilder的效率测试

效率 : StringBuilderr > StringBuffer > String

3.String、StringBuffer 和 StringBuilder 的选择

1.如果字符串存在大量的修改操作,一般使用 StringBuffer或StringBuilder
2.如果字符串存在大量的修改操作,并在单线程的情况,使用 StringBuilder
3.如果字符串存在大量的修改操作,并在多线程的情况,使用 StringBuffer
4、如果字符串很少修改,被多个对象引用,使用String。如配置信息等

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐song元

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值