Java复习笔记(八)-----字符串操作

本文深入剖析了Java中的String、StringBuffer及StringBuilder的区别与联系,并通过实验对比了不同字符串拼接方式的性能表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

String

String在开发当中可能比基本数据类型还要常用,但需要明确的是String并不是基本数据类型,而是一个不可变的对象。查看源码也会发现String类是用final修饰的,我们每次对String类型的操作改变,实际上都是生成一个新的String类型对象。

String既然为对象,那么在未初始化时,他的值为null;

public static void main(String[] args) {
        String str1 = new String();
        String str2 = "";
        String str3;
        System.out.println(str1);
        System.out.println(str2);
        System.out.println(str1.equals(str2));
}   
------------------------------------
Output:
""
""
true
------------------------------------
str1 和 str2 表示已经String已经new,已经创建了对象的引用,是需要分配内存空间的,只是值为 "" 空串,
而 str3 没有初始化和赋值,为null.没有创建对象的引用,不需要分配内存空间。

在字符串中存在一个非常特殊的地方,就是字符串池,每当我们创建一个字符串对象时,首先就会检查字符串池中是否存在面值相等的字符串,如果有,则不再创建,直接放回字符串池中对该对象的引用,若没有则创建然后放入到字符串池中并且返回新建对象的引用。这个机制是非常有用的,因为可以提高效率,减少了内存空间的占用。所以在使用字符串的过程中,推荐使用直接赋值(即String s=”aa”),除非有必要才会新建一个String对象(即String s = new String(“aa”))。

String的常用方法
方法介绍
String.length()字符串的长度 (.length)
String.charAt(i)通过下标找字符
String.lastIndexOf(String str)通过字符串找下标,找字符最后一次出现的位置
String.indexOf(String str)通过字符串找下标,如果没有找到返回-1
String.indexOf(String a,String b)从第b个位置开始找 a 出现的位置
String.isEmpty()判断字符串是否为”“空串(判断String的length()是否为0)
String.contains(String str)判断字符串是否包含某个字符串
String.split(String str)以某个字符串分割 返回String[]
String.substring(int beginIndex)截取字符串 返回下标beginIndex之后的字符串
String.substring(int a, int b)从a切割到b
String.replace(String a, String b)以b字符串替换a字符串
String.startsWith(String prefix)String是否以某个字符串开始,返回boolean类型
String.endsWith(String suffix)String是否以某个字符串开始,返回boolean类型
String.concat(String str)拼接字符串,相当于+
String.equals(String str)比较两个字符串是否相等
String.equalsIgnoreCase(String str)忽略大小写比较两个字符串是否相等
String.toLowerCase()转换成小写
String.toUpperCase()转换成大写
String.getBytes()将一个字符串转换成为字节数组
String.trim()去除字符串两边的空格
String.valueOf()把其他类型变成String类型

StringBuffer

StringBuffer和String一样都是用来存储字符串的,只不过由于他们内部的实现方式不同,导致他们所使用的范围不同,对于StringBuffer而言,他在处理字符串时,若是对其进行修改操作,它并不会产生一个新的字符串对象,所以说在内存使用方面它是优于String的。

其实在使用方法,StringBuffer的许多方法和String类都差不多,所表示的功能几乎一模一样,只不过在修改时StringBuffer都是修改自身,而String类则是产生一个新的对象,这是他们之间最大的区别。

同时StringBuffer是不能使用=进行初始化的,它必须要产生StringBuffer实例,也就是说你必须通过它的构造方法进行初始化。

在StringBuffer的使用方面,它更加侧重于对字符串的变化,例如追加、修改、删除,相对应的方法

StringBuffer的常用方法
方法介绍
StringBuffer.append();在末尾拼接字符
StringBuffer.reverse()反转字符串
StringBuffer.insert(int a,String b)在a的位置插入字符串b
StringBuffer.delete(int start,int end)移除从位置a到位置b的内容

StringBuilder

StringBuilder也是一个可变的字符串对象,它与StringBuffer操作方法基本相同,不同之处就在于StringBuilder是线程不安全的,基于这点,它的速度一般都比StringBuffer快。StringBuider的主要操作也是append与insert方法。这两个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符添加或插入到字符串生成器中。

正确使用String、StringBuffer、StringBuilder

字符串类型可变线程安全操作
String字符串常量不可变安全产生一个新对象
StringBuffer字符串变量可变安全自身内容发生改变
StringBuilder字符串变量可变不安全自身内容发生改变

使用方面由于String每次修改都需要产生一个新的对象,所以对于经常需要改变内容的字符串最好选择StringBuffer或者StringBuilder.而对于StringBuffer,每次操作都是对StringBuffer对象本身,它不会生成新的对象,所以StringBuffer特别适用于字符串内容经常改变的情况下。

但是并不是所有的String字符串操作都会比StringBuffer慢,在某些特殊的情况下,String字符串的拼接会被JVM解析成StringBuilder对象拼接,在这种情况下String的速度比StringBuffer的速度快。如:

public static void main(String[] args) {

        long start_01 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            String name1 = "I" + " am" + " chenssy" ;
        }
        long end_01 = System.currentTimeMillis();
        System.out.println("String所消耗的时间:" + (end_01 - start_01) + "毫秒");


        long start_02 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            StringBuffer name2 = new StringBuffer("I").append("  am").append(" chenssy");
        }
        long end_02 = System.currentTimeMillis();
        System.out.println("StringBuffer所消耗的时间:" + (end_02 - start_02) + "毫秒");
}
------------------------------------
Output:
String所消耗的时间:3毫秒
StringBuffer所消耗的时间:59毫秒
------------------------------------

