文章目录
由于最近公司组织架构做出调整,导致公司一部分人员调动。团队成员又开始寻找新的机会。下面的面试题有的是同事问我的,有的是我面试可能会问的。To be honest,有些面试题还是需要提前准备的…, 进入正题:
强调一点,有时候同一问题,面试官可能问法上有区别,这个需要大家能具备"听到题目关键字,就基本锁定考察点以及延伸的能力"。比如提到String类,就要想要其特性&WHY,内存相关(字符串常量池),源码级别(hashCode实现)… 这样去准备面试,可以做到有的放矢…成为offer收割机。PS:准备面试的 时候多问自己几个WHY…
1.String类是可变吗?如何实现的?
- String 是不可变的;
- 因为String是关键字final修饰的,故不能被继承,且所有的成员方法都默认为final。
1.1 讲一下String为什么这样设计?
直接上固定套路,不知道可以参考面试技巧。
前提知识:字符串常量池
废话一句:这样设计的原因主要是从效率和安全性上考虑的。
效率:
- 只有将String设计为不可变的,才能实现常量池。常量池的存在减少了heap的空间。因为不必再在为每个字符串对象开辟独立的Heap空间。字符串常量池的存在使JVM提高了性能和减少了内存开销。
- Java中的String对象的哈希码被频繁地使用,字符串的不可变性保证了hash码的唯一性。尤其是在HashMap中String作为key。
安全:
- No doubt 线程安全:String的不可变性保证同一字符串实例被多个线程共享,所以保障了多线程的安全性。
- 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^ns[0] + 31^(n-1)* s[1]+ … + s[n]
2.1 这里为什么乘以31?
存在即合理,平时不多问自己why?面试时就多几分把握…
- 31是奇质数(odd prime):按照传统,一般都是乘以质数.
- 31可以表示为(2^5-1)。所以它的乘法运算可以被替换成: 31 * i = (i << 5) -i .JVM自动完成优化。
- 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类不可变,那如何实现可变的需求呢?
StringBuilder 和 StringBuffer开始表演,(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()。
优秀!!!,就是他啦…## 标题