面试题——————不可变类String类

最近看到二哥的一篇文章,对于如何做好一件事情深有感触,自己阅读完也总结了下面这句话分别是:不断学习,不断思考,不断实践。基础知识有了扎实了,下面要做的就是深入。人往高处走嘛!

String类相信大家并不陌生可能天天都在使用(只要你写代码)、今天也看了一些博主的讲解对比自己与他们之间有什么理解出入和差距,所以整理了一下。正好也为面试作准备!

进入正题

01做个题热热身
 		String str = new String("abc");
        String str2 = "abc";

必备知识点:

聊一聊两种创建字符串对象的方式

  • 构造器创建对象
  • 双引号创建对象

构造器和双引号创建对象有啥区别?
构造器创建对象:new String(“abc”)会创建两个对象,一个在堆内存,一个在常量池,堆内存对象是常量池对象的一个拷贝副本。

注意事项:
如果常量池中存在"abc"对象就只创建一个对象(堆内存中)
如果常量池不存在"abc"对象就创建两个对象

双引号创建对象:常量池(因为new String(“abc"创建了两个对象,常量池中已有"abc”,不再创建对象)所以将"abc"的内存地址赋给str2,str2根据内存地址,指向了常量池中的"abc")

                         ------------- 实战演练-------------

牛刀小试

判断str1与str2是否相等

public class StringTest {
    public static void main(String[] args) {
 		String str1 = "abc";
        String str2 = "abc";
        System.out.println(str1==str2);//true
        System.out.println(str1.equals(str2));//true
    }
}        

顺便复习一下==equals方法的区别?
对于 == 来说
如果比较的是基本数据变量,直接比较"值"。
如果比较的是引用类型变量,比较的是内存地址。
对于 equals 来说
如果重写了equals方法,比较的是引用变量指向对象的内容。
如果没有重写equal方法,比较的是引用变量指向对象的内存地址。
诸如String类、Date对equals方法进行了重写。

分析:
第一条打印语句 字符串属于引用类型并且str1、str2指向了同一个内存地址,所以==比较结果相等为true
第二条打印语句 存在equals方法,所以我们先考虑String类是否重写该方法。从JDK1.8文档中得知
将此字符串与指定对象进行比较。 其结果是true当且仅当该参数不是null并且是String对象,表示相同的字符序列作为该对象。
也就是说str1与str2是相同的字符序列对象,所以equals比较结果为true

图解:
在这里插入图片描述

  1. 运行前编译 当前类编译为StringTest.class (字节码文件),文件加载到内存的方法区
  2. main方法压入栈内存
  3. 常量池创建"abc"对象,产生内存地址,赋值给main方法下的str1,str1根据内存地址指向内存地址
  4. 程序运行到str2 发现常量池已经存在相同的对象,就不在创建(目的为了节省内存开销),str2也指向了内存地址
  5. str1、str2都指向同一地址,所以==结果为true
  6. 由于string类重写了equals方法,str1 、str2内容相等(相同的字符序列) ,所以equals结果为true

初露锋芒
判断str1与str2是否相等

public class StringTest {
    public static void main(String[] args) {
        String str = new String("abc");
        String str2 = "abc";
        System.out.println(str==str2);//false
        System.out.println(str1.equals(str2));//true
    }
}

分析:
第一条打印语句 str2引用指向常量池内存地址,str引用指向堆内存地址,所以==结果为false
第二条打印语句 string类重写了equal是方法str1与str2的内容相同(相同的字符序列),所以equals结果为true

图解:
在这里插入图片描述
内存过程大致如下:

1)运行前编译,生成StringTest.class文件(字节码文件)加载到内存的方法区
2)main方法压入栈内存
3)new String(“abc”)在常量池创建对象,产生内存地址,赋值给main方法的str,str根据内存地址,指向堆内存,同时堆内存拷贝一份副本
4)双引号创建对象时发现常量池已经存在,不会再去创建,str2指向了常量池的内存地址。
5) str指向堆内存 str2指向常量池地址 所以==结果为false
6) str、str2内容相同(相同字符序列) 所以equals结果为true

为什么堆内存拷贝一份副本?
JDK API 1.6中这样解释构造方法——String(String original)的
初始化新创建的String对象,使其表示与参数相同的字符序列; 换句话说,新创建的字符串是参数字符串的副本。

兵不血刃
判断str1与str2是否相等

public class StringTest {
    public static void main(String[] args) {
        String str1 = "ab";
        String str2 = "abc";
        String str3 = st1 + "c";
        System.out.println(str2 == str3);//false
        System.out.println(str2.equals(str3));//true
    }
}

分析:
第一条打印语句 ==结果为false,无法上面的知识解决那就要借助API和反编译工具去理解
第二条打印语句 equals结果突然 不难理解因为都是相同的字符序列结果为true

