String 的特点是什么?它有哪些重要的方法?

几乎所有的 Java 面试都是以 String 开始的,如果第一个问题没有回答好,则会给面试官留下非常不好的第一印象,而糟糕的第一印象则会直接影响到自己的面试结果,就好像刚破壳的小鹅一样,会把第一眼看到的动物当成自己的母亲,即使它第一眼看到的是一只小狗或小猫,也会默认跟随其后,心理学把这种现象叫做印刻效应。印刻效应不仅存在于低等动物之中,同样也适用于人类,所以对于 String 的知识,我们必须深入的掌握才能为自己赢得更多的筹码。

典型回答

以主流的 JDK 版本 1.8 来说,String 内部实际存储结构为 char 数组,源码如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // 用于存储字符串的值
    private final char value[];
    // 缓存字符串的 hash code
    private int hash; // Default to 0
    // ......其他内容
}

String 源码中包含下面几个重要的方法。

1. 多构造方法

String 字符串有以下 4 个重要的构造方法:

// 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 为参数的构造函数,因为这三种数据类型,我们通常都是单独使用的,所以这个小细节我们需要特别留意一下。

2. 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。

还有一个和 equals() 比较类似的方法 equalsIgnoreCase(),它是用于忽略字符串的大小写之后进行字符串对比。

3. compareTo() 比较两个字符串

compareTo() 方法用于比较两个字符串,返回的结果为 int 类型的值,源码如下:

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    // 获取到两个字符串长度最短的那个 int 值
    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 char1-char2。比如,两个字符串分别存储的是 1 和 2,返回的值是 -1;如果存储的是 1 和 1,则返回的值是 0 ,如果存储的是 2 和 1,则返回的值是 1。

还有一个和 compareTo() 比较类似的方法 compareToIgnoreCase(),用于忽略大小写后比较两个字符串。

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

  • equals() 可以接收一个 Object 类型的参数,而 compareTo() 只能接收一个 String 类型的参数;
  • equals() 返回值为 Boolean,而 compareTo() 的返回值则为 int。

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

4. 其他重要方法

  • indexOf():查询字符串首次出现的下标位置
  • lastIndexOf():查询字符串最后出现的下标位置
  • contains():查询字符串中是否包含另一个字符串
  • toLowerCase():把字符串全部转换成小写
  • toUpperCase():把字符串全部转换成大写
  • length():查询字符串的长度
  • trim():去掉字符串首尾空格
  • replace():替换字符串中的某些字符
  • split():把字符串分割并返回字符串数组
  • join():把字符串数组转为字符串

问题分析

String 源码属于所有源码中最基础、最简单的一个,对 String 源码的理解也反应了你的 Java 基础功底。

String 问题如果再延伸一下,会问到一些更多的知识细节,这也是大厂一贯使用的面试策略,从一个知识点入手然后扩充更多的知识细节,对于 String 也不例外,通常还会关联的询问以下问题:

  • 为什么 String 类型要用 final 修饰?
  • == 和 equals 的区别是什么?
  • String 和 StringBuilder、StringBuffer 有什么区别?
  • String 的 intern() 方法有什么含义?
  • String 类型在 JVM(Java 虚拟机)中是如何存储的?编译器对 String 做了哪些优化?

接下来我们一起来看这些问题的答案。

知识扩展

1. == 和 equals 的区别

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

查看源码我们可以知道 Object 中也有 equals()  方法,源码如下:

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

可以看出,Object 中的 equals() 方法其实就是 ==,而 String 重写了 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;
}

2. final 修饰的好处

从 String 类的源码我们可以看出 String 是被 final 修饰的不可继承类,源码如下:

