面试话痨(二)C:JAVA String,别觉得你穿个马甲我就不认识你了

  面试话痨系列是从技术广度的角度去回答面试官提的问题,适合萌新观看!html

   面试官,别再问我火箭怎么造了,我知道螺丝的四种拧法,你想听吗?java


  String相关的题目,是面试中常常考察的点,当面试中遇到了String相关的问题,咱们能够这么聊:面试

一:String底层结构算法

  从底层结构上来讲,jdk1.8的String,底层是char[]。我在工做中几乎不多用到char[],由于List太好用了,我宁愿用List<Character>也不想用char[],由于之前学C时体会过用char[]的痛苦,长度必须事先设定好,也没有丰富的API去处理数据。因此刚开始用java时,以为String这个类简直太好用了(直视面试官的眼睛,露出有点很差意思的笑)。后来才知道,原来是源码在替咱们负重前行。数据库

  好比咱们都知道String的拼接,大部分状况下都是新建一个空间(这里能够用稍微缓慢不肯定的语气说这句话,稍有心的面试官就会问你什么状况下不会新建空间,你就能够回答:1.连续相加时jdk会自动优化成一次空间的新建,2.两个字符串常量相加的值若是是已经存在的字符串常量,那么会直接指向这个已存在的字符常量),新建的缘由就是由于底层是一个char[],没法直接扩容。而新建空间的花费不少,因此对于有屡次拼接需求的状况,我会酌情选择StringBuffer和StringBuilder,主要是看是否存在多线程的状况,存在的话就用StringBuffer(线程安全的方式,涉及的知识点不少,能够专门写一章,先留个传送门:面试话痨(三)我会锁的三种配法,您配吗?)。编程

 

  另外,我有次看jdk11里面String的底层已经变成了byte[],顺手查了一下,说是用byte[]存的话,能够减小大概一半的内存占用,再具体的怎么实现怎么优化的没太仔细了解。(对于本身不太懂的知识,介绍完本身懂得部分之后,必定要接一句:再往下本身就不清楚了之类的话。这样面试官就不会再接着问你这个问题)。数组

  题外话:字符之间能够直接使用‘+’和‘-’运算符,实际计算的是字符对应的ASCII码的位置的加减,如 ‘1’ - ‘0’ = (int)1,用这个方法能够快速的完成char到int的强转,不少String类的题目都须要用到这个方法。缓存

 

二:final修饰符安全

  由于char[]自己就是一个不能扩容的数组,因此用不可变的常量去修饰字符串就很适合。另外,final的特性也能为String带来不少的好处:数据结构

  1. 安全方面来讲,密码、我的信息等基本都是以String为载体来进行存储的,final修饰的String类不能够被继承,建立的对象也不能够被改变,能够保证关键数据的安全性。

  2. 性能方面来讲,final修饰后,String就被放入了常量池,常量池中有专门的字符串常量池,JVM能够将多个同样的String指向同一个地址,其中有任意一个String改变时,由于final的特性都会去从新建一个地址(或者指向另一个值刚好相同的地址),不会影响原来的值;另外,String的不可变性让它的hashcode是固定的,能够被缓存的,用来作Map的key运算更快捷;还有,final修饰后,也不会存在多线程安全的问题。

  (原本想接着说一下常量的,可是一想到常量,脑子里就蹦出了方法区,堆,栈,JDK版本更替、GC方式,类加载,JVM内存模型,Java内存模型,可见性,volatile,自旋锁,太多了。先留个坑吧。面试话痨(四)常量在哪里呀,常量在哪里)。

  与final长得像的,还有finally和finalize。finally就是跟在try或者catch后面的一个关键字,之前咱们学的是,try以后一定会执行finally,但其实这个是有前提的,就是程序不崩溃或者不被强制结束,try中加一句 System.exit(0); ,finally就不会被执行。

  至于finalize,这个方法几乎没人用了,它是Object类中就自带的方法,学名析构函数,据说是在java刚出生时,为了迎合C编程人员的习惯添加的方法。在对象快要被回收时调用且只会被调用一次。若是finalize中的代码将另一个指针指向了该对象,那么JVM就会放弃该对象的回收。等到下一次该对象又不可达了,JVM就会直接回收,不会再调用finalize方法。所以方法存在不肯定性,不多被使用。

 