疑惑的原因是str3对象是通过连接字符串(+)创建的 对于(连接字符串)+JDK1.8帮助文档也有介绍
Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串。 字符串连接是通过StringBuilder (或StringBuffer )类及其append方法实现的。 字符串转换是通过方法来实现toString ,由下式定义Object和继承由在Java中的所有类。

如果想要看到+是如何转化为StringBuiler操作的可以通过反编译工具jad来实现(文章最后会分享)

public class StringTest 
{

    public StringTest ()
    {
    }

    public static void main(String args[])
    {
        String s = "ab";
        String s1 = "abc";
        String s2 = (new StringBuilder()).append(s).append("c").toString();
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
    }
}

通过反编译工具可以发现原先 String str3 = st1 + "c"实际操作是
String s2 = (new StringBuilder()).append(s).append(“c”).toString()

这样是不是清晰了很多。
因为new StringBuilder()通过append方法创建出来的对象都存储在堆内存中,所以会产生一个内存地址0x0011,但这并不是String对象所以需要通过object类中的toString方法转化为String 对象 ,String对象也在堆内存中产生了一个内存地址0x002然后赋值给s2、s2指向了堆内存地址0x0022

图解:
在这里插入图片描述
内存大致过程如下:

  1. 常量池创建了"ab"对象,产生内存地址赋值给str1,str1指向了"ab";
  2. 常量池创建了"abc"对此昂,产生了内存地址赋值给了str2,str2指向了"abc";
  3. 连接字符串会被java通过StringBuilder方法的append方法创建一个新的对象得到"abc",
    这时候内存地址0x0011表示的是一个StringBuilder对象,不是String对象。
  4. 需要通过object的toString方法转化为String对象
  5. String对象将内存地址0x0022赋值给str3,str3指向"abc"

锋不可当
判断str1与str2是否相等

public class StringTest {
    public static void main(String[] args) {
        String str1 = "a" + "b" + "c";
        String str2 = "abc";
        System.out.println(str1 == str2);//true
        System.out.println(str1.equals(str2));//true
    }
}        

分析:
第一条打印语句 通过反编译工具得知原先str1=“a”+“b”+“c”;反编译后 String s = “abc”;
可以得出结论:

1.连接字符串左右两边如果是字符串,直接在常量池创建
2.连接字符串左右两边如如果任意一边存在引用变量,就会通过StringBuilder或者(StringBuffer)的append方法创建对象(new出来的对象都存储在堆内存中)
所以str1、str2指向同一地址 ==结果为true

第二条打印语句 str1、str2都是相同的字符序列所以内容相同结果为true

public class StringTest
{

    public StringTest()
    {
    }

    public static void main(String args[])
    {
        String s = "abc";
        String s1 = "abc";
        System.out.println(s == s1);
        System.out.println(s.equals(s1));
    }
}

干了下面这些题

		String s1 = new String("zs");
        String s2 = new String("zs");
        System.out.println(s1==s2);//false
        String s3="zs";
        String s4="zs";
        System.out.println(s3==s4);//true
        System.out.println(s3==s1);//false
        String s5="zszs";
        String s6=s3+s4;
        System.out.println(s5==s6);//false
        final String s7="zs";
        final String s8="zs";
        String s9=s7+s8;
        System.out.println(s5==s9);//true
        final String s10=s3+s4;
        System.out.println(s5==s10);//false

前面的不再过多的赘述了,重点说一下final修改的成员变量(只能在构造方法中初始化值,随后修改不了)
String s9=s7+s8;这行代码如果按照上面的理解应该是在堆内存中创建对象,但是final修饰的成员变量被拘留在了常量池中,所以连接字符串返回的对象存储在常量池中,又因为"zszs"被创建过了,所以s5和s9引用指向同一内存地址==结果为true
final String s10=s3+s4;这行代码与上面看起来相同,虽然final修饰了但是实际上通过反编译发现连接字符串依然有效了,这是因为final是在创建对象之后拘留了,所以这时候堆内存已经创建出来了对象。
所以s5指向的是常量池地址,s10指向的是堆内存地址 ==结果为false。
如果不明白可以通过jad反编译或者画图去理解

public class Test
{

    public Test()
    {
    }

    public static void main(String args[])
    {
        String s = new String("zs");
        String s1 = new String("zs");
        System.out.println(s == s1);
        String s2 = "zs";
        String s3 = "zs";
        System.out.println(s2 == s3);
        System.out.println(s2 == s);
        String s4 = "zszs";
        String s5 = (new StringBuilder()).append(s2).append(s3).toString();
        System.out.println(s4 == s5);
        String s6 = "zszs";
        System.out.println(s4 == s6);
        String s7 = (new StringBuilder()).append(s2).append(s3).toString();
        System.out.println(s4 == s7);
    }
}
02String类中一些方法