对于这两种方式,你会发现第一种比第二种快太多了,在这里StringBuffer的优势荡然无存。其真实的原因就在于JVM做了一下优化处理,其实String name = “I” + “am” + “chenssy” ,在JVM眼中就是String name = “I am chenssy ” ;这样的方式对于JVM而言,真的是不要什么时间。但是如果我们在这个其中增加一个String对象,那么JVM就会按照原来那种规范来构建String对象了。如下:

public static void main(String[] args) {
        String str = "I";
        long start_01 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            String name1 = str + "am" + "chenssy" ;
        }
        long end_01 = System.currentTimeMillis();
        System.out.println("String所消耗的时间:" + (end_01 - start_01) + "毫秒");


        long start_02 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            StringBuffer name2 = new StringBuffer("I").append(" am").append("chenssy");
        }
        long end_02 = System.currentTimeMillis();
        System.out.println("StringBuffer所消耗的时间:" + (end_02 - start_02) + "毫秒");
}
------------------------------------
Output:
String所消耗的时间:121毫秒
StringBuffer所消耗的时间:55毫秒
------------------------------------

对于这三者使用的场景做如下概括(参考:《编写搞质量代码:改善java程序的151个建议》):

  • String:在字符串不经常变化的场景中可以使用String类,如:常量的声明、少量的变量运算等。

  • StringBuffer:在频繁进行字符串的运算(拼接、替换、删除等),并且运行在多线程的环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装等。

  • StringBuilder:在频繁进行字符串的运算(拼接、替换、删除等),并且运行在多线程的环境中,则可以考虑使用StringBuffer,如SQL语句的拼装、JSON封装等

字符串拼接方式

对于字符串而言我们经常是要对其进行拼装处理的,在java中提高了三种拼装的方法:+、concat()以及append()方法。这三者之间存在什么区别呢?先看如下示例:

public static void main(String[] args) {
        // +
        long start_01 = System.currentTimeMillis();
        String a = "a";
        for (int i = 0; i < 100000; i++) {
            a += "b";
        }
        long end_01 = System.currentTimeMillis();
        System.out.println("+    所消耗的时间:" + (end_01 - start_01) + "毫秒");

        // concat()
        long start_02 = System.currentTimeMillis();
        String c = "c";
        for (int i = 0; i < 100000; i++) {
            c = c.concat("d");
        }
        long end_02 = System.currentTimeMillis();
        System.out.println("concat所消耗的时间:" + (end_02 - start_02) + "毫秒");

        // append
        long start_03 = System.currentTimeMillis();
        StringBuffer e = new StringBuffer("e");
        for (int i = 0; i < 100000; i++) {
            e.append("d");
        }
        long end_03 = System.currentTimeMillis();
        System.out.println("append所消耗的时间:" + (end_03 - start_03) + "毫秒");
    }

------------------------------------    
+     所消耗的时间:5795毫秒
concat所消耗的时间:2274毫秒
append所消耗的时间:4毫秒
------------------------------------

从上面的运行结果可以看出,append()速度最快,concat()次之,+最慢。原因请看下面分解:

+ 方式拼接字符串

编译器对+进行了优化,它是使用StringBuilder的append()方法来进行处理的,我们知道StringBuilder的速度比StringBuffer的速度更加快,但是为何运行速度还是那样呢?主要是因为编译器使用append()方法追加后要同toString()转换成String字符串,
str +=”b” 等同于 str = new StringBuilder(str).append(“b”).toString();

它变慢的关键原因就在于new StringBuilder()和toString(),这里可是创建了10W个StringBuilder对象,而且每次还需要将其转换成String,速度能不慢么?

concat()方法拼接字符串
public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    char buf[] = new char[count + otherLen];
    getChars(0, count, buf, 0);
    str.getChars(0, otherLen, buf, count);
    return new String(0, count + otherLen, buf);
    }

这是concat()的源码,它看上去就是一个数字拷贝形式,我们知道数组的处理速度是非常快的,但是由于该方法最后是这样的:return new String(0, count + otherLen, buf);这同样也创建了10W个字符串对象,这是它变慢的根本原因。

append()方法拼接字符串
public synchronized StringBuffer append(String str) {
    super.append(str);
        return this;
    }

StringBuffer的append()方法是直接使用父类AbstractStringBuilder的append()方法,该方法的源码如下:

public AbstractStringBuilder append(String str) {
    if (str == null) str = "null";
        int len = str.length();
    if (len == 0) return this;
    int newCount = count + len;
    if (newCount > value.length)
        expandCapacity(newCount);
    str.getChars(0, len, value, count);
    count = newCount;
    return this;
}

与concat()方法相似,它也是进行字符数组处理的,加长,然后拷贝,但是请注意它最后是返回并没有返回一个新串,而是返回本身,也就说这这个10W次的循环过程中,它并没有产生新的字符串对象。

通过上面的分析,我们需要在合适的场所选择合适的字符串拼接方式,但是并不一定就要选择append()和concat()方法,原因在于+根据符合我们的编程习惯,只有到了使用append()和concat()方法确实是可以对我们系统的效率起到比较大的帮助,才会考虑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值