三:equals

  在Obejct中,equals和==是同样的,都是直接比较数据的存放地址是否一致,而在String中,equals方法被重写成三个步骤的判断。

  

  HashMap中的equals大体也是使用了这三个步骤的判断:地址是否相等 --> size是否相等 --> 每个key是否有equals的key,对应的value是否equals。

  不一样的类对于equals的实现方式不同,但他们都遵循若hashcode不相等,则equals也不相等的原则。这样作的目的主要是为了让Hash类集合插入值时的重复断定更合理:

   

  试想一下,假设两个身份证对象的hashcode不相等,equals却相等,那么两个相同身份证信息就会被放入HashSet中了!这会对咱们的编码形成很大的困扰,因此对于须要被用于Hash的key值的对象(HashSet的值插入至关因而将值放入了key中,再插入了一个固定value值的HashMap),咱们须要知足若hashcode不相等,则equals也不相等的原则。

  HashSet中的元素重复断定,优先断定hashcode的缘由是:hashcode经过与HashSet的大小取余之后,能够快速的定位到可能相等的元素的位置,这时再对该位置上存在的元素的进行equals就行,只要hash分布的足够均匀,该操做的时间复杂度就接近于O(1),而若去掉hashcode寻址,直接使用equals对n个数进行对比的话,时间复杂度就是O(n)。

  说到这里还能够提一个常常被问到的问题,HashMap的查询时间复杂度是多少?

 

  如我上面的分析同样,时间复杂度是接近于O(1)的,固然也会有O(n)的状况。好比新建了一个类,hashcode值所有返回1,这样全部的值都会接到一个链表上,数组+链表的结构就退化成纯链表,时间复杂度就变成了O(n)。jdk1.8之后,链表长度超过8,而且数组长度大于64时,链表会变成红黑树,红黑树的遍历时间复杂度为O(log(n))。(红黑树、B树、索引、数据库、图、广度搜索、深度搜索相关的数据结构及算法后面再说。面试话痨(N)尚未头绪不知道什么时间写)。

 


  最后,送你们一道我特别喜欢的面试题。 

    请将一段字符串转换成整数,不要使用parseInt。

 

    public int MyParseInt(String str) throws Exception{
        /*
            1.数据校验
            (无论面试过程当中遇到什么题,均可以先优雅的写下
                //1.数据校验
              而后成竹在胸的写一些有的没的校验,写的过程当中再想后面应该怎么写。
              写注释+周全的校验是写代码的基本素养)
         */
        String errMsg = check(str);
        if (errMsg != null) {
            throw new Exception(errMsg);
        }
        char[] c = str.toCharArray();
        int result = 0;
        for (int i = 0; i < c.length; i++) {
            result = result * 10 + (c[i] - '0');
        }
        return result;
    }

    /**
     * 
     * @param str 待校验的数据
     * @return 错误信息,没有错误时返回null
     */
    private String check(String str) {
        if (str == null) {
            return "输入不能为空!";
        }
        /*
            不多有人能一遍写出不用调试没有bug的代码,更别说在面试那么紧张的气氛中了
            因此,在写完可以基本实现题目要求的代码之后,最好能想下代码中不足的地方,而后坦诚的告诉面试官:
                这个代码还有须要改进的地方,好比能够添加对 负号、正号、小数点的支持,好比没有校验int的阀值,
                若是是工做中遇到,我确定能解决这些问题。
         */
        if (str.replaceAll("[0-9]","").length() > 0){
            return "包含非法字符";
        }
        return null;
    }

   本章中提到的一些面试回答技巧这里再写一下,我我的以为挺有用的:

  一、 对于本身擅长的问题,能够在问题中留下一些点,吸引面试官继续提问。

  二、 对于本身不擅长的问题,回答完本身会的部分之后,能够直接说再往下本身就不懂了,不要不懂装懂,让面试官继续问下去后再回答不知道。

  三、 若是面试官已经问了你不知道的问题,尽可能从性能和安全方面,说出一个答案。直接先坦诚的告诉面试官你不知道,只是根据我的经验这个应该是这样的,由于这样对于安全/性能的优势是xxx。


   目录以下:

   面试话痨(一)让咱们来热切的讨论这个养猪场吧

   面试话痨(二)C:JAVA String,别觉得你穿个马甲我就不认识你了