JAVA提供了String类来创建和操作字符串。在源码中可以看到,String类内部的实现也是一个字节数组,这个数组是final类型的。因此String是不可变的对象,每次对String类进行改变的时候都会生成一个新的String对象,然后将指针指向新的String对象。

 /** The value is used for character storage. */
    private final char value[];

String类中方法也证实了操作字符串不会改变自身,而是创建新的对象,诸如concat、replaceAll、replace等方法。

举个例子:

        String s = "abcd";
        s = s.concat("ef");
        System.out.println(s);

对字符串"abcd"操作将指定的字符串连接到该字符串的末尾
在源码中返回的是一个new对象,所以创建的字符串对象"abcdef"存储在堆内存中。

public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
03为什么String是不可变的类?

前面做了很多铺垫,最后总结一下String是不可变类的原因。

  1. 常量池的需要
    字符串常量池是 Java 堆内存中一个特殊的存储区域,当创建字符串对象时候,如果常量池存在就不会二次创建,直接引用对象,目的是为了节省内存的开销,提高运行效率。
  2. hashcode需要
    因为字符串是不可变的,所以在它创建的时候,其 hashCode 就被缓存了,因此非常适合作为哈希值(比如说作为 HashMap 的键),多次调用只返回同一个值,来提高效率。
  3. 线程安全
    由于不可变对象不能被更改,所以它们可以在多个线程之间自由共享。这消除了同步的需求
04分享反编译工具

反编译工具jad
链接:https://pan.baidu.com/s/1F8oUkDTTBqkl1UkomESgyA
提取码:7ns3

