关于String的面试题


由于最近公司组织架构做出调整,导致公司一部分人员调动。团队成员又开始寻找新的机会。下面的面试题有的是同事问我的,有的是我面试可能会问的。To be honest,有些面试题还是需要提前准备的…, 进入正题:

强调一点,有时候同一问题,面试官可能问法上有区别,这个需要大家能具备"听到题目关键字,就基本锁定考察点以及延伸的能力"。比如提到String类,就要想要其特性&WHY,内存相关(字符串常量池),源码级别(hashCode实现)… 这样去准备面试,可以做到有的放矢…成为offer收割机。PS:准备面试的 时候多问自己几个WHY…

1.String类是可变吗?如何实现的?

  1. String 是不可变的;
  2. 因为String是关键字final修饰的,故不能被继承,且所有的成员方法都默认为final。
1.1 讲一下String为什么这样设计?

直接上固定套路,不知道可以参考面试技巧
前提知识:字符串常量池

废话一句:这样设计的原因主要是从效率安全性上考虑的。

效率:

  1. 只有将String设计为不可变的,才能实现常量池。常量池的存在减少了heap的空间。因为不必再在为每个字符串对象开辟独立的Heap空间。字符串常量池的存在使JVM提高了性能和减少了内存开销。
  2. Java中的String对象的哈希码被频繁地使用,字符串的不可变性保证了hash码的唯一性。尤其是在HashMap中String作为key。

安全:

  1. No doubt 线程安全:String的不可变性保证同一字符串实例被多个线程共享,所以保障了多线程的安全性。
  2. Java应用以及内部的类加载机制,基本都是用String来作为参数的。如果字符串可变,那么会引起各种严重错误和安全漏洞。

更加详细的内容,推荐:深入理解Java中的String(大坑)

2.是否看过String的源码,其hashCode()是如何实现的?

先看一下String的源码(简化版本),记住其计算公式。

hash = s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1] 此处的n指的是字符串长度。

/**
* hash = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
*/
 public int hashCode(int hash,String s) {
 char[] value =  s.toCharArray();
        if (hash == 0 && value.length > 0) {
            char val[] = value;
            for (int i = 0; i < value.length; i++) {
            	// 注意这里的31
                hash = 31 * hash + val[i];
            }
        }
        return hash;
    }

下面来推导一下(下标从0开始):

i = 0, hash0 = s[0]
i =1, hash1 = hash[0] * 31 + s[1] = 31 * s[0] +s[1]
i = 2,hash2 = hash1 * 31 + s[2] = 31 ( 31 * s[0] +s[1]) + s[2] = 31^2 * s[0] + 31 * s[1] + s[2]
···
i= n, hash = 31^n
s[0] + 31^(n-1)* s[1]+ … + s[n]

2.1 这里为什么乘以31?

存在即合理,平时不多问自己why?面试时就多几分把握…

  1. 31是奇质数(odd prime):按照传统,一般都是乘以质数.
  2. 31可以表示为(2^5-1)。所以它的乘法运算可以被替换成: 31 * i = (i << 5) -i .JVM自动完成优化。
  3. 31作为因子,计算出来的hashCode范围比较”适中“,能兼顾效率。。PS:(因子太小,hash冲突几率大;因子太大,结果容易溢出。)

想深度了解的,不妨参考:科普:String hashCode 方法为什么选择数字31作为乘子

2.2 String为什么要重写hashCode?## 标题

因为String重写了equals()方法 …2333,会不会被打啊?


2.2.1 equals() 和 " == "的区别?

1.默认情况下,两者没有区别。因为 equals()方法Object中内置的方法,其内部实现是借助于”==“
2. equals()方法我们一般自己实现,用于比较对象的内容。而 “ ==”比较的是对象的内存地址。

2.2.2 equals() 和 hashCode()方法有什么联系?

推荐做法:一般重写equals (),也要重写hashCode() ,PS: hashCode()重写不是必须的。若用不到hashCode ,就无需重写。(就怕你分不清啊…)

这里主要阐述一下需要重写hashCode()的理由:

主要是针对于map相关操作,避免碰撞与冲突。用于根据key的哈希码来计算存储位置,以及保证相同对象的key的唯一性。

HashMap中的Node的hashCode()实现

 	public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

Objects的hashCode()

 public static int hashCode(Object o) {
        return o != null ? o.hashCode() : 0;
    }
2.2.3 hashCode()实现的规定

IAW,hashcode相等说明不了问题…

对象相等,hashcode一定相等;反之不成立。
对象不相等,hashcoce一定不相等;反之成立。

3.String类不可变,那如何实现可变的需求呢?

StringBuilderStringBuffer开始表演,(PS: 记不住就:两SB)
两者都是字符序列可变的字符串,用法也基本都相同,常用方法都是 :append(), charAt(),substring(),delete()…and so on.

3.1 StringBuffer

这里主要强调一下:StringBuffer是线程安全的,常用操作方法全部被synchronized修饰。

append()

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
3.2 StringBuilder

append()

 @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
3.3 StringBuffer 和StringBuilder的区别?

1.StringBuffer 是线程安全的,适用于单多线程
2. StringBuilder 是非线程安全的,适用于单线程
3. OK ,速度上,StringBuilder 快于 StringBuffer。(常识)

记不住,就想一下Buffer是缓冲的意思,肯定适用于多任务…即(多线程环境)

3.4 StringBuffer 和StringBuilder可以被继承吗?

WTF?

记住就行,两者都被final修饰,不能被继承,打死也别忘记,关于String的就是不能被继承即可…

3.5 多个字符串使用+拼接,你认为内部是怎么处理的?

Oh,My God…!

略微思考一番,我认为其内部应该是新建一个StringBuilder对象,然后调用一系列的append(),最后toString()。

优秀!!!,就是他啦…## 标题

发布了23 篇原创文章 · 获赞 14 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/wangcheeng/article/details/104692249