精华内容
下载资源
问答
  • 面试官,不要再问我三次握手和四次挥手

    万次阅读 多人点赞 2019-10-08 09:55:58
    三次握手和四次挥手是各个公司常见的考点,也具有一定的水平区分度,也被一些面试官作为热身题。很多小伙伴说这个问题刚开始回答的挺好,但是后面越回答越冒冷汗,最后就歇菜了。 见过比较典型的面试场景是这样的: ...

    温馨提示:本篇文章会长期维护及更新,详情见:https://yuanrengu.com/2020/77eef79f.html

    面试相关文章推荐:

    三次握手和四次挥手是各个公司常见的考点,也具有一定的水平区分度,也被一些面试官作为热身题。很多小伙伴说这个问题刚开始回答的挺好,但是后面越回答越冒冷汗,最后就歇菜了。

    见过比较典型的面试场景是这样的:

    面试官:请介绍下三次握手
    求职者:第一次握手就是客户端给服务器端发送一个报文,第二次就是服务器收到报文之后,会应答一个报文给客户端,第三次握手就是客户端收到报文后再给服务器发送一个报文,三次握手就成功了。
    面试官:然后呢?
    求职者:这就是三次握手的过程,很简单的。
    面试官:。。。。。。
    番外篇:一首凉凉送给你

    记住猿人谷一句话:面试时越简单的问题,一般就是隐藏着比较大的坑,一般都是需要将问题扩展的。上面求职者的回答不对吗?当然对,但距离面试官的期望可能还有点距离。

    希望大家能带着如下问题进行阅读,收获会更大。

    1. 请画出三次握手和四次挥手的示意图
    2. 为什么连接的时候是三次握手?
    3. 什么是半连接队列?
    4. ISN(Initial Sequence Number)是固定的吗?
    5. 三次握手过程中可以携带数据吗?
    6. 如果第三次握手丢失了,客户端服务端会如何处理?
    7. SYN攻击是什么?
    8. 挥手为什么需要四次?
    9. 四次挥手释放连接时,等待2MSL的意义?

    三次握手和四次挥手.png

    1. 三次握手

    三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。

    刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。
    进行三次握手:

    • 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN。此时客户端处于 SYN_SENT 状态。

      首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。

    • 第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s)。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_RCVD 的状态。

      在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。

    • 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。

      确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。

    发送第一个SYN的一端将执行主动打开(active open),接收这个SYN并发回下一个SYN的另一端执行被动打开(passive open)。

    在socket编程中,客户端执行connect()时,将触发三次握手。

    三次握手.png

    1.1 为什么需要三次握手,两次不行吗?

    弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。

    • 第一次握手:客户端发送网络包,服务端收到了。
      这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
    • 第二次握手:服务端发包,客户端收到了。
      这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
    • 第三次握手:客户端发包,服务端收到了。
      这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

    因此,需要三次握手才能确认双方的接收与发送能力是否正常。

    试想如果是用两次握手,则会出现下面这种情况:

    如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。

    1.2 什么是半连接队列?

    服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列

    当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

    这里在补充一点关于SYN-ACK 重传次数的问题:
    服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。
    注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…

    1.3 ISN(Initial Sequence Number)是固定的吗?

    当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。ISN可以看作是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。

    三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

    1.4 三次握手过程中可以携带数据吗?

    其实第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手不可以携带数据

    为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。

    也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。

    1.5 SYN攻击是什么?

    服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。

    检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstat 命令来检测 SYN 攻击。

    netstat -n -p TCP | grep SYN_RECV
    

    常见的防御 SYN 攻击的方法有如下几种:

    • 缩短超时(SYN Timeout)时间
    • 增加最大半连接数
    • 过滤网关防护
    • SYN cookies技术

    2. 四次挥手

    建立一个连接需要三次握手,而终止一个连接要经过四次挥手(也有将四次挥手叫做四次握手的)。这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

    TCP 连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),客户端或服务端均可主动发起挥手动作。

    刚开始双方都处于ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程如下:

    • 第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。
      即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。
    • 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。
      即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。
    • 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
      即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。
    • 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
      即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。

    收到一个FIN只意味着在这一方向上没有数据流动。客户端执行主动关闭并进入TIME_WAIT是正常的,服务端通常执行被动关闭,不会进入TIME_WAIT状态。

    在socket编程中,任何一方执行close()操作即可产生挥手操作。
    image.png

    2.1 挥手为什么需要四次?

    因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。

    2.2 2MSL等待状态

    TIME_WAIT状态也成为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。

    对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。

    这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。

    2.3 四次挥手释放连接时,等待2MSL的意义?

    MSL是Maximum Segment Lifetime的英文缩写,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

    为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。

    两个理由:

    1. 保证客户端发送的最后一个ACK报文段能够到达服务端

      这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,服务端超时重传FIN+ACK报文段,而客户端能在2MSL时间内收到这个重传的FIN+ACK报文段,接着客户端重传一次确认,重新启动2MSL计时器,最后客户端和服务端都进入到CLOSED状态,若客户端在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到服务端重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则服务端无法正常进入到CLOSED状态。

    2. 防止“已失效的连接请求报文段”出现在本连接中

      客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。

    2.4 为什么TIME_WAIT状态需要经过2MSL才能返回到CLOSE状态?

    理论上,四个报文都发送完毕,就可以直接进入CLOSE状态了,但是可能网络是不可靠的,有可能最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文

    3. 总结

    《TCP/IP详解 卷1:协议》有一张TCP状态变迁图,很具有代表性,有助于大家理解三次握手和四次挥手的状态变化。如下图所示,粗的实线箭头表示正常的客户端状态变迁,粗的虚线箭头表示正常的服务器状态变迁。

    TCP状态变迁图.jpg

    以后面试官再问你三次握手和四次挥手,直接把这一篇文章丢给他就可以了,他想问的都在这里。

    参考:《TCP/IP详解 卷1:协议》


    个人公众号:猿人谷
    公众号会有更多关于面试、源码解读、架构设计、程序人生等干货!
    期待您的关注

    在这里插入图片描述

    展开全文
  • 一个HashMap跟面试官扯了半个小时

    万次阅读 多人点赞 2020-03-15 23:37:16
    一个HashMap能跟面试官扯上半个小时 关注 安琪拉的博客 1.回复面试领取面试资料 2.回复书籍领取技术电子书 3.回复交流领取技术电子书 前言 HashMap应该算是Java后端工程师面试的必问题,因为其中的知识点太多,很...

    一个HashMap能跟面试官扯上半个小时

    《安琪拉与面试官二三事》系列文章
    一个HashMap能跟面试官扯上半个小时
    一个synchronized跟面试官扯了半个小时
    一个volatile跟面试官扯了半个小时

    《安琪拉教鲁班学算法》系列文章

    安琪拉教鲁班放技能之动态规划

    前言

    HashMap应该算是Java后端工程师面试的必问题,因为其中的知识点太多,很适合用来考察面试者的Java基础。

    开场

    面试官: 你先自我介绍一下吧!

    安琪拉: 我是安琪拉,草丛三婊之一,最强中单(钟馗不服)!哦,不对,串场了,我是**,目前在–公司做–系统开发。

    面试官: 看你简历上写熟悉Java集合,HashMap用过的吧?

    安琪拉: 用过的。(还是熟悉的味道)

    面试官: 那你跟我讲讲HashMap的内部数据结构?

    安琪拉: 目前我用的是JDK1.8版本的,内部使用数组 + 链表红黑树;

    安琪拉: 方便我给您画个数据结构图吧:

    面试官: 那你清楚HashMap的数据插入原理吗?

    安琪拉: 呃[做沉思状]。我觉得还是应该画个图比较清楚,如下:

    在这里插入图片描述

    1. 判断数组是否为空,为空进行初始化;
    2. 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;
    3. 查看 table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中;
    4. 存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据(onlyIfAbsent为false);
    5. 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;(如果当前节点是树型节点证明当前已经是红黑树了)
    6. 如果不是树型节点,创建普通Node加入链表中;判断链表长度是否大于 8并且数组长度大于64, 大于的话链表转换为红黑树;
    7. 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。

    面试官: 陷入沉默,讲的这么清楚,难道是也关注了微信公众号【安琪拉的博客】,我继续按照套路问,刚才你提到HashMap的初始化,那HashMap怎么设定初始容量大小的吗?

    安琪拉: [这也算问题??] 一般如果new HashMap() 不传值,默认大小是16,负载因子是0.75, 如果自己传入初始大小k,初始化大小为 大于k的 2的整数次方,例如如果传10,大小为16。(补充说明:实现代码如下)

    static final int tableSizeFor(int cap) {
      int n = cap - 1;
      n |= n >>> 1;
      n |= n >>> 2;
      n |= n >>> 4;
      n |= n >>> 8;
      n |= n >>> 16;
      return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    

    补充说明:下图是详细过程,算法就是让初始二进制右移1,2,4,8,16位,分别与自己位或,把高位第一个为1的数通过不断右移,把高位为1的后面全变为1,最后再进行+1操作,111111 + 1 = 1000000 = 262^6 (符合大于50并且是2的整数次幂 )

    在这里插入图片描述

    面试官: 你提到hash函数,你知道HashMap的哈希函数怎么设计的吗?

    安琪拉: [问的还挺细] hash函数是先拿到 key 的hashcode,是一个32位的int值,然后让hashcode的高16位和低16位进行异或操作。

    面试官: 那你知道为什么这么设计吗?

    安琪拉: [这也要问],这个也叫扰动函数,这么设计有二点原因:

    1. 一定要尽可能降低hash碰撞,越分散越好;
    2. 算法一定要尽可能高效,因为这是高频操作, 因此采用位运算;

    面试官: 为什么采用hashcode的高16位和低16位异或能降低hash碰撞?hash函数能不能直接用key的hashcode?

    [这问题有点刁钻], 安琪拉差点原地💥了,恨不得出biubiubiu 二一三连招。

    安琪拉: 因为key.hashCode()函数调用的是key键值类型自带的哈希函数,返回int型散列值。int值范围为**-2147483648~2147483647**,前后加起来大概40亿的映射空间。只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。你想,如果HashMap数组的初始大小才16,用之前需要对数组的长度取模运算,得到的余数才能用来访问数组下标。(来自知乎-胖君)

    源码中模运算就是把散列值和数组长度-1做一个"与"操作,位运算比取余%运算要快。

    bucketIndex = indexFor(hash, table.length);
    
    static int indexFor(int h, int length) {
         return h & (length-1);
    }
    

    顺便说一下,这也正好解释了为什么HashMap的数组长度要取2的整数幂。因为这样(数组长度-1)正好相当于一个“低位掩码”。“与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问。以初始长度16为例,16-1=15。2进制表示是00000000 00000000 00001111。和某散列值做“与”操作如下,结果就是截取了最低的四位值。

      10100101 11000100 00100101
    & 00000000 00000000 00001111
    ----------------------------------
      00000000 00000000 00000101    //高位全部归零,只保留末四位
    

    但这时候问题就来了,这样就算我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。更要命的是如果散列本身做得不好,分布上成等差数列的漏洞,如果正好让最后几个低位呈现规律性重复,就无比蛋疼。

    时候“扰动函数”的价值就体现出来了,说到这里大家应该猜出来了。看下面这个图,

    img

    右移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。

    最后我们来看一下Peter Lawley的一篇专栏文章《An introduction to optimising a hashing strategy》里的的一个实验:他随机选取了352个字符串,在他们散列值完全没有冲突的前提下,对它们做低位掩码,取数组下标。

    img

    结果显示,当HashMap数组长度为512的时候(292^9),也就是用掩码取低9位的时候,在没有扰动函数的情况下,发生了103次碰撞,接近30%。而在使用了扰动函数之后只有92次碰撞。碰撞减少了将近10%。看来扰动函数确实还是有功效的。

    另外Java1.8相比1.7做了调整,1.7做了四次移位和四次异或,但明显Java 8觉得扰动做一次就够了,做4次的话,多了可能边际效用也不大,所谓为了效率考虑就改成一次了。

    下面是1.7的hash代码:

    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    

    面试官: 看来做过功课,有点料啊!是不是偷偷看了公众号安琪拉的博客, 你刚刚说到1.8对hash函数做了优化,1.8还有别的优化吗?

    安琪拉: 1.8还有三点主要的优化:

    1. 数组+链表改成了数组+链表或红黑树;
    2. 链表的插入方式从头插法改成了尾插法,简单说就是插入时,如果数组位置上已经有元素,1.7将新元素放到数组中,原始节点作为新节点的后继节点,1.8遍历链表,将元素放置到链表的最后;
    3. 扩容的时候1.7需要对原数组中的元素进行重新hash定位在新数组的位置,1.8采用更简单的判断逻辑,位置不变或索引+旧容量大小;
    4. 在插入时,1.7先判断是否需要扩容,再插入,1.8先进行插入,插入完成再判断是否需要扩容;

    面试官: 你分别跟我讲讲为什么要做这几点优化;

    安琪拉: 【咳咳,果然是连环炮】

    1. 防止发生hash冲突,链表长度过长,将时间复杂度由O(n)降为O(logn);

    2. 因为1.7头插法扩容时,头插法会使链表发生反转,多线程环境下会产生环;

      A线程在插入节点B,B线程也在插入,遇到容量不够开始扩容,重新hash,放置元素,采用头插法,后遍历到的B节点放入了头部,这样形成了环,如下图所示:

      在这里插入图片描述

      1.7的扩容调用transfer代码,如下所示:

      void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
          while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
              e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i]; //A线程如果执行到这一行挂起,B线程开始进行扩容
            newTable[i] = e;
            e = next;
          }
        }
      }
      
      
    3. 扩容的时候为什么1.8 不用重新hash就可以直接定位原节点在新数据的位置呢?

      这是由于扩容是扩大为原数组大小的2倍,用于计算数组位置的掩码仅仅只是高位多了一个1,怎么理解呢?

      扩容前长度为16,用于计算(n-1) & hash 的二进制n-1为0000 1111,扩容为32后的二进制就高位多了1,为0001 1111。

      因为是& 运算,1和任何数 & 都是它本身,那就分二种情况,如下图:原数据hashcode高位第4位为0和高位为1的情况;

      第四位高位为0,重新hash数值不变,第四位为1,重新hash数值比原来大16(旧数组的容量)

      在这里插入图片描述

    面试官: 那HashMap是线程安全的吗?

    安琪拉: 不是,在多线程环境下,1.7 会产生死循环、数据丢失、数据覆盖的问题,1.8 中会有数据覆盖的问题,以1.8为例,当A线程判断index位置为空后正好挂起,B线程开始往index位置的写入节点数据,这时A线程恢复现场,执行赋值操作,就把A线程的数据给覆盖了;还有++size这个地方也会造成多线程同时扩容等问题。

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
      Node<K,V>[] tab; Node<K,V> p; int n, i;
      if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
      if ((p = tab[i = (n - 1) & hash]) == null)  //多线程执行到这里
        tab[i] = newNode(hash, key, value, null);
      else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
          e = p;
        else if (p instanceof TreeNode) // 这里很重要
          e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
          for (int binCount = 0; ; ++binCount) {
            if ((e = p.next) == null) {
              p.next = newNode(hash, key, value, null);
              if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                treeifyBin(tab, hash);
              break;
            }
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
              break;
            p = e;
          }
        }
        if (e != null) { // existing mapping for key
          V oldValue = e.value;
          if (!onlyIfAbsent || oldValue == null)
            e.value = value;
          afterNodeAccess(e);
          return oldValue;
        }
      }
      ++modCount;
      if (++size > threshold) // 多个线程走到这,可能重复resize()
        resize();
      afterNodeInsertion(evict);
      return null;
    }
    

    面试官: 那你平常怎么解决这个线程不安全的问题?

    安琪拉: Java中有HashTable、Collections.synchronizedMap、以及ConcurrentHashMap可以实现线程安全的Map。

    HashTable是直接在操作方法上加synchronized关键字,锁住整个数组,粒度比较大,Collections.synchronizedMap是使用Collections集合工具的内部类,通过传入Map封装出一个SynchronizedMap对象,内部定义了一个对象锁,方法内通过对象锁实现;ConcurrentHashMap使用分段锁,降低了锁粒度,让并发度大大提高。

    面试官: 那你知道ConcurrentHashMap的分段锁的实现原理吗?

    安琪拉: 【天啦撸! 俄罗斯套娃,一个套一个】ConcurrentHashMap成员变量使用volatile 修饰,免除了指令重排序,同时保证内存可见性,另外使用CAS操作和synchronized结合实现赋值操作,多线程操作只会锁住当前操作索引的节点。

    如下图,线程A锁住A节点所在链表,线程B锁住B节点所在链表,操作互不干涉。

    面试官: 你前面提到链表转红黑树是链表长度达到阈值,这个阈值是多少?

    安琪拉: 阈值是8,红黑树转链表阈值为6

    面试官: 为什么是8,不是16,32甚至是7 ?又为什么红黑树转链表的阈值是6,不是8了呢?

    安琪拉: 【你去问作者啊!天啦撸,biubiubiu 真想213连招】因为作者就这么设计的,哦,不对,因为经过计算,在hash函数设计合理的情况下,发生hash碰撞8次的几率为百万分之6,概率说话。。因为8够用了,至于为什么转回来是6,因为如果hash碰撞次数在8附近徘徊,会一直发生链表和红黑树的互相转化,为了预防这种情况的发生。

    面试官: HashMap内部节点是有序的吗?

    安琪拉: 是无序的,根据hash值随机插入

    面试官: 那有没有有序的Map?

    安琪拉: LinkedHashMap 和 TreeMap

    面试官: 跟我讲讲LinkedHashMap怎么实现有序的?

    安琪拉: LinkedHashMap内部维护了一个单链表,有头尾节点,同时LinkedHashMap节点Entry内部除了继承HashMap的Node属性,还有before 和 after用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。

    /**
     * The head (eldest) of the doubly linked list.
    */
    transient LinkedHashMap.Entry<K,V> head;
    
    /**
      * The tail (youngest) of the doubly linked list.
    */
    transient LinkedHashMap.Entry<K,V> tail;
    //链接新加入的p节点到链表后端
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
      LinkedHashMap.Entry<K,V> last = tail;
      tail = p;
      if (last == null)
        head = p;
      else {
        p.before = last;
        last.after = p;
      }
    }
    //LinkedHashMap的节点类
    static class Entry<K,V> extends HashMap.Node<K,V> {
      Entry<K,V> before, after;
      Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
      }
    }
    

    示例代码:

    public static void main(String[] args) {
      Map<String, String> map = new LinkedHashMap<String, String>();
      map.put("1", "安琪拉");
      map.put("2", "的");
      map.put("3", "博客");
    
      for(Map.Entry<String,String> item: map.entrySet()){
        System.out.println(item.getKey() + ":" + item.getValue());
      }
    }
    //console输出
    1:安琪拉
    2:3:博客
    

    面试官: 跟我讲讲TreeMap怎么实现有序的?

    安琪拉:TreeMap是按照Key的自然顺序或者Comprator的顺序进行排序,内部是通过红黑树来实现。所以要么key所属的类实现Comparable接口,或者自定义一个实现了Comparator接口的比较器,传给TreeMap用于key的比较。

    面试官: 前面提到通过CAS 和 synchronized结合实现锁粒度的降低,你能给我讲讲CAS 的实现以及synchronized的实现原理吗?

    安琪拉: 下一期咋们再约时间,OK?

    面试官: 好吧,回去等通知吧!


    回复评论区的几个问题:

    1. @掌心一点微笑: put方法时候,指定位置存在数据->否->存放节点 -> 放入红黑树节点吗?不应该是存放节点->节点数是否大于阈值?这里不懂,求大佬解释
      这个地方图画的确实有问题,感谢指正,已更新。
    2. @海淀好男孩:初始容量不是2的幂会自动改成2的幂那里有些错误吧,50的二进制和下面的无符号右移4位不对啊
      这里是以50做初始值演示的,先进行-1 操作然后开始二进制运算的。

    参考资料

    1. An introduction to optimising a hashing strategy
    2. JDK 源码中 HashMap 的 hash 方法原理是什么?
    3. 淡腾的枫-HashMap中的hash函数

    关注Wx公众号:【安琪拉的博客】 —揭秘Java后端技术,还原技术背后的本质

    《安琪拉与面试官二三事》系列文章 持续更新中
    一个HashMap能跟面试官扯上半个小时
    一个synchronized跟面试官扯了半个小时

    展开全文
  • 面试官:指针都不会,我们不需要你这样的人!

    万次阅读 多人点赞 2020-05-19 21:14:15
    看完这篇“指针”,包你在面试官面前,有得扯,扯得清!

    上周,学弟在一个面试中,被考官问到了有关指针的问题。然鹅,学弟说了半天,最终也没能让考官满意。为此我还替他捏了一把冷汗!鉴于此,今天我来写一篇关于指针的初级干货,让你在面试官前有得扯,并扯清楚

    1. 初识指针

    什么是指针呢?想想,我们脑海里的指针大概长什么样:

    在这里插入图片描述
    这样?那我们今天就来聊一聊“指针”表的使用方法吧!

    在这里插入图片描述
    咳咳,不开玩笑了!
    在编程语言中,指针可比你上面能看到的“指针”抽象得多。所以在学习时,我们往往知道有指针这么个东西,但是又迷迷糊糊不知道怎么用它。下面,我就来带大家直观地感受一下什么是指针!

    简单来说,指针是一种特殊的变量。特殊在于,这种变量存储的不是普通值(比如1,2,100,‘q’);它存储的是内存地址,比如0x101、0x886等。

    不理解也没关系,下面我画了一张示意图,让大家直观地看一下什么是指针:
    在这里插入图片描述
    注:图中的0x886等都是为方便说明假定的虚拟地址。

    我们都知道,变量的存储是需要占用内存空间的。
    图中,Age是一个变量,它的存放地址是0x886,存放的值是18。
    pAge是一个指针,也可以说是一个特殊的变量。它的存放地址是0x601,存放的内容是0x886。

    对比,很容易发现,指针和变量有很多相似之处;不同之处在于,指针存放的是内存地址。

    再仔细观察一下,可以发现:指针pAge中存储的地址=变量Age的内存地址。因此也可以说,指针pAge是指向内存单元Age的特殊变量。

    相信说到这里,大家对指针已经有一个直观地认识了。那么我们如何去使用它呢?
    掌握下面四个部分,大家就可以轻松上手指针了:

    1. 声明指针
    2. 使用 & 获取变量地址
    3. 使用指针存储地址
    4. 使用 * 访问指向的数据

    下面我们就来分别学习这四个部分吧!

    (1) 声明指针

    指针作为一种特殊变量,也是需要声明的。我们先回忆下,之前在【C++养成计划】深入浅出——变量作用域(Day3)中,我们是如何声明一个int类型变量的:

    int Age; //声明一个变量Age
    

    在声明指针时,也需要指明类型。而该类型,比如int,对应的是该指针指向内存单元中存储的数的类型。

    int *pAge; //声明一个指针变量
    

    在声明变量时,我们一般会将变量初始化为0。同样,在声明指针时,我们也不希望它指向随机的内存单元。因此会将指针初始化为NULL。

    int Age = 0;
    int *pAge = NULL;
    

    注:初始化为NULL的指针被称为NULL指针或空指针,空指针(即pAge)是一个定义在标准库中的值为零的常量。

    (2) 使用 & 获取变量地址

    符号 & 被称为引用运算符。如果Age表示一个变量,&Age将是存储该变量的内存地址。
    下面,我们举一个简单的例子,来获取变量Age的地址:

    #include<iostream>
    using namespace std;
    int main()
    {
    	int Age=18;
    	cout<<"变量Age存放在内存中的地址是:"<<hex<<&Age<<endl;
    	return 0;
    }
    

    运行后输出结果:
    在这里插入图片描述
    注:程序中的hex,是为了输出的格式为16进制,这是地址表示的一种约定。

    (3) 使用指针存储地址

    我们知道了指针是用于存储内存地址的变量,也知道了如何声明指针以及获取变量的地址。现在就可以将它们关联起来,使用指针来存储 & 获取的地址。
    在这里插入图片描述
    如上图所示,这次我们就可直接通过&Age获取地址,将其传给指针pAge。
    下面举一个小例子,来声明和初始化指针。

    #include<iostream>
    using namespace std;
    int main()
    {
    	int Age = 18;
    	cout<<"变量Age存放在内存中的地址是:"<<hex<<&Age<<endl;
    	int *pAge = &Age;
    	cout<<"指针pAge中存放的地址是:"<<hex<<pAge<<endl;
    	return 0;
    }
    

    运行结果:
    在这里插入图片描述
    可见,变量Age存放在内存中的地址=指针pAge中存放的地址。说明引用运算符 & 取到了Age的内存地址,并传给了指针pAge。

    (4) 使用 * 访问指向的数据

    符号 * 也被称为解除引用运算符。通常,只要是合法的指针pAge,我们就可以通过 *pAge 访问指针pAge包含的地址处存储的值。(注意是访问地址所对应的值,而不是地址)
    下面,举一个简单的例子:

    #include<iostream>
    using namespace std;
    int main()
    {
    	int Age = 18;
    	cout<<"Age = "<<Age<<endl;
    	int *pAge = &Age;
    	cout<<"*pAge = "<<*pAge<<endl; 
    	return 0;
    }
    

    运行结果:
    在这里插入图片描述
    简单来说,指针pAge是指向变量Age对应的内存单元的,所以通过符号 * 就可以获得Age对应的值。


    这里是一段防爬虫文本,请读者忽略。 本文原创首发于 CSDN,作者【AI 菌】。
    博客首页:https://blog.csdn.net/wjinjie
    本文链接:https://ai-wx.blog.csdn.net/article/details/107509243
    未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!


    2. 动态内存分配

    为了帮助开发者更好地管理应用程序占用的内存,C++提供了两个运算符:new和delete。指针是包含内存地址的变量,在高效分配内存方面扮演了重要的角色。

    (1) new/delete动态分配和释放内存

    当你使用new来分配内存块时,如果成功,new将返回指向一个指针,指向一个分配的内存,否则将引发异常。
    使用new为一个int类型的数分配内存:

    int *pNum = new int;
    

    使用new为多个元素分配内存:

    int *pNums = new int[10];
    

    注:new请求分配内存时,并不能保证请求总能得到满足,因为这取决于系统的状态以及内存资源的可用性。

    使用new分配的内存最终都需要使用对应的delete进行释放:

    1. 对于一个元素的情况:
    int *pNum = new int;  //分配内存空间
    -----程序块-----
    delete pNum;  //释放内存空间
    
    1. 多个元素的情况
    int *pNums = new int[10];  //分配内存空间
    -----程序块-----
    delete[] pNUms;  //释放内存空间
    

    注:不再使用分配的内存后,一定要通过delete释放,否则可能出现内存泄漏。

    下面举一个简单的例子:开辟一个内存空间来存放年龄,在输出存储年龄的内存地址后,再释放分配的内存空间。

    #include<iostream>
    using namespace std;
    int main()
    {
    	int *pAge = new int;
    	cout<<"请输入您的年龄:";
    	cin>>*pAge;
    	cout<<"存储年龄的内存地址是:"<<hex<<pAge<<endl;
    	delete pAge;
    	return 0;
    }
    

    运行结果:
    在这里插入图片描述

    (2) 带关键字const的指针

    在前面【C++养成计划】深入浅出——变量作用域(Day3)中讲过,将变量声明为const时,变量的取值在整个生命周期内固定为初始值。这种变量的值是不能修改的。
    指针也是变量,因此也可以将const用于指针。const指针有以下三种:

    1. 指针指向的数据为常量,不能修改。但可以修改指针包含的地址,即指针可以指向其他地方。
    1.指针指向的数据为常量
    int Age = 18;
    const int *pAge = &Age;  //不能更改pAge指向的数据Age的值
    
    2. 想将Age改为20,错误的做法
    *pAge = 20;  //错误
    
    3. 正确的做法
    int CopyAge = 20;
    pAge = &CopyAge; //可改变指针指向的地址
    
    1. 指针包含的地址是常量,不能修改,单可修改指针指向的数据。
    int Age = 18;
    int* const pAge = &Age;
    *pAge = 20; //做法正确,可以改变值
    
    1. 指针包含的地址以及他指向的值都是常量,因此都不能修改。
    int Age = 18;
    const int* const pAge = &Age;
    

    (3) 指针 VS 数组

    当我们声明一个数组时,比如下面这样:

    int Array[10];
    

    编译器将分配固定的内存,用于存储10个整数。同时向你提供一个指向数组中第一个元素的指针。换句话说,Array是一个指针,指向第一个元素Array[0]。下面程序演示了这种关系:

    #include<iostream>
    using namespace std;
    int main()
    {
    	int Array[10]={0,1,2,3,4,5,6,7,8,9};
    	int *pNum = Array;
    	cout<<"*pNum = "<<*pNum<<endl;
    	cout<<"Array[0] = "<<Array[0]<<endl; 
    	return 0;
    }
    

    运行结果:
    在这里插入图片描述
    由此可见,数组名是一个指针,且指向第一个元素。

    3. 使用指针时的常见错误

    (1) 内存泄漏

    如果在使用new动态分配的内存不再需要后,开发者没有及时使用delete释放内存的话,这些内存就会被预留并分配给你的应用程序。这将减少可供其他应用程序使用的系统内存量,甚至会降低应用程序的执行速度,这就是所说的内存泄漏
    比如下面这样:

    int* pNums = new int[10];
    -----程序块-----
    //忘记进行delete[]
    

    忘记对已经请求分配的内存进行delete,很容易造成内存泄漏,我们在使用new请求分配动态内存时,一定要注意这个。

    (2) 无效指针

    使用运算符 * 对指针解除引用,以访问指向的值时。务必确保该指针指向了有效的内存单元,否则程序很可能崩溃。
    导致指针无效的原因很多,但都要归结于糟糕的内存管理。这里仅介绍两种常见的引起指针无效的情形:

    1. 声明指针过程中,没有将其初始化为NULL,并且在后面也没有对指针赋以有效的地址。
    2. 使用new为指针申请动态内存时,当内存需要量特别大时,可能分配不成功,导致无效指针。比如下面这样:
    int* pNums = new int[10000000000]; //申请的内存量太大,可能导致分配不成功
    

    由于水平有限,博客中难免会有一些错误,有纰漏之处恳请各位大佬不吝赐教!

    在这里插入图片描述
    推荐文章

    展开全文
  • 8年经验面试官详解 Java 面试秘诀

    万次阅读 多人点赞 2019-11-19 17:31:49
    本人目前在一家知名外企担任架构师,而且最近八年来,在多家外企和互联网公司担任Java技术面试官,前后累计面试了有两三百位候选人。在本文里,就将结合本人的面试经验,针对Java初学者、Java初级开发和Java开发,给...
     
    640?wx_fmt=gif

    640?wx_fmt=jpeg

     

    作者 | 胡书敏
    责编 | 刘静
    出品 | CSDN(ID:CSDNnews)
    本人目前在一家知名外企担任架构师,而且最近八年来,在多家外企和互联网公司担任Java技术面试官,前后累计面试了有两三百位候选人。在本文里,就将结合本人的面试经验,针对Java初学者、Java初级开发和Java开发,给出若干准备简历和准备面试的建议。
     
    640?wx_fmt=png
    Java程序员准备和投递简历的实战技巧
     
    1.1 简历中应包含的要素,一个都别落下
    为了让简历更吸引技术面试官或其它相关筛选简历的人,大家在准备简历应当注意“直接”两字:能让筛选人能直接地看出本人的教育背景、工作经历和项目经理,并让他们“直接”感到这份简历能纳入考虑范围。
    根据这个原则,大家可以按次序在简历中列出如下表所给出的要素。
    简历中应包含的要素
    目的
    基本信息,比如姓名,性别,年龄,目前所在城市,是否在职,手机和电邮等。
    1 让招聘方了解候选人的基本信息。
    2 以便招聘方通过手机等方式能联系到候选人。
    按时间倒叙写教育背景,一般只需要包含高中以上,初中高中等不必写,但需包含专业和学历学位信息。
    用专业和学历学位等信息向招聘方证明自己的技术背景。
    总结性地列出自己所掌握的技能。比如:
    1 有3年Java经验,有2年Spring MVC经验。
    2有3年Oracle经验,有2年Oracle调优经验。
    等等
    一般这些总结点是和职务需求是一致的,这样能让招聘方直接地感受到该候选人的匹配度。
    在这基础上,可以适当列些能成功帮到自己的总结点。
    按倒叙列出工作过的公司,并列出在这些公司里的项目经验,这部分的技能下文会详细描述。
    在项目经验描述里,能通过项目用到的技术经验等,具体地给出自己“匹配”该岗位的证明。
    可以列出和应聘岗位相关的培训经历和得到过的奖励
    这些属于加分项,同等情况下能优先录用
    用少量篇幅列出自己的兴趣和自我总结
    让招聘公司进一步了解候选人
    1.2 该如何描述公司的工作情况
    这部分一般是按时间倒叙描述,比如可以按如下的格式写:
    2015年11月到2017年10月,在xx公司,职务是Java高级开发。离职理由是想进一步发展。
    2012年2月到2015年11月,在xx公司,职务是Java初级开发。离职理由是想进一步发展。
    按此格式写之前的公司情况
    这部分的内容应当尽量靠前,在罗列公司情况时,请大家注意如下的四个要点。
    第一,工作情况可以和项目经验分开写,一般会在后继的项目经验里写具体用到的技术框架以及所做过项目的细节,在这里的工作情况描述里,可以不用过于复杂,让招聘方看到你之前的公司情况即可。
    第二,尽量别出现长时间的“空白期”,比如上份工作是2月份结束的,而下份工作是6月开始的。如果出现持续三个月以上的“不在职状态”,需要在简历中说明情况,比如这段时间你是换城市发展了,或辞职复习考研或复习考公务员,总之得找个能说得过去的理由。
    第三,在简历上,尽量别让人感觉你每份工作都做不长,但不能以此作假。比如我见过有候选人会合并公司,比如2016年11月到2017年3月在A公司,2017年4月到10月在B公司,他为了不让招聘方感觉他换工作太频繁,在简历上就写2016年11月到2017年10月在B公司工作,而故意合并了A公司的经历。这样的话,如果遇到背景调查,会露馅,即使有些公司不做调查,在劳动手册等材料上也能反应出真实的工作情况,所以这种做法有一定的风险性。
    这里推荐的做法是,不要合并公司,但可以写明理由,比如当时小王是被外派公司A以人力派遣的形式外派到B公司,但没过多久A公司因某种原因不再具备人力派遣的资质了,这时小王就不得不终止与A公司的合同转而和B公司签约,这样虽然看上去小王是换了公司,但实际上没有。通过类似的合理解释,招聘方就不再会质疑小王的工作能力和稳定性了。
    第四,可以写上合适的离职理由,尤其当你短时间里换工作比较多,可能引起招聘方的质疑的情况里,更该考虑些合适的理由。
    合理的离职理由可以是,想为自己提供一个更大的发展空间,或想通过升级来独当一面,以此进一步提升自己的能力,或公司因资金等方面的原因倒闭了。总之,这不是我主观上不稳定,而是由于客观原因导致我不得不换工作。
    而可能会导致没面试机会的离职原因是,待遇问题(虽然大家心知肚明,但不能这样写),或无法承受大压力,或同事领导排挤。这类理由往往会暴露出候选人的缺点,所以不建议大家采用。从这意义上来讲,“合同期满”也不是一个好的离职原因,因为如果候选人能力强,那么为什么原公司不和你续约呢?
    总之,在描述公司情况时,一旦出现会让招聘方感觉你能力不强或不稳定时,一定得醒目地写上足以信服的理由,这样你的简历才会有机会被继续被读下去,进而你才会有技术面试的机会。
    1.3 尽量把学习培训项目和毕业设计项目往商业项目上靠
    商业项目是指能挣钱的项目,和它对应的就是些不以挣钱为目的的学习项目或毕业设计项目。正因为客户付了钱,所以商业项目的要求要远远高于学习或毕业设计项目,这也是为什么招聘公司会看重商业项目而会主动过滤学习项目的原因。
    比如小张在大三时帮计算机系的王老师所在的ABC软件公司干了半年的活,如果小张在简历上写:“在校期间,从x年x月到x年x月完成了xx系统,用到了xx技术”,那么这多半会被当成类似于课程设计的学习经验,但如果再加上如下关键性的描述:“这个系统是属于xx公司的xx商业项目里的一部分,我和另外三位开发人员做了半年,最终这个系统成功上线并在客户xx公司的环境里投入运营”,那这样小张的商业项目总年限里就能加上这半年时间了。
    又如小李在做毕业设计时,花了7个月的时间参与了导师的一个电商商业项目,他主要的工作是设计一个调度算法,但也参与了一些诸如订单管理模块的工作。如果他就平淡地写一句,毕业设计是xx,毕业论文是xx,那么招聘方看过就算了,也不会认为小李在做毕业设计时还有过商业项目经验,这样小李未免有些吃亏。
    但如果这样写:“在x年x月到x年x月的7个月里,在毕业设计中,我参与了xx公司的xx电商项目,客户方是x,我参与了订单管理和xx模块,并设计了其中的调度算法,在我的毕业论文里,详细介绍了这种做法”。文字没修改太多,但足以让小李增加7个月的商业项目经验。
    我们发现大多数初级程序员的水平其实也差不多,这时就得看谁的商业项目经验丰富了。比如有次我们无法从两位候选人中权衡,因为他们的综合条件和面试情况都差不多,但其中有一位在大三阶段有段为期6个月的商业项目实习经验,另一位没有(也有可能他也有但没当成商业项目来写),这种情况下我们就录用了有实习经验的候选人了。
    1.4 描述项目的技巧
    我们可以根据职位需求,从如下几个方面来描述项目经验。
    第一,简要描述项目的背景,比如时间范围,客户是谁,项目规模有多大。如下是范例。
    从x年x月到现在(这个时间范围至少是最近半年),我参与某外汇交易系统,客户是xx银行,这个项目组的构成是,1位项目经理外加10位开发,总共的规模大概在80个人月左右。
    第二,大致描述项目的需求和包含哪些模块,然后简要说下你做了哪些模块,同时说下在这个项目用到的开发工具和主要技术点,这部分的描述如下所述。
    这个外汇交易系统包括挂盘撮合成交、实盘成交、反洗钱和数据批处理等模块,我主要负责了挂盘撮合成交模块,其中用到了Spring MVC架构,数据库是Oracle,用Mybatis实现的ORM,该系统是运行发布在Weblogic服务器上,我们还用了Nginx来实现负载均衡,用Redis来缓存数据。在这个项目里,我还用到了JS实现了一些前台页面。
    第三,这里可以结合职位的需求,描述JD里要求的技术在项目里是如何用的。同样这里也应围绕技术,而别多写业务细节
    1.5 在简历中描述项目时可以添加的亮点
    我们见过不少简历,在描述项目时,也能像上文一样,能根据招聘职位的具体要求展示出自己的匹配点,这种简历属于“达标”,即可以纳入考虑范围。在这个基础上,如果大家在项目里有下表列出的亮点,一定请写上,这就是大家优于别人的地方。
    1. 数据库和JVM调优;
    2. 你理解的框架底层代码;
    3. 项目里用到的设计模式;
    4. 项目管理和部署工具;
    5. 结合若干案例,讲述你分析和解决bug的技能;
    6. 其它能帮助到你的加分项,比如工期紧,用到新技术等。
    1.6 哪些简历可以通过筛选
    从面试官角度来看,除了学历等硬件条件外,如果简历满足如下的4点要求,就一般能有面试机会了。
    1. 商业项目足量,且其中包含的技能和职位介绍很匹配;
    2. 最近用到的技能和职位介绍很匹配;
    3. 没有过长职业空白期或不稳定等情况;
    4. 一定请记住,公司只能通过简历认识到你,简历上没写清楚等同于你不行。
    其实这就是我们写简历的方向,而且,在针对具体公司投递简历时,还可以以此为目标,微调简历。
     
    640?wx_fmt=png
    面试时该如何讲解技术项目赢得面试官好感
     
    2.1 别害怕,因为面试官什么都不知道
    面试官是人,不是神,拿到你的简历的时候,是没法核实你的项目细节的(一般公司会到录用后,用背景调查的方式来核实)。更何况,你做的项目是以月为单位算的,而面试官最多用30分钟来从你的简历上了解你的项目经验,所以你对项目的熟悉程度要远远超过面试官,所以你一点也不用紧张。如果你的工作经验比面试官还丰富的话,甚至还可以控制整个面试流程(笔者在面试方面成精后也经常干这种事情,大家一定也能行)。
     
    面试官
    对你以前的项目和技能
    很了解
    只能听你说,只能根据你说的内容做出判断
    在面试过程中的职责
    在很短的时间内防守成功即可
    如果找不出漏洞,就只能算你以前做过
    准备时间
    面试前你有充足的时间准备
    一般在面试前用30分钟阅读你的简历
    沟通过程
    你可以出错,但别出关键性的错误
    不会太为难你,除非你太差
    技巧
    你有足够的技巧,也可以从网上找到足够多的面试题
    其实就问些通用的有规律的问题
    既然面试官无法了解你的底细,那么他们怎么来验证你的项目经验和技术?下面总结了一些常用的提问方式。
    提问方式
    目的
    让你描述工作经验和项目(极有可能是最近的),看看你说的是否和简历上一致
    看你是否真的做过这些项目
    看你简历上项目里用到的技术,比如框架、数据库,然后针对这些技术提些基本问题
    还是验证你是否做过项目,同时看你是否了解这些技术,为进一步提问做准备
    针对某个项目,不断深入地问一些技术上的问题,或者从不同侧面问一些技术实现,看你前后回答里面是否有矛盾
    深入核实你的项目细节
    针对某技术,问些项目里一定会遇到的问题,比如候选人说做过数据库,那么就会问索引方面的问题
    通过这类问题,核实候选人是否真的有过项目经验(或者还仅仅是学习经验)
    2.2 面试时的错误表现
    在面试过程中,如果候选人出现如下的表现,那么很有可能过不了面试,请大家注意。
    1. 面试时介绍的项目时间等情况简历上写的不一致,这就有简历造假的嫌疑;
    2. 介绍项目时只介绍业务,忽略技术。因为面试官只关心技术,不关心业务;
    3. 对于提到的技术,连最基本的问题也回答不上,这就说明候选人这项技术没掌握;
    4. 说得太流利或太磕磕巴巴,这就说明在背词或者是表达有问题。
    2.3 面试中介绍项目的范例
    第一步,介绍项目基本情况
    可以这样说,这个项目是xx产品的xx模块的,有xx和xx模块,我做了xx模块,用了半年,我的组里一共有5个人。这里可以谈下业务,但别深入,因为面试官不熟悉,也不想熟悉候选人的业务,这块时间控制在1分钟之内。
    第二步,介绍项目里关键技术和管理方式
    可以这样说,这个项目里,我用到了Spring框架,用到nginx等组件,项目管理用Maven,部署用jenkins,静态扫描用Sonar,任务管理和bug管理用jira,平时采用敏捷的项目迭代方式,每天有站会,大约1月一个迭代版本。这块可以根据自己的情况来介绍,时间也别太长,估计用1分钟也就够了。
    第三步,结合业务讲用到的技术,但别展开
    比如有个职位介绍,里面写到需要有数据库优化的经验,那么可以说,项目里xx模块,我用到MyCat作为分库分表,(不展开技术),上线后,数据库能承受住每秒2000个并发请求(说下用好的结果)。
    又如一个JD里说要用到微服务技术,那么就可以说,项目里用到了Spring Cloud框架,用到了Ribbon,Eureka等组件,容器是Docker。用好以后,在发布时会发现,各模块之间的调用耦合性大大降低。
    2.4 介绍项目时的要点归纳
    从上述介绍项目的范例中,可以归纳出相关要点如下。
    1. 面试前,需要阅读职位介绍,挖掘用过的技能要点,然后尽可能地在介绍项目里提到这些技能关键字;
    2. 在介绍项目里,结合业务,提到职位介绍里的技术,因为一旦技术结业业务,就说明你有过相关技术的实践经验,而不是仅仅只会理论;
    3. 别过多介绍业务,多抛出职位介绍里的关键字。还是这句话,面试官不关心业务,你提到业务只是以此证明你在实践中用过相关技术而已;
    4. 此时还在项目介绍阶段,别过多展开技能,你抛出技能关键字后,面试官自然会问的。而一旦你过多展开技术,那么面试官就有可能感觉到你思路不清晰。
     
    640?wx_fmt=png
    Java面试者该准备哪些加分项技能
     
    3.1框架是重点,但别让人感觉你只会山寨别人的代码
    一般工作在3年内的候选人,大多仅仅是能“山寨”别人的代码,也就是说能在现有框架的基础上,照着别人写的流程,扩展出新的功能模块。比如要写个股票挂单的功能模块,是会模仿现有的下单流程,然后从前端到后端再到数据库,依样画葫芦写一遍,最多把功能相关的代码点改掉。
    如果单纯使用SSM框架,大多数项目都会有痛点。比如数据库性能差,或者业务模块比较复杂,并发量比较高,用Spring MVC里的Controller无法满足跳转的需求。所以我一般还会主动问:你除了依照现有框架写业务代码时,还做了哪些改动?
    我听到的回答有:增加了Redis缓存,以避免频繁调用一些不变的数据。或者,在MyBitas的xml里,select语句where条件有isnull,即这个值有就增加一个where条件,对此,会对任何一个where增加一个不带isnull的查询条件,以免该语句当传入参数都是null时,做全表扫描。或者,干脆说,后端异步返回的数据量很大,时间很长,我在项目里就调大了异步返回的最大时间,或者对返回信息做了压缩处理,以增加网络传输性能。
    对于这个问题,我不在乎听到什么回答,我只关心回答符不符逻辑。一般只要答对,我就会给出“在框架层面有自己的体会,有一定的了解”,否则,我就只会给出“只能在项目经理带领下编写框架代码,对框架本身了解不多”。
    其实,在准备面试时,归纳框架里的要点并不难,我就不信所有人在做项目时一点积累也没,只要你说出来,可以说,这方面你就碾压了将近7成的竞争者。
    3.2 别单纯看单机版的框架,适当了解些分布式
    在描述项目里框架技术时,最好你再带些分布式的技术。下面我列些大家可以准备的分布式技术。
    1. 反向代理方面,nginx的基本配置,比如如何通过lua语言设置规则,如何设置session粘滞。如果可以,再看些nginx的底层,比如协议,集群设置,失效转移等;

    2. 远程调用dubbo方面,可以看下dubbo和zookeeper整合的知识点,再深一步,了解下dubbo底层的传输协议和序列化方式;

    3. 消息队列方面,可以看下kafka或任意一种组件的使用方式,简单点可以看下配置,工作组的设置,再深入点,可以看下Kafka集群,持久化的方式,以及发送消息是用长连接还是短拦截。

     
     
     
    以上仅仅是用3个组件举例,大家还可以看下Redis缓存,日志框架,MyCAT分库分表等。准备的方式有两大类,第一是要会说怎么用,这比较简单,能通过配置文件搭建成一个功能模块即可,第二是可以适当读些底层代码,以此了解下协议,集群和失效转移之类的高级知识点。 
    3.3 数据库方面,别就知道增删改查,得了解性能优化
    在实际项目里,大多数程序员用到的可能仅仅是增删改查,当我们用Mybatis时,这个情况更普遍。不过如果你面试时也这样表现,估计你的能力就和其它竞争者差不多了。
    这方面,你可以准备如下的技能:
    1. SQL高级方面,比如group by, having,左连接,子查询(带in),行转列等高级用法;

    2. 建表方面,你可以考虑下,你项目是用三范式还是反范式,理由是什么?

    3. 尤其是优化,你可以准备下如何通过执行计划查看SQL语句改进点的方式,或者其它能改善SQL性能的方式(比如建索引等);

    4. 如果你感觉有能力,还可以准备些MySQL集群,MyCAT分库分表的技能。比如通过LVS+Keepalived实现MySQL负载均衡,MyCAT的配置方式。同样,如果可以,也看些相关的底层代码。

     
     
     
     
    哪怕你在前三点表现一般,那么至少也能超越将近一般的候选人,尤其当你在SQL优化方面表现非常好,那么你在面试高级开发时,数据库层面一定是达标的,如果你连第四点也回答非常好,那么恭喜你,你在数据库方面的能力甚至达到了初级架构的级别。
    3.4 Java核心方面,围绕数据结构和性能优化准备面试题
    Java核心这块,网上的面试题很多,不过在此之外,大家还应当着重关注集合(即数据结构)和多线程并发这两块,在此基础上,大家可以准备些设计模式和虚拟机的说辞。
    下面列些我一般会问的部分问题:
    1. String a = "123"; String b = "123"; a==b的结果是什么?这包含了内存,String存储方式等诸多知识点;

    2. HashMap里的hashcode方法和equal方法什么时候需要重写?如果不重写会有什么后果?对此大家可以进一步了解HashMap(甚至ConcurrentHashMap)的底层实现;

    3. ArrayList和LinkedList底层实现有什么差别?它们各自适用于哪些场合?对此大家也可以了解下相关底层代码;

    4. volatile关键字有什么作用?由此展开,大家可以了解下线程内存和堆内存的差别;

    5. CompletableFuture,这个是JDK1.8里的新特性,通过它怎么实现多线程并发控制?

    6. JVM里,new出来的对象是在哪个区?再深入一下,问下如何查看和优化JVM虚拟机内存;

    7. Java的静态代理和动态代理有什么差别?最好结合底层代码来说。

     
     
     
     
     
     
     
    通过上述的问题点,我其实不仅仅停留在“会用”级别,比如我不会问如何在ArrayList里放元素。大家可以看到,上述问题包含了“多线程并发”,“JVM优化”,“数据结构对象底层代码”等细节,大家也可以举一反三,通过看一些高级知识,多准备些其它类似面试题。
    3.5 Linux方面,至少了解如何看日志排查问题
    如果候选人能证明自己有“排查问题”和“解决问题”的能力,这绝对是个加分项,但怎么证明?目前大多数的互联网项目,都是部署在Linux上,也就是说,日志都是在Linux,下面归纳些实际的Linux操作。
    1. 能通过less命令打开文件,通过Shift+G到达文件底部,再通过?+关键字的方式来根据关键来搜索信息;

    2. 能通过grep的方式查关键字,具体用法是, grep 关键字 文件名,如果要两次在结果里查找的话,就用grep 关键字1 文件名 | 关键字2 --color。最后--color是高亮关键字;

    3. 能通过vi来编辑文件;

    4. 能通过chmod来设置文件的权限。

     
     
     
     
    当然,还有更多更实用的Linux命令,但在实际面试过程中,不少候选人连一条linux命令也不知道。还是这句话,你哪怕知道些很基本的,也比一般人强了。 
    3.6 通读一段底层代码,作为加分项
    如何证明自己对一个知识点非常了解?莫过于能通过底层代码来说明。我在和不少工作经验在5年之内的程序员沟通时,不少人认为这很难?确实,如果要通过阅读底层代码了解分布式组件,那难度不小,但如果如下部分的底层代码,并不难懂。
    1. ArrayList,LinkedList的底层代码里,包含着基于数组和链表的实现方式,如果大家能以此讲清楚扩容,“通过枚举器遍历“等方式,绝对能证明自己;

    2. HashMap直接对应着Hash表这个数据结构,在HashMap的底层代码里,包含着hashcode的put,get等的操作,甚至在ConcurrentHashMap里,还包含着Lock的逻辑。我相信,如果大家在面试中,看看而言ConcurrentHashMap,再结合在纸上边说边画,那一定能征服面试官;

    3. 可以看下静态代理和动态代理的实现方式,再深入一下,可以看下Spring AOP里的实现代码;

    4. 或许Spirng IOC和MVC的底层实现代码比较难看懂,但大家可以说些关键的类,根据关键流程说下它们的实现方式。 

     
     
     
     
    其实准备的底层代码未必要多,而且也不限于在哪个方面,比如集合里基于红黑树的TreeSet,基于NIO的开源框架,甚至分布式组件的Dubbo,都可以准备。而且准备时未必要背出所有的底层(事实上很难做到),你只要能结合一些重要的类和方法,讲清楚思路即可(比如讲清楚HashMap如何通过hashCode快速定位)。
    那么在面试时,如何找到个好机会说出你准备好的上述底层代码?在面试时,总会被问到集合,Spring MVC框架等相关知识点,你在回答时,顺便说一句,“我还了解这块的底层实现”,那么面试官一定会追问,那么你就可以说出来了。
     
    640?wx_fmt=png
    预估面试题,准备对应的回答
     
    4.1 哪些问题面试中大概率会被问到
    在面试里,不管如何引导面试官,其实如下方面的问题很大可能会被问到,所以在面试前可以提前准备。
    1. 职位介绍里提到的技能要点,比如职位介绍里有提到Mybatis,那么面试官一定会问相关问题;

    2. 你在项目介绍时抛出的技术关键字,比如你在面试过程中介绍项目时提到了Redis,那么在介绍完项目后,面试官就会问,“你项目里是如何使用Redis的?”,类似的,简历中你写的技术,也有可能会被问到;

    3. Java核心,数据库,Spring框架,项目管理等基础问题,这些就不用说了,不过如果你引导得当的话,面试官会花费很多时间问你提到的技术,这块会问得比较少;

    4. 必要的算法题,比如排序等,其实面试官感觉你技术可以的话,这块就不怎么会问了,但准备的时候需要看这个,有备无患。

     
     
     
     
    4.2 面试官提问的方式
    以上介绍了常见问题的种类,这里介绍下面试官常用的提问方式。
    • 问用法,比如直接提问,项目里你netty怎么用的?这块大家可以结合项目准备说辞;

    • 问流程,比如结合业务,讲下nginx负载均衡的用法?这也可以结合项目和网上搜到的资料准备说辞;

    • 问原因,比如为什么要用netty?这块就要结合项目说明了;

    • 问技术点, 比如netty里零拷贝怎么回事?对此,需要对简历上提到的每个技术点,以及面试过程中将要提到的每个技术点,搜相关面试问题,并结合业务说明;

    • 问基础知识,比如finally从句的用途,这就可以通过刷题来获取了。

     
     
     
     
     
    4.3 举例说明该如何准备面试问题
    下面给出准备问题的技巧。
    • 斟酌面试时抛出的技能,逐一准备说辞;

    • 针对技术,网上搜索问题,比如搜Spring IOC面试题,结合网上的参考答案准备说辞;

    • 准备技术的实施要点,比如做了哪些配置文件,你在项目里踩过哪些坑?

    • 最好结合底层代码说明。

     
     
     
     
    如下给出两个例子,先以MyCat分库分表为例,给出介绍说辞的技巧。
    • 准备业务背景,为什么要用?比如我们项目数据库并发压力大,需要用MyCat作为分库分表;

    • 如何使用,无非是设置分库规则,改写SQL语句等;

    • 准备下踩到的坑,比如自增长主键在每台机器上都要保证唯一;

    • 然后再结合些底层代码,准备下一条SQL语句是如何分发到对应的分库上的,然后执行好以后又如何返回的;

    • 再可以准备些只有做过才知道的细节,比如发布上线和清洗数据的流程;

    • 网上找些MyCAT的面试题,准备相关说辞。

     
     
     
     
     
     
    一般说到了这里,面试官就不怎么问了,哪怕你后面再被问倒,面试官也会感觉你MyCat很熟悉。
    下面以Netty为例,给出相关技巧。
    • 结合业务需求点,说下为什么要用这个技术,怎么用的,以及用了有什么好处? 比如为了优化网络通讯协议,所以用基于TCP协议的Netty,业务模块里的xxx功能是用到netty;
    • 准备下踩到的坑,比如在某业务场景里,我遇到了半包粘包问题,我是通过调试底层代码解决的;
    • 用了Netty对项目的帮助。比如Netty是基于TCP协议的,它要比Http协议要轻,所以通讯性能高,且Netty内部的Reactor线程模型对系统的IO帮助很大;
    • 基于零拷贝、读写索引和异步处理机制,准备些底层代码,在面试里说明;
    • 顺带再准备下Netty的组件,工作流程等问题,这能搜到问题和相关说辞。
     
     
     
     
     
    在讲的时候,大家甚至可以边画Netty流程图,再结合底层代码说明,这样面试官一定会对大家刮目相看。
    其实这里仅仅是抛砖引玉,或者提到的技术比较高深 ,但可以讲述的技术还可以是线程池,MyBatis组件,Redis,甚至是虚拟机优化等。哪怕是初级开发,也能多少抓住一两个点,按上述思路说明。
     
    640?wx_fmt=png
    面试时如何不被面试官牵着鼻子,自我把控面试的走向?
     
    5.1 在介绍项目时,引导话题的技巧以及案例
    在做项目介绍的时候,你可以穿插说出一些你的亮点,但请记得,不论在介绍项目还是在回答问题,你当前的职责不是说明亮点而是介绍项目,一旦你详细说,可能会让面试官感觉你跑题了。
    所以这时你可以一笔带过,比如你可以说,“我们的项目对数据要求比较大,忙的时候平均每小时要处理几十万条数据”,这样就可以把面试官引入“大数据”的方向。
    你在面试前可以根据职位的需求,准备好这种“一笔带过”的话。比如这个职位的需求点是Spring MVC框架,大数据高并发,要有数据库调优经验,那么介绍以往项目时,你就最好突出这些方面你的实际技能。
    再给大家举个例子,比如Java虚拟机内存管理和数据库优化是绝大多数项目都要遇到的两大问题,大家都可以在叙述项目经验时说,在这个项目里,我们需要考虑内存因素,因为我们的代码只允许在2G内存环境中运行,而且对数据库性能要求比较高,所以我们经常要监控优化内存和数据库里的SQL语句。这样当面试官深入提问时,就能抛出自己准备好的虚拟机内存优化和数据库优化方面的说辞。
    或者说,在项目介绍时提到,在xx模块里,我们使用了nginx做负载均衡,达到了承受百万级并发的效果,从而引出nginx的话题。
    实在不行,你也可以说“我除了做开发,也做了了解需求,测试和部署的工作,因为这个项目人手比较少,压力比较大”,这样你也能展示你有过独挡一面的经历。
    5.2 以Netty为例,讲述引出值钱话题的技巧
    比如在介绍项目时,我提到了Netty技术,如果面试官没打断,我就问,能否介绍其中的Netty细节?得到允许后再说。
    或者把技术关联到面试官可能会问的问题上,比如问及网络通讯时介绍Netty,这个事先整理一个问题列表,遇到此类问题,顺带抛出Netty说辞。
    问题列表可以是,项目里你用到哪些组件?用到哪些通讯协议?如何进行模块间的交互等等,然后先回答问题本身,再扩展到Netty。但请记住,别自说自话,因为过犹不及,其它技术照此办理
    5.3 以案例说明,在回答问题时引出准备过话题的技巧
    比如面试官问你Spring相关问题,假设问到,你对Spring依赖注入了解多少,在说好Spring相关问题后再提一句,我们同时用Spring,以低耦合的方式整合了MyCAT组件,从而达到了分库分表的效果,这样就引出了分库分表的话题 。
    或者在介绍Netty流程后,再说一句,在实际项目里,我们还遇到了因Netty底层代码而导致的OOM问题,对此,我们组负责排查和解决问题,这样就自然而然地引出了OOM内存溢出的问题。
    或者在介绍完线程相关问题时,再提一句,在项目里,我们用到了线程池来管理线程,这样就引出了高并发的话题。
    但在引导的时候,请注意如下的三点。
    第一,面试官不接口的,应当立即停止,再说下去就属于自说自话了。
    第二,还是要准备必要的基础问题,还是要刷题,还是要准备各种说辞,因为面试前的全面准备,是引导的基础。
    第三,应当引导面试官问些“框架”和“性能调优”等值钱方面的技能,这样才能最大程度地展示你的能力,同样,此类问题需要面试前准备。
    5.4 你可以引导的加分项
    在如下的一些表格里,归纳的加分项甚至初级开发多少也能准备,其中涵盖了诸多方面。
    表 Java Core方面可以准备的亮点
    技术方面
    可以说的亮点
    Java集合对象
    1 能根据项目的需求选用合适的集合对象,比如知道ArrayList和LinkedList的差异,并能合理选用。
    2 能在合适的场合选用WeakHashMap。
    3 可以适当讲一些集合的JDK底层实现代码。
    异常处理方面
    能在finally从句里写释放资源的代码
    JDBC方面
    1 能通过PreparedStatement的预处理方法来防止SQL注入。
    2 能通过批处理来提升操作性能。
    3 能通过实例讲述事务隔离级别的含义
    多线程方面
    1 会结合项目使用线程池
    2 能通过锁或信号量等手段正确地处理多线程并发时的数据一致性。
    3 熟悉各种并发组件

    表 数据库方面可以准备的亮点

    技术方面
    可以说的亮点
    建表
    建表时需要根据项目的数据情况,考虑是采用三范式或是反范式。
    SQL调优
    1 可以通过查看日志等方式看哪些SQL需要调优。
    2 可以通过执行计划查看SQL的所消耗的代价,并据此调优。
    3 可以通过建索引,建分区等手段来优化SQL性能。
    事务
    1 可以说下JDBC或Spring里是如何管理事务的。
    2 可以说下Spring里的声明式事务的做法和优点。
    3 可以举例说明事务隔离级别和事务传播机制的用法。
    分布式数据库
    1 可以通过MyCAT进行分库分表,从而减轻对单表访问所需要的代价。
    2 可以通过集群等方式来承担对数据库的过量的访问请求。
    NoSQL和Hadoop
    这两个本身就是个亮点,如果大家用过,可以结合项目来说明。

    表Java Web框架方面可以准备的亮点

    技术方面
    可以说的亮点
    Spring MVC/Boot架构
    1 可以说下Spring的IOC和AOP是如何优化项目结构的。
    2 可以说下拦截器等Spring组件对项目的帮助。
    3 可以说下Spring Boot对项目的帮助
    ORM,比如Mybatis
    使用这种ORM技术时,如何优化访问和操作数据库的性能。
    Spring和Mybatis等的整合
    可以讲下整合框架的细节,并可以举例说明整合后的框架能很好地适应需求的变更。

    表 分布式组件方面可以准备的亮点

    技术方面
    可以说的亮点
    组件应用
    1 结合配置文件等,说明怎么用的
    2 结合业务,说下具体的效果,比如限流后有什么好处。
    如何应对高并发的场景
    1 如何达到负载均衡
    2 如何进行失效转移
    定位排查和解决问题
    1 如何通过分析log定位问题
    2 问题的根源和解决方法
    健康检查和线上监控
    1 如何通过健康检查确定系统正常运行
    2 出了问题,如何发出警告

    由此大家能看到,其实很多事先可以准备的点,其实是你没有想到,但你项目里一定用过。你据此准备,在通过上述技巧在面试中合理地找机会说出来,你面试成功的可能性一定会大大增加。

     
    640?wx_fmt=png
    总结,面试准备后,结果可能就大不同
     
    先从面试官的角度看下,哪些人能面试成功?
    1. 最近半年的项目经历和JD匹配度很高;
    2. 通过面试,JD上的技能候选人大多能掌握;
    3. 候选人在Java核心,数据库和框架方面的基础技能达标;
    4. 不是刺头,团队合作没问题,没有其它大问题。
    但如果大家面试前不准备,或者准备不到位,那么就会面临如下的后果了:
    1. 简历未必能过筛选,甚至没有面试机会;
    2. 无法证明项目里用到的技术和JD高度契合;
    3. 介绍项目经验时没问题,把提问的主动权交给面试官;
    4. 不知道将会问哪些问题,所有问题都现场想;
    5. 在面试现场,没法让面试官全面了解你的技术亮点。
    但如果按照上述方法准备,大家很大程度上能得到如下的收获。
    1. 能通过微调简历,得到更多的面试机会;
    2. 能通过挖掘项目经验,证明自己的技能和JD契合;
    3. 能知道哪些属于值钱技能,并能结合业务准值钱技能和调优技能的说辞,而且能不露痕迹地展示;
    4. 不仅限于coding,更能展示项目管理(sonar等),linux,项目部署(nginx)等方面的技能;
    5. 知道面试大致会问哪些问题,并由此能事先准备;
    6. 能事先尽可能多地挖掘亮点,并在面试时展示。
    这就是大家阅读本文后的收获,最后感谢大家看完本文。
    作者简介:胡书敏,知名外企资深架构师,8年内面试过数以百计的Java工程师,5年的Java培训讲师经验,帮助众多初学者成功拿到心仪的Offer。著有《Java核心技术及面试指南》。CSDN博客专家:https://blog.csdn.net/sxeric
    声明:本文为作者原创投稿,未经允许请勿转载。

    【End】

    0基础学习python,每天5分钟,进步一点点

    https://edu.csdn.net/topic/python115?utm_source=csdn_bw

    640?wx_fmt=jpeg

    热 文 推 荐 

     

    展开全文
  • 面试官:怎么做JDK8的内存调优?

    万次阅读 多人点赞 2020-08-03 10:23:15
    看着面试官真诚的眼神,心中暗想看起来年纪轻轻却提出如此直击灵魂的问题。擦了擦额头上汗,我稍微调整了一下紧张的情绪,对面试官说:
  • 我是真的没想到,面试官会这样问我ArrayList。
  • 面试官:你说你懂i++跟++i的区别,那你会做下面这道题吗? 面试官:“说一说i++跟++i的区别” 我:“i++是先把i的值拿出来使用,然后再对i+1,++i是先对i+1,然后再去使用i” 面试官:“那你看看下面这段代码,运行...
  • 字节跳动这家公司,应该是所有秋招的公司中,...大部分情况下,面试官都会问一个不怎么难的问题,不过你千万别太开心,因为这道题往往可以拓展出更多有难度的问题,或者一道题看起来很简单,但是给出最优解,确实很...
  • 小伙子之前问了你这么多Redis的知识,你不仅对答如流,你还能把各自场景的解决方案,优缺点说得这么流畅,说你是不是看过敖丙写的《吊打面试官》系列呀? 惊!!!老师你怎么知道的,我看了他的系列根本停不下来啊...
  • 【Java面试官】史上最全的JAVA专业术语面试100问

    万次阅读 多人点赞 2019-11-20 14:24:10
    各位看点赞再看,养成好习惯(●´∀`●) gitee上已经开源https://gitee.com/Li-Ren/blog里面有一线大厂面试点脑图,欢迎Star和PR你认为重要的知识点。 前言: 面试技巧另外开篇再说,先上面试干货吧。 面试...
  • (提到MQ又是一整条的知识链路,什么异步、削峰、解耦等等,所以面试,我们还是不打没有把握的胜仗) 絮叨 另外,敖丙把自己的面试文章整理成了一本电子书,共 1630页!目录如下 现在免费送给大家,在我的公众号三...
  • 在面试中,三次握手和四次挥手可以说是问的最频繁的一个知识点了,我相信大家也都看过很多关于三次握手与四次挥手的文章,今天的这篇文章,重点是围绕着面试,我们应该掌握哪些比较重要的点,哪些是比较被面试官给问...
  • 太惨了,面试又被吊打
  • Java面试官最爱问的volatile关键字

    万次阅读 多人点赞 2019-11-08 10:49:37
    在Java的面试当中,面试官最爱问的就是volatile关键字相关的问题。经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用volatile关键字作为...
  • 面了三次了,总算虐了一次面试官
  • 如果你不会看源码,请耐心看下去 一、我的真实经历 标题是我2019.6.28在深圳某500强公司面试时候面试官跟我说的话,即使是现在想起来,也是觉得无尽的羞愧,因为自己的愚钝、懒惰和自大,我到深圳的第一场面试便栽了...
  • 吊打面试官

    千次阅读 2019-12-05 23:30:27
    在一个寂寞难耐的夜晚,我痛定思痛,决定开始写面试相关的文章,希望能帮助各位读者以后面试势如破竹,对面试官进行360°的反击,吊打问你的面试官,让一同面试的同僚瞠目结舌,疯狂收割大厂Offer!...
  • 这里可以看到,阿里的面试官并不会像有一些公司一样拿着题库一道一道的问,而是会根据面试者做过的事情以及面试过程中的一些内容进行展开。 面试官:那你能说说什么是索引吗? 我:(这道题肯定难不住我啊)...
  • 作为技术面试官,我在面试时考虑什么?

    万次阅读 多人点赞 2019-10-21 09:43:17
    作为一个面试官,我们也需要这样,我也会经常总结和反思自己的面试技巧,现分享如下,希望求职者能有反向的思维,下次面试成绩能更好。 机会是留给有针对性准备的人的 我觉得是这是最重要的一点,很多人可能觉得同伴...
  • 如何做好一个面试官

    千次阅读 多人点赞 2020-02-20 21:42:56
    找到了我17年写的一篇关于面试官的思考,没有发在csdn上,经过三年的时间,今天看来某些想法不够成熟,所以整理修改后重新发出来。 首先声明一点,我没怎么面试过别人,只是参加过几场面试,经历的面试官只有小20个...
  • 面试官问你HTTP状态码,你敢答吗?

    万次阅读 多人点赞 2020-04-03 17:02:16
    听说面试会问HTTP状态码,相比那些神仙操作,这个记忆性的知识点准备起来比较容易,不应该成为丢分项。
  • java面试官如何面试别人

    千次阅读 2018-02-20 15:11:26
    java面试官如何面试别人(一)  java面试官的“面试心得”  在公司当技术面试官几年间,从应届生到工作十几年的应聘者都遇到过。先表达一下我自己对面试的观点:  1.笔试、面试去评价一个人肯定是不够准确的,...
  • 100道Java高频面试题(阿里面试官整理)

    千次阅读 多人点赞 2021-02-25 00:59:00
    这次只放出九十道,剩下10道准备找认识的几位面试官去要,希望不会被拒绝。 这些题我认为认真思考还是很有价值的,答案整理中,全部整理好,我会做出PDF,整理好后大家公众号后台回复“Java面试题”领取。 Java高频...
  • 面试官:怎么做JDK8的垃圾收集器的调优?

    万次阅读 多人点赞 2020-08-11 09:39:32
    如此浅显的回答,无法让面试官达到深入的要求,肯定不能满足面试官强烈的需求,果不其然面试官又追问到:
  • 昨天有个小伙伴去阿里面试实习生岗位,面试官问他了一个老生常谈的问题:你说一说 Java 创建线程都有哪些方式? 这哥们心中窃喜,这个老生常谈的问题早已背的滚瓜烂熟,于是很流利的说了出来。 Java 创建线程有两种...
  • 文章目录作为面试官被放鸽子的50个理由,论如何放面试官的鸽子不能帯饭的不去面试公司福利少不去太远的不去-路上时间浪费太多周围没商业街买外卖的不去面试公司门牌不好找不去太近的不去(怕家里人查岗)每次开会听...
  • 听我讲完GET、POST原理,面试官给我倒了杯卡布奇诺

    万次阅读 多人点赞 2020-04-19 19:22:55
    毕业后他去了上海而我开始北漂,但每次过节回老家我俩都会和朋友们一起吃饭,这次回家过年也不例外,我们朋友几个去了枣庄出名的小板凳酱骨头,饭后他给我们聊了聊4年前来这家公司的面试经历,据说跟面试官有着...
  • 面试官:有如下场景:在多线程并发情况下,有一个共享变量,不同线程设置不同值后,各线程只想获取自己设置的值,如何实现?小小白:使用ThreadLocal,...
  • 面试官:请你谈谈Java的类加载过程

    万次阅读 多人点赞 2018-02-01 00:08:28
    刚刚走出校门的应届毕业生,如果在去寻求一份Java开发的工作时,你的面试官很有可能一边看着你的简历,一边漫不经心地问你:了解过Java类的加载过程吗? 这个时候你一定要注意了,虽然这是一个老生常谈的问题,但是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 74,807
精华内容 29,922
关键字:

面试官