具体安装操作如下:
http://www.itwanger.com/java/2019/10/22/javac-jad.html

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 好的,下面是对大数据面试题——spark面试题(一)的回答: 1. 什么是Spark?它与Hadoop有什么区别? Spark是一个快速、通用、可扩展的大数据处理引擎,它可以在内存中进行数据处理,因此比Hadoop更快。与Hadoop相比,Spark的优点在于它可以在内存中进行数据处理,因此速度更快,而且它支持更多的数据处理方式,例如流处理、图形处理等。 2. Spark的核心组件有哪些? Spark的核心组件包括Spark Core、Spark SQL、Spark Streaming、MLlib和GraphX。 3. 什么是RDD?它有哪些特点? RDD是Spark中的一个基本概念,它代表一个不可变的分布式数据集合。RDD具有以下特点: - 可以在内存中进行计算,因此速度快; - 支持多种操作,例如map、reduce、filter等; - 可以进行持久化,以便在后续计算中重复使用。 4. Spark中的map和flatMap有什么区别? map和flatMap都是RDD中的转换操作,它们的区别在于: - map操作对每个元素进行转换,返回一个新的元素; - flatMap操作对每个元素进行转换,返回一个包含多个元素的序列。 5. 什么是Spark的shuffle操作? Spark的shuffle操作是指将数据重新分区的操作,它通常发生在reduce操作之前。Shuffle操作会将数据从多个节点上收集到一个节点上,然后重新分区,以便进行后续的计算。 6. Spark中的cache和persist有什么区别? cache和persist都是将RDD持久化到内存中,以便在后续计算中重复使用。它们的区别在于: - cache操作默认将数据持久化到内存中,而persist操作可以指定将数据持久化到内存、磁盘或者其他存储介质中; - cache操作是persist操作的一种简化形式,它默认将数据持久化到内存中,并且只能持久化到内存中。 7. Spark中的reduceByKey和groupByKey有什么区别? reduceByKey和groupByKey都是对键值对RDD进行操作的函数,它们的区别在于: - reduceByKey操作会在每个分区内先进行本地聚合,然后再进行全局聚合,因此效率更高; - groupByKey操作会将所有的键值对都进行网络传输,然后在一个节点上进行聚合,因此效率较低。 8. Spark中的broadcast变量有什么作用? broadcast变量是一种只读的变量,它可以在所有节点上共享,以便在计算过程中使用。使用broadcast变量可以避免在网络上传输大量的数据,从而提高计算效率。 9. 什么是Spark的checkpoint操作? Spark的checkpoint操作是将RDD持久化到磁盘上,以便在后续计算中重复使用。与cache和persist不同的是,checkpoint操作会将数据持久化到磁盘上,以便在内存不足时可以从磁盘上恢复数据。 10. Spark中的Task是什么? Task是Spark中的一个基本概念,它代表一个可以在一个节点上执行的计算任务。Spark将一个RDD分成多个分区,每个分区对应一个Task,这些Task可以并行执行,以提高计算效率。 ### 回答2: 今天我们来讨论一下关于Spark大数据面试的一些常见问题。Spark是一种基于Hadoop的开源计算系统,它能够快速处理大规模数据,并且支持多种编程语言,包括Java、Scala和Python等。以下是一些Spark面试题及其答案: 1. Spark有哪几种部署模式? Spark有三种部署模式,分别是本地模式、集群模式和分布式模式。本地模式指的是在本地运行Spark应用程序,不需要连接到外部计算机。集群模式指的是单个Spark集群环境,它由一组Spark节点组成,可以在数据中心或云中运行。分布式模式指的是使用多个Spark集群并行处理大规模数据。 2. Spark和Hadoop的区别是什么? Spark和Hadoop都是处理大规模数据的工具,但它们有一些区别。首先,Spark处理数据速度快,因为它将数据存储在内存中,而Hadoop则将数据存储在磁盘中。其次,Spark支持更多的编程语言,包括Java、Scala和Python等,而Hadoop只支持Java。此外,Spark具有更好的机器学习和图形处理功能,可以更好地支持大规模数据分析。 3. Spark的RDD是什么? RDD是Spark中重要的概念,全称为Resilient Distributed Dataset。它是一个不可变的分布式数据集合,可以分区存储在不同节点上,并且每个分区都可以在并行处理中进行处理。RDD支持两种操作,即转化操作和行动操作。转化操作将一个RDD转换为另一个RDD,而行动操作返回一个结果或将结果输出至外部系统。 4. Spark的优化技术有哪些? Spark优化技术包括数据本地化、共享变量、宽依赖和窄依赖、缓存和持久化,以及数据分区等技术。数据本地化将数据存储在尽可能接近计算节点的位置,以减少网络传输的开销。共享变量将常用的变量通过广播或累加器的方式在节点中共享,从而减少网络传输量。宽依赖和窄依赖指的是在转化操作中RDD之间的依赖关系,窄依赖表示每个父分区最多与一个子分区有关联,而宽依赖则表示多个子分区可能与多个父分区关联。缓存和持久化技术可将RDD保存在内存中,从而加速访问速度。数据分区可以将数据划分为较小的块进行并行处理。 5. Spark Streaming是什么? Spark Streaming是Spark的一个扩展模块,它支持实时数据流处理。Spark Streaming可以将实时数据流以微批次方式处理,每个批次的数据处理平均耗时只有几秒钟。Spark Streaming可以将数据存储在内存或磁盘中,同时支持多种数据源和数据输出方式。 以上是关于Spark大数据面试题的一些回答,希望能够对大家有所帮助。如果你想深入学习Spark和大数据处理技术,可以考虑参加相关的培训课程或在线课程。 ### 回答3: Spark是一个分布式计算框架,它可以使大规模数据处理更加高效和便捷。因此,在企业招聘大数据领域的人才时,对Spark的技术能力要求越来越高。以下是Spark面试题的回答: 1. Spark有哪些组件? Spark框架由三个核心组件组成:Spark Core、Spark SQL和Spark Streaming。此外,还有Spark MLlib、Spark GraphX、Spark R等个别不同的子组件。 2. 什么是RDD?与Dataframe有什么区别? RDD(弹性分布式数据集)是Spark的核心数据抽象,是不可变的分布式对象集合。RDD可以从文件中读取数据、从内存中读取数据、并行修改数据等。而Dataframe和RDD似,但是Dataframe更加强大,因为它是带有结构化的RDD。Dataframe在处理大规模结构化数据时非常有效和便捷。 3. Spark如何处理缺失数据? Spark提供了两种处理缺失数据的方法:第一种是使用DataFrame API中的na函数,可以删除或替换缺失值;第二种是使用MLlib中的Imputer,可以将缺失值替换为均值或中位数。 4. 什么是Spark的任务(task)? 一个任务是Spark作业中的最小执行单位。Spark集群上的作业被划分为多个任务,这些任务可以并行执行。 5. Spark的shuffle操作是什么?它为什么是昂贵的? Spark的shuffle操作是将一组数据重新分配到不同计算节点上的操作。Shuffle操作可能会导致大量数据的磁盘写入、网络传输和数据重组,这些都是非常昂贵的操作。因此,它在Spark集群中是一个相当昂贵的操作。 6. Spark中的Partition有什么作用? Partition是Spark中的数据划分单位。它可以将数据分成多个块并对每个块进行处理。Partition 可以提高 Spark 的并行度和运行效率,因为它可以将大规模数据分成多个小块,并在集群的多个计算节点上并行处理数据。 总而言之,Spark是大数据领域中使用最广泛的计算引擎之一,其技术理念和应用场景非常广泛。对于求职者而言,掌握 Spark 的基本概念和技术特点,提高对 Spark 的理解和应用能力,将有助于更好地处理和分析大规模数据集。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值