public final class String 
	implements java.io.Serializable, Comparable<String>, CharSequence { //...... }

那这样设计有什么好处呢?

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

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

总结来说,使用 final 修饰的第一个好处是安全;第二个好处是高效,以 JVM 中的字符串常量池来举例,如下两个变量:

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

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

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

3. String 和 StringBuilder、StringBuffer 的区别

因为 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 append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

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

4. String 和 JVM

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

它们在 JVM 存储的位置,如下图所示:

小贴士:JDK 1.7 之后把永生代换成的元空间,把字符串常量池从方法区移到了 Java 堆上。

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

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

虽然 s1 拼接了多个字符串,但对比的结果却是 true,我们使用反编译工具,看到的结果如下:

Compiled from "StringExample.java"
public class com.lagou.interview.StringExample {
  public com.lagou.interview.StringExample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 3: 0

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String Java
       2: astore_1
       3: ldc           #2                  // String Java
       5: astore_2
       6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: aload_1
      10: aload_2
      11: if_acmpne     18
      14: iconst_1
      15: goto          19
      18: iconst_0
      19: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
      22: return
    LineNumberTable:
      line 5: 0
      line 6: 3
      line 7: 6
      line 8: 22
}

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

总结

我们从 String 的源码入手,重点讲了 String 的构造方法、equals() 方法和 compareTo() 方法,其中 equals() 重写了 Object 的 equals() 方法,把引用对比改成了字符串值对比,也介绍了 final 修饰 String 的好处,可以提高效率和增强安全性,同时我们还介绍了 String 和 JVM 的一些执行细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/270931.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

走进数字金融峰会,为金融科技数字化赋能

12月20—21日&#xff0c;FSIDigital数字金融峰会在上海圆满召开。本次峰会包含InsurDigital数字保险峰会和B&SDigital数字银行与证券峰会2场平行峰会&#xff1b;吸引了近600位来自保险、银行、证券以及金融科技等行业的领导者和专家齐聚一堂&#xff0c;共同探讨金融业数…

数据库01_增删改查

1、什么是数据&#xff1f;什么是数据库&#xff1f; 数据&#xff1a;描述事物的符号记录称为数据。数据是数据库中存储的基本对象。数据库&#xff1a;存放数据的仓库&#xff0c;数据库中可以保存文本型数据、二进制数据、多媒体数据等数据 2、数据库的发展 第一阶段&…

docker-compose部署kafka

docker-compose.yml配置 version: "3" services:kafka:image: bitnami/kafka:latestports:- 7050:7050environment:- KAFKA_ENABLE_KRAFTyes- KAFKA_CFG_PROCESS_ROLESbroker,controller- KAFKA_CFG_CONTROLLER_LISTENER_NAMESCONTROLLER- KAFKA_CFG_LISTENERSPLAIN…

面向对象设计与分析40讲(15)简单工厂方法模式

文章目录 定义示例优缺点定义 简单工厂模式是一种创建型模式,用于根据客户端的需求创建对象实例,所谓的需求反映到编程语言里就是传入的参数。 简单工厂模式包括三个主要部分: 工厂类(Simple Factory):这是整个模式的核心。它负责根据客户端的请求来创建并返回相应的对…

【教程】从gitee或者github,下载单个文件或文件夹命令

1.打开git 2.初始化 git init 3.设置允许下载子目录 &#xff08;不需要修改任何&#xff0c;只要原样复制&#xff0c;需要按照个人状况修改的话我会标注&#xff09; git config core.sparseCheckout true 4. 选择要下载的单个文件夹的路径 这里单引号内部需要修改&…

Shell 脚本基础

Shell脚本 脚本以#!/bin/bash开头 执行方式 直接使用文件名执行&#xff1a;文件需要执行权限 以bash xxx.sh来执行, 本质上是bash解析器去执行, 文件作为一个输入, 因此可以不需要执行权限 变量 系统变量 自定义变量 定义变量 # 定义一个变量username, 注意不能有多余…

一体机定制_工控触控一体机安卓主板方案

工控一体机是一种集成化的硬件方案&#xff0c;采用了联发科MT8768八核芯片和12nm制程工艺。该芯片拥有2.0GHz的主频和IMG PowerVR GE8320图形处理GPU&#xff0c;具备强大的视频处理能力&#xff0c;并且兼容大部分的视频格式和解码能力。工控一体机搭载了Android 9.0操作系统…

学Java的第二天

一、常量 1.值不可以变化的量。 2. 分类&#xff1a; 字符串常量 用双引号括起来的多个字符&#xff0c;可以包含 0、1 或多个&#xff0c;例如 "a" 、 "abc" 、 " 中国 " 整数常量&#xff0c;例如&#xff1a; -10 、 0 、 88 小数常量&…

浅谈Java反射中的getFields()方法和getDeclaredFields ()方法

目录 1. 概念2. getFields()方法2. getDeclaredFields()方法4. 总结 1. 概念 反射是Java中一种强大的机制&#xff0c;允许在运行时获取、检查和操作类、方法、字段等信息&#xff0c;而不需要在编译时知道这些信息。 其中字段&#xff08;Field&#xff09;在Java中是类中用…

【精选】vulnhub CTF5 NanoCMS漏洞 (青铜门笔记)

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏…

微信小程序使用canvas制作海报并保存到本地相册(超级详细)

案例图 分析案例图都有哪些元素 1.渐变背景 2.圆形头像 3.文字 4.文字超出换行 5.图片居中 6.文字居中 7.单位适配 8.弹窗保存图片。因为一个个绘制图形太麻烦所以这里都采用了方法封装。 canvas api介绍 最后有全部代码&#xff0c;复制即用。 data数据 data() {return {myO…

并发程序设计--D1进程的创建和回收

进程和程序内容区别 进程包含的内容&#xff1a; BSS段&#xff1a;存放程序中未初始化的全局变量 数据段&#xff1a;已初始化的全局变量 代码段&#xff1a;程序执行代码 堆&#xff08;heap&#xff09;&#xff1a;malloc等函数分配内存 栈(stack)&#xff1a;局部变量…

升压斩波电路的simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 一、升压斩波电路概述 二、升压斩波电路的基本工作原理 5.完整工程文件 1.课题概述 升压斩波电路的simulink建模与仿真&#xff0c;通过双闭环结构实现电池&#xff0c;点击的控制。 2.系统仿真结果 …

复试情报准备

英语自我介绍&#xff0c;介绍完老师会根据你的回答用英语问你问题&#xff0c;比如介绍一下你的本科学校&#xff0c;或者家乡什么的。计网过一遍&#xff0c;会问两道题。接下来是重点&#xff0c;我当时是根据我成绩单&#xff0c;问了我本科学过的科目&#xff0c;比如pyth…

PPT中加入页码

PPT中加入页码 文章目录 简单版本样式更改 简单版本 PPT中插入页码&#xff0c;基础的就是在“插入”选项卡中单机“幻灯片编号”即可 样式更改 然而&#xff0c;就像我们做幻灯片不满足于白底黑字一样&#xff0c;页码也总不能是默认的样式。 比如&#xff0c;在页码下面…

Spring Cloud Gateway 网关整合 Knife4j 4.3 实现微服务接口文档聚合

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

QT小技巧 - 使用QMovie进行gif切帧

简介 使用QMovie 将 gif 进行切帧&#xff0c; magick 进行合并代码 QString gifPath "E:\\workspace\\qt\\gif2imgs\\203526qre64haq3ccoobqi.gif"; // 你的图片QMovie movie(gifPath); movie.setCacheMode(QMovie::CacheNone);qDebug() << movie.frameCou…

31. Ajax

简介 AJAX 是 Asynchronous JavaScript And XML 的简称。直译为&#xff0c;异步的JS和XML。AJAX的实际意义是&#xff0c;不发生页面跳转、异步载入内容并改写页面内容的技术。AJAX也可以简单的理解为通过JS向服务器发送请求。 AJAX这门技术很早就被发明&#xff0c;但是直到…

基于AR+地图导航的景区智慧导览设计

随着科技的飞速发展&#xff0c;智慧旅游已经成为现代旅游业的一个重要趋势。在这个背景下&#xff0c;景区智慧导览作为智慧旅游的核心组成部分&#xff0c;正逐渐受到越来越多游客的青睐。本文将深入探讨地图导航软件在景区智慧导览中的应用&#xff0c;并分析其为游客和景区…

C++中的存储类及其实例

文章目录 0. 语法1. 自动存储类自动存储类对象的属性自动存储类的例子 2. 外部存储类extern存储类对象的属性extern存储类的例子 3. 静态存储类静态存储类的属性静态存储类的例子 4. 寄存器存储类寄存器存储类对象的属性寄存器存储类例子 5. 可变&#xff08;mutable&#xff0…