阅读 118

不为人知的 String(值得一看)

image.png

这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战

点进来的小伙伴肯定有点疑问,String 不是修饰字符串的吗,有什么可拆解的,接下来我们通过几道面试题一块学习下

话不多说,我们先通过源码了解下大概

分析源码

我们以主流的jdk1.8源码来看,String内部存储结构为char数组。

image.png

其中包含几个重要构造方法

//无参构造方法
public String() {
    this.value = "".value;
}


//string为参数的构造方法
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}


// char[] 为参数构造方法
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}


// StringBuffer 为参数的构造方法
public String(StringBuffer buffer) {
    synchronized(buffer) {
        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}


// StringBuilder 为参数的构造方法
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
复制代码

上述我们简单了解了一下string的源码。比较容易被我们忽略的是以StringBuffer 和 StringBuilder 为参数的构造函数,因为这三种数据类型,我们通常都是单独使用的,所以这个小细节我们需要特别留意一下

接下来我们看下string的方法

  • equals() 比较两个字符串是否相等

  • indexOf():查询字符串首次出现的下标位置

  • contains():查询字符串中是否包含另一个字符串

  • toLowerCase():把字符串全部转换成小写

  • toUpperCase():把字符串全部转换成大写

  • length():查询字符串的长度

  • trim():去掉字符串首尾空格

  • split():把字符串分割并返回字符串数组

...........还有一些,这里不进行赘述

equals() 比较两个字符串是否相等

先看波源码

//这是我们经常使用的方法
public boolean equals(Object anObject) {
    // 对象引用相同直接返回 true
    if (this == anObject) {
        return true;
    }
    // 判断需要对比的值是否为 String 类型,如果不是则直接返回 false
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            // 把两个字符串都转换为 char 数组对比
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 循环比对两个字符串的每一个字符
            while (n-- != 0) {
                // 如果其中有一个字符不相等就 true false,否则继续对比
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
复制代码

String类型重写了Object中的equals()方法,equals()方法需要传递一个Object类型的参数值,在比较时会先通过 instanceof判断是否为String类型,如果不是则会直接返回false,instanceof的使用如下:

Object oString = "123";
Object oInt = 123;
System.out.println(oString instanceof String); // 返回 true
System.out.println(oInt instanceof String); // 返回 false
复制代码

当判断参数为 String 类型之后,会循环对比两个字符串中的每一个字符,当所有字符都相等时返回 true,否则则返回 false。

OK 其他方法 大家可以下面自己进去看下。下面我们通过面试题去研究下

为什么String类型要用final修饰?(高频面试题)

这个问题 我换个问法,那就是用使用final有什么好处,从 String 类的源码我们可以看出 String 是被 final 修饰的不可继承类。

那好处是什么呢,在这里请允许我我引用了java之父的回答

Java 语言之父 James Gosling 的回答是,他会更倾向于使用 final,因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值;如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失。

James Gosling 还说迫使 String 类设计成不可变的另一个原因是安全,当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题,这是迫使 String 类设计成不可变类的一个重要原因。

看着是不是贼抽象 哈哈。毕竟是java之父,说的话不是一般人能听懂的,下面我给搭大家看副图理解下:

image.png

只有字符串是不可变时,我们才能实现字符串常量池,字符串常量池可以为我们缓存字符串,提高程序的运行效率

试想一下如果String是可变的,那当s1的值修改之后,s2的值也跟着改变了,这样就和我们预期的结果不相符了,因此也就没有办法实现字符串常量池的功能了。

总结来说,使用 final 修饰的第一个好处是安全;第二个好处是高效

== 和 equals的区别是什么?(高频面试题)

大家可能张口就来的是:== 对于基本数据类型来说,是用于比较 “值”是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的。

源码如下:

public boolean equals(Object obj) {
    return (this == obj);
}


public boolean equals(Object anObject) {
    // 对象引用相同直接返回 true
    if (this == anObject) {
        return true;
    }
    // 判断需要对比的值是否为 String 类型,如果不是则直接返回 false
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            // 把两个字符串都转换为 char 数组对比
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 循环比对两个字符串的每一个字符
            while (n-- != 0) {
                // 如果其中有一个字符不相等就 true false,否则继续对比
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

复制代码

可以看出,Object 中的 equals() 方法其实就是 ==,而 String 重写了 equals() 方法把它修改成比较两个字符串的值是否相等。

String 和StringBuilder、StringBuffer有什么区别?(高频面试题)

因为String类型是不可变的,所以在字符串拼接的时候如果使用String的话性能会很低,因此我们就需要使用另一个数据类型StringBuffer,它提供了append 和 insert 方法可用于字符串的拼接,它使用 synchronized 来保证线程安全,我用两幅图给大家看下。更直观一些

image.png

image.png

可以直观的看到StringBuffer 使用了 synchronized 来保证线程安全,所以性能不是很高,于是在 JDK 1.5 就有了 StringBuilder,它同样提供了 append 和 insert 的拼接方法,但它没有使用 synchronized 来修饰,因此在性能上要优于 StringBuffer。所以这也是另一个面试题的答案,为什么StringBuilder性能上要优于 StringBuffer。

所以当我们在使用这些的时候。要考虑在什么场景下使用。如果并发比较高,则用StringBuffer,反之builder,这也是任何事物都面临的问题,你得到一种东西,必然会牺牲另外一种东西,就像StringBuffer,安全,但性能低

OK 今天的学习就到这里。我们下期再见

总结

上述只是我们简单的聊了聊string源码,我想说jdk的任何一个东西,哪怕再小,都蕴藏着很多值得我们学习,去思考的东西。我们要善于看源码,学习他们的方式。

弦外之音

感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。也欢迎有问题我们下面评论交流

加油! 我们下期再见!

给大家分享几个我前面写的几篇骚操作

聊聊不一样的策略模式(值得收藏)

copy对象,这个操作有点骚!

文章分类
后端
文章标签