Java - String源码解析及常见面试问题

简介: Java - String源码解析及常见面试问题

2020070719363976.png


Pre

Java Version : 主流版本JDK 8


Q1: String 是如何实现的?



2020070720173633.png


看到了吧 , 底层存储是 char 数组

public final class String  implements java.io.Serializable, Comparable<String>, CharSequence {
    // the value is used for character storage    存储字符串的值
    private final char value[];
    // Cache the hash code for the string  缓存字符串的 hash code
    private int hash; // Default to 0
    // ...... 
}


Q2: String 有哪些重要的方法?

构造函数

20200707202309855.png


挑几个比较重要的

// 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());
}


这里需要提一下的是: 以 StringBuffer 和 StringBuilder 为参数的构造函数容易被忽略,因为String 、 StringBuffer、StringBuilder 这三种数据类型, 通常都是单独使用的哇。 知道就行,反正平常也不这么写


还有其他构造函数 ,大家可以自行看一下


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) { // 循环比对两个字符串的每一个字符
                    if (v1[i] != v2[i])  // 如果其中有一个字符不相等就直接返回false,否则继续对比,直接到结束
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }


equals() 是String 类型重写的 Object 中的 方法,Object#equals() 方法需要传递一个 Object 类型的参数值所以才有了上面的instanceof 类型判断 。 当判断参数为 String 类型之后,会循环对比两个字符串中的每一个字符,当所有字符都相等时返回 true,否则则返回 false。


【Object#equals()】

 public boolean equals(Object obj) {
        return (this == obj);  // 仅判断的对象引用,即比较的是对象在内存中的地址
    }


【instanceof 用法】

Object a= "123";
Object b= 123;
System.out.println(a instanceof String); //  true
System.out.println(b instanceof String); //  false


另外还有一个 equalsIgnoreCase(), 忽略字符串的大小写之后进行字符串对比。


compareTo()

比较两个字符串

   public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2); // 取两个字符串中长度最短的那个字符串的长度  
        char v1[] = value;
        char v2[] = anotherString.value;
        int k = 0;
        while (k < lim) {  // 对比每一个字符
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {// 有字符不相等时返回差值 
                return c1 - c2;  
            }
            k++;
        }
        return len1 - len2;
    }

从源码总可以看到compareTo() 方法会循环对比所有的字符,当两个字符串中有任意一个字符不相同时,则 return c1 - c2。


举个例子


“53334433”.compareTo(“3”) ----> 2 【取最小长度,第一个字符 5 和 3 比,转成char 比较, 不相等 返回 5 - 3 = 2】


再来个例子: 两个字符串分别存储的是 1 和 2,返回的值是 -1;如果存储的是 1 和 1,则返回的值是 0 ,如果存储的是 2 和 1,则返回的值是 1。


还有个compareToIgnoreCase 忽略大小写后比较两个字符串。


【equals() vs compareTo() 】


可以看出 compareTo() 方法和 equals() 方法都是用于比较两个字符串的,但它们有两点不同:


equals() 可以接收一个 Object 类型的参数,而 compareTo() 只能接收一个 String 类型的参数

equals() 返回值为 Boolean,而 compareTo() 的返回值则为 int


它们都可以用于两个字符串的比较,当 equals() 方法返回 true 时,或者是 compareTo() 方法返回 0 时,则表示两个字符串完全相同


其他重要方法


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

lastIndexOf():查询字符串最后出现的下标位置

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

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

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

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

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

replace():替换字符串中的某些字符

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

join():把字符串数组转为字符串


Q3: 为什么 String 类型要用 final 修饰


从源码中可以知道String是final修饰的?

为啥子嘞?


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


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


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


我们以JVM中的常量池来举个例子

String s1 = "java";
String s2 = "java";


只有字符串是不可变时,我们才能实现字符串常量池。

字符串常量池可以为我们缓存字符串,这样的话不用每次都去开辟一块内存地址存放,自然就提高了运行效率。

20200707232917645.png




如果String是可变的,那字符串常量池就歇菜了。。。。。


Q4: == 和 equals 的区别是什么

【==】


  • 对于基本数据类型来说, 比较 “值”是否相等的
  • 对于引用类型来说, 比较引用地址是否相同的

Object#equals() 其实就是 ==

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


String#equal这是重写了父类Object的equals方法,把它修改成了比较两个字符串的值是否相等,分析如上。


Q5: String 和 StringBuilder、StringBuffer 有什么区别


简单来说:

  • String 不可变 ,正是因为不可变,所以字符串在拼接时,效率低,所以才有了下面两个
  • StringBuffer 线程安全
  • StringBuilder 线程不安全


String 类型是不可变的,所以在字符串拼接的时候如果使用 String 的话性能会很低。

因此我们就需要使用另一个数据类型 StringBuffer,它提供了 append 和 insert 方法可用于字符串的拼接,它使用 synchronized 来保证线程安全

@Override
public synchronized StringBuffer append(Object obj) {
    toStringCache = null;
    super.append(String.valueOf(obj));
    return this;
}
@Override
public synchronized StringBuffer insert(int offset, String str) {
    toStringCache = null;
    super.insert(offset, str);
    return this;
}  

因为它使用了 synchronized 来保证线程安全,所以性能不是很高。


于是在 JDK 1.5 就有了 StringBuilder,它同样提供了 append 和 insert 的拼接方法,但它没有使用 synchronized 来修饰,因此在性能上要优于 StringBuffer,所以在非并发操作的环境下可使用 StringBuilder 来进行字符串拼接。

      @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    @Override
    public StringBuilder insert(int offset, String str) {
        super.insert(offset, str);
        return this;
    }

当然了,append 和 insert的方法入参有很多,这里仅仅列举出了一个,主要是让你看下 synchronized实现上的区别。


Q6: String 类型在 JVM中是如何存储的?编译器对 String 做了哪些优化


String 常见的创建方式有两种

  • new String()
  • 直接赋值


直接赋值的方式会先去字符串常量池中查找是否已经有此值,如果有则把引用地址直接指向此值,否则会先在常量池中创建,然后再把引用指向此值;


new String() 一定会先在堆上创建一个字符串对象,然后再去常量池中查询此字符串的值是否已经存在,如果不存在会先在常量池中创建此字符串,然后把引用的值指向此字符串


举个例子

String s1 = new String("Java");
String s2 = s1.intern();
String s3 = "Java";
System.out.println(s1 == s2); //  ------> false
System.out.println(s2 == s3); //  ------>  true

20200707234207746.png

除此之外编译器还会对 String 字符串做一些优化,例如以下代码

String s1 = "Ja" + "va";
String s2 = "Java";
System.out.println(s1 == s2);

输出 true

javap -c 反汇编看一下


20200707235100600.png


从编译代码 #2 可以看出,代码 “Ja”+“va” 被直接编译成了 “Java” ,因此 s1==s2 的结果才是 true,这就是编译器对字符串优化的结果

相关文章
|
3月前
|
机器学习/深度学习 人工智能 JSON
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
Resume Matcher 是一款开源AI简历优化工具,通过解析简历和职位描述,提取关键词并计算文本相似性,帮助求职者优化简历内容,提升通过自动化筛选系统(ATS)的概率,增加面试机会。
268 18
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
|
3月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
356 29
|
3月前
|
缓存 安全 Java
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
99 11
|
3月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
108 4
|
3月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
3月前
|
Java
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、&quot;+&quot;操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
|
3月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
3月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
3月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
4月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
436 0

推荐镜像

更多
  • DNS