欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java面经查缺补漏之第九天(又要开始查缺补漏了,哈哈哈哈哈,今天来q一份阿里的面经,另外分享一份面试技巧,写得真的不错)

程序员文章站 2022-05-12 16:34:45
...

中间断了几天,今天重新开始,这个东西还是要坚持啊!而且最近有些懈怠,多看看别人的面经就知道自己还有多长的路要走!

刷真题,补算法,看面经!

 

先来分享一点我认为我看到的一个比较好的面试技巧。然后再说今天的查缺补漏的内容

作者:_XiaoTeng_
链接:https://www.nowcoder.com/discuss/29890
来源:牛客网
 

  • 好好对着自己写的简历一行一行看一遍,这都是你挖的坑,是准备给面试官作为切入点交流的,并不是自己往里跳的。(对每一行都要有足够了解和把握)

  • 面试过程不要紧张,尤其是前几次,建议先从小公司入手锻炼下面试经验(参考我之后自身的反面教材

  • 面试方式不同,侧重点不同(无非是电话、视频、现场三种)。

        电话面试建议找个人少安静的地方坐着回答,并且建议拿纸笔多做记录多画多写。(当然如果你觉得身边很多朋友可以让你越聊越嗨那也可以,坐着是让你整个节奏慢下来,说话明显更加沉稳,亲身体会过站着走来走去和坐着的区别)

        视频面试其实和电话类似,只是可以实时写代码,面试官能看到你的表情。这里还是要放松,如果你比较紧张,可以不直视镜头,好好想问题就是了,因为很多面试官你答得好也会面无表情(因为他们也不常视频,表情都很尴尬),然后你看到他们没表情的表情肯定会受影响。

        现场面呢,最重要的是和面试官互动了,说几个点:语气要轻松点,多点肢体动作有助表达,多笑;不太好说清的就用笔在纸上画,一遍画一边讲,面试官也会更容易和你交流;如果你可以时不时幽默一下开开玩笑是更好了;见面和离开记得礼貌地握个手说声谢谢。

  • 学会平等交流,别把自己身段放的太低。其实有一点你要清楚,面试是个双选的过程,他可以拒绝你,你也可以拒绝他。千万不要太上赶着,反而会影响自己正常的表达和逻辑。(就跟你见了喜欢的姑娘就不会说话了一个道理)

  • 回答问题的时候不要一口气把知道的全部说完,然后还毫无条理。学会一个知识点由浅入深讲解给面试官,并且留有余地给他进一步去问。

    举个例子:

        就说最简单和普遍的HashMap,让你讲讲,你就可以先说说hashMap的设计原理,底层结构(链表+数组)扩容方式等,从这你就可以说说这种设计好在哪里(比如讲一讲put是如何做hash的),这时候你可以说这种hash可能会有冲突,hashMap也是做了相应设计的。

        然后面试官会问题你怎么解决冲突?你可以再给他讲讲解决hash冲突的三种通常方式,而hashMap用的是链式法,然后可以说到这样会有隐患就是hash链过长。

        面试官再问,你会给他讲解决复杂度高的长链用了红黑树的结构,这里还可以延伸到红黑树的特点或者jdk7和jdk8的不同实现,这时候你可以说解决hash冲突,但hashMap还会有并发和同步的问题。

        面试官会让你再讲讲,你可以说说hashtable是线程安全的,怎么实现的(sync函数),并不好,从而引出更好的juc包,说说concurrentHashMap,之后又可以说道锁分段原理,弱一致性迭代器,concurrentHashMap的锁粒度(java7和java8不同),同包的CopyOnWriteArray等等。

        你还可以延伸说到锁(重量、轻量、悲观乐观各自实现、底层源码等等)、缓存(因为很多时候Map的结构可以作为缓存,从而可以说到缓存系统的设计,kv原理,分布式缓存redis、memcashed等等)    

        举这个例子就是想说,一个简单的基础问题可以一步一步有条理有层次的回答,每一层表达完抛个引子,让面试官可以继续问下去,从而让面试官真正了解你的掌握的深度。

  • 如果真的不巧聊到不擅长的地方,学会转移话题,从一个点中聊自己感兴趣或是有把握的方面(比如你对消息队列不太熟但是redis用的熟,你就可以在问到消息队列的时候说,因为之前都是自己做的项目嘛,性能方面没有考虑到最优,一些异步的方式还是靠redis list去实现的,虽然redis的消息机制并不常见,但当时还是满足了需求,之后可以考虑性能方面的提升和技术评估;又比如问你http请求细节,rest的设计实现细节,你可以说http restapi服务接口性能的一些不足,后来使用了rpc的方式,当然你这么说一定是要对rpc很了解)其实有的时候面试官是知道你是有意转移的,但是往往他们也不会抓着你不会的去问,非让你自己承认自己的盲区,他们也许根本不在意这些。

  • 如果真的被问到不会的,就直接说你不会(说你不会、说你不会,我再补充两遍),或者礼貌地说这方面可能我还要多学习。(对一个拿不准的问题千万不要猜,即使是二选一的那种问题,猜错了直接完蛋,猜对了被人看出来,再往深问还是完蛋)另外,像可能,大概是,我觉得这种表达最好不要,一听就是对一个点没把握,有可能会让面试官觉得学习太浮躁不喜欢寻求原理。
    那对于自己知道原理(确实是理解了的)但是没用过的东西,就讲讲原理,并承认自己实践不足,表现出好学的态度。面试一定要真诚。

  • 问到有什么offer就直接说,不要藏着掖着,也不要把更好的offer(比如bat的)讲的非常诱人,一副bat我都拿到了的样子(面试官会心想,那你还来面试我们干什么)。再强调面试过程一定要真诚。除了直接说,诚实点之外,也要真的做些思考:对方公司跟之前的offer比优势在哪,比如平台更大?专业技能栈更match?工作更有挑战力?地点更合适?有机会留用?随便一条符合的都可以讲出来,起码让对方觉得你想来面是有原因的并且真的有可能加入。(如果你还提前了解对方公司的文化,可以讲出这个文化自己很认同那就更可以了)

 

1.hashMap的扩容原理?

其实扩容也属于一种解决哈希冲突的方式。

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容。

HashMap的容量由一下几个值决定:


static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;      // HashMap初始容量大小(16) 

static final int MAXIMUM_CAPACITY = 1 << 30;               // HashMap最大容量

transient int size;                                       // The number of key-value mappings contained in this map

 

static final float DEFAULT_LOAD_FACTOR = 0.75f;          // 负载因子

 

HashMap的容量size乘以负载因子[默认0.75] = threshold;  // threshold即为开始扩容的临界值

 

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;    // HashMap的基本构成Entry数组

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置。

0.75这个值成为负载因子,那么为什么负载因子为0.75呢?这是通过大量实验统计得出来的,如果过小,比如0.5,那么当存放的元素超过一半时就进行扩容,会造成资源的浪费;如果过大,比如1,那么当元素满的时候才进行扩容,会使get,put操作的碰撞几率增加。

HashMap中扩容是调用resize()方法,方法源码:


void resize(int newCapacity) {

    Entry[] oldTable = table;

    int oldCapacity = oldTable.length;

    //如果当前的数组长度已经达到最大值,则不在进行调整

    if (oldCapacity == MAXIMUM_CAPACITY) {

        threshold = Integer.MAX_VALUE;

        return;

    }

    //根据传入参数的长度定义新的数组

    Entry[] newTable = new Entry[newCapacity];

    //按照新的规则,将旧数组中的元素转移到新数组中

    transfer(newTable);

    table = newTable;

    //更新临界值

    threshold = (int)(newCapacity * loadFactor);

}

//旧数组中元素往新数组中迁移

void transfer(Entry[] newTable) {

    //旧数组

    Entry[] src = table;

    //新数组长度

    int newCapacity = newTable.length;

    //遍历旧数组

    for (int j = 0; j < src.length; j++) {

        Entry<K,V> e = src[j];

        if (e != null) {

            src[j] = null;

            do {

                Entry<K,V> next = e.next;

                int i = indexFor(e.hash, newCapacity);//放在新数组中的index位置

                e.next = newTable[i];//实现链表结构,新加入的放在链头,之前的的数据放在链尾

                newTable[i] = e;

                e = next;

            } while (e != null);

        }

    }

}

 可以看到HashMap不是无限扩容的,当达到了实现预定的MAXIMUM_CAPACITY,就不再进行扩容。

另外hashmap大小是2的幂次可以增加查询效率。

2.Integer的常量缓存池的问题?

什么是Integer常量缓存池

当我们使用Integer的时候会存储数据,避免重复的new对象,缓存数据的范围在-128 到127 之间的数据, 如果超出这个数据则创建一个新的对象。

3.ConcurrentHashMap的size()怎么做的?

参考:https://zhuanlan.zhihu.com/p/40627259

什么是concurrenthashmap

线程安全的 Map 的实现有 HashTable 和 ConcurrentHashMap 等。HashTable 对集合读写操作通过 Synchronized 同步保障线程安全, 整个集合只有一把锁, 对集合的操作只能串行执行,性能不高。ConcurrentHashMap 是另一个线程安全的 Map, 通常来说他的性能优于 HashTable。 ConcurrentHashMap 的实现在 JDK1.7 和 JDK 1.8 有所不同。

在 JDK1.7 版本中,ConcurrentHashMap 的数据结构是由一个 Segment 数组和多个 HashEntry 组成。简单理解就是ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 Segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。

JDK1.8 的实现已经摒弃了 Segment 的概念,而是直接用 Node 数组 + 链表 + 红黑树的数据结构来实现,并发控制使用 Synchronized 和 CAS 来操作,整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本。 通过 HashMap 查找的时候,根据 hash 值能够快速定位到数组的具体下标,如果发生 Hash 碰撞,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

在jdk1.8中,采用了CAS+syn的形式来保证hashmap并发的安全性。

  • JDK1.7 和 JDK1.8 对 size 的计算是不一样的。 1.7 中是先不加锁计算三次,如果三次结果不一样在加锁。
  • JDK1.8 size 是通过对 baseCount 和 counterCell 进行 CAS 计算,最终通过 baseCount 和 遍历 CounterCell 数组得出 size。
  • JDK 8 推荐使用mappingCount 方法,因为这个方法的返回值是 long 类型,不会因为 size 方法是 int 类型限制最大值。

4.Spring的AOP关于拦截private方法一些问题?

网上有人说可以通过反射机制拦截private方法,不知道对不对。

5.hashmap在1.7和1.8的不同点?

链表在超过一定大小的时候,默认是8,就会转化为红黑树。

头插法改成了尾插法。