当前位置 博文首页 > 不划水的可乐的博客:一次关于String,String Builder,String Buf

    不划水的可乐的博客:一次关于String,String Builder,String Buf

    作者:[db:作者] 时间:2021-09-12 12:06

    前言:

    String,StringBuilder,StringBuffer的区别是啥?这个面试题估计每个程序员都应该碰到过吧。依稀记得第一次面试的时候,面试官问我这个问题时,心想着这不是很简单吗。深入了解这个问题后,发现这里面并不简单,面试官的套路还是深啊!

    在这里插入图片描述

    面试官:你好,欢迎来面试,介绍一下自己吧。
    你好,我是**, 来自 **,毕业于拖拉机学院,目前工作了2年,在 ** 公司做了一年的开发,做过的项目有。。。布拉布拉

    然后双方对项目进行深入的讨论。。。

    基础问答:

    面试官:为什么我们建议在定义HashMap的时候,就指定它的初始化大小呢?

    答:在当我们对HashMap初始化时,如果没有为其设置初始化容量,那么系统会默认创建一个容量为16的大小的集合。当我们向HashMap中添加元素时,如果HashMap的容量值超过了它的临界值(默认16*0.75=12)时,(0.75是HashMap的加载因子)HashMap将会重新扩容到下一个2的指数次幂(2^4=16 下一个2的指数次幂是2^5=32)。由于HashMap扩容要进行resize的操作,频繁的resize,会导致HashMap的性能下降,所以建议在确定HashMap集合的大小的情况下,指定其初始化大小,避免做过多的resize操作,导致性能下降。

    面试官:那么HashMap什么时候进行扩容呢?

    答:当我们不断的向HashMap中添加元素时,它会判断HashMap当前的容量值(当前元素的个数)是否超过了它的临界值(在没有指定其初始化大小时,默认16*0.75=12),如果添加的元素个数超过了临界值,它就会开始进行扩容。

    面试官:HashMap在扩容时,扩容到多大?

    答:HashMap在扩容时,它会扩容到下一个2的指数次幂,即当前容量的2倍,比如当前容量是24=16,将会扩容到下一个2的指数次幂25=32.

    面试官:你能说一下为什么说HashMap是线程不安全的吗?

    答:HashMap在多线程并发时线程不安全,主要表现在下面两个方面:

    (1) 当向HashMap中put(添加)元素时导致的多线程数据不一致

    比如有两个线程 A 和 B ,首先 A 希望插入一个 key-value键值对到HashMap 中,它首先计算记录所要落到的 hash 桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程 A 的时间片用完了,而此时线程 B 被调度得以执行,和线程 A 一样执行,只不过线程 B 成功将记录插到了桶里面。假设线程 A 插入的记录计算出来的 hash 桶索引和线程 B 要插入的记录计算出来的 hash 桶索引是一样的,那么当线程 B 成功插入之后,线程 A 再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程 B 插入的记录,这样线程 B 插入的记录就凭空消失了,造成了数据不一致的行为。

    简单来说就是在多线程环境下,向HashMap集合中添加元素会存在覆盖的现象,导致了线程不安全。

    (2) 当HashMap进行扩容调用resize()函数时引起死循环

    HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。

    HashMap的线程不安全主要体现在下面两个方面:

    1.在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。

    2.在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。

    面试官:那你能说一下String,StringBuilder,StringBuffer到底有什么区别吗?

    String的值是不可改变的,这就导致每次对String的操作都会生成新的String对象,不禁效率底下, 而且浪费大量的内存空间;StringBuilder是可变类,任何对他指向的字符串的操作都不会产生新的对 象,但单线程不安全;StringBuffer底层方法使用了synchronized关键字,线程比较安全,但效率 较StringBuilder慢;

    面试官:String类它可以被继承吗?

    不可以,因为String类使用final关键字进行修饰,所以不能被继承,并且StringBuilder,StringBuffer也是如此都被final关键字修饰。

    面试官:为什么String的是不可变的?

    因为存储数据的char数组是使用final进行修饰的,所以不可变。
    在这里插入图片描述

    面试官:为什么String Buffer是线程安全的?

    这是因为在StringBuffer类内,常用的方法都使用了synchronized 进行同步所以是线程安全的,然而StringBuilder并没有。这也就是运行速度StringBuilder > StringBuffer的原因了。

    另外本人整理了20年面试题大全,包含spring、并发、数据库、Redis、分布式、dubbo、JVM、微服务等方面总结,下图是部分截图,需要的话点这里点这里,暗号CSDN。

    在这里插入图片描述

    面试官:刚才你说到了synchronized关键字 ,那能讲讲synchronized的表现形式嘛?

    • 对于静态同步方法,锁是当前类的class对象。
    • 对于普通同步方法 ,锁是当前实例对象。
    • 对于同步方法块,锁是Synchonized括号配置的对象。

    面试官:能讲讲synchronized的原理嘛?

    synchronized是一个重量级锁,实现依赖于JVM 的 monitor 监视器锁。主要使用monitorenter和monitorexit指令来实现方法同步和代码块同步。在编译的是时候,会将monitorexit指令插入到同步代码块的开始位置,而monitorexit插入方法结束处和异常处,并且每一个monitorexit都有一个与之对应的monitorexit。

    任何对象都有一个monitor与之关联,当一个monitor被持有后,它将被处于锁定状态,线程执行到monitorenter指令时间,会尝试获取对象所对应的monitor的所有权,即获取获得对象的锁,由于在编译期会将monitorexit插入到方法结束处和异常处,所以在方法执行完毕或者出现异常的情况会自动释放锁。

    面试官:内心OS:竟然没问倒他,看来让他培训是没啥希望了,让他回去等通知吧 。

    你的水平我这边基本了解了,我对你还是比较满意的,但是我们这边还有几个候选人还没面试,没办法直接给你答复,你先回去等通知吧。
    ??
    :好的好的,谢谢面试官,我这边先回去了。内心OS:好险好险,一个string差点被问倒,幸好面试前好好看了一下,不然今天就是面试惨案了。
    在这里插入图片描述

    最后:

    针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

    下面的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2020收集的一些大厂的面试真题(都整理成文档,小部分截图),有需要的可以点击进入暗号CSDN

    在这里插入图片描述

    在这里插入图片描述

    cs