实习面试经历

2020.3.17 15:38 - 16:35 「57 分钟」阿里一面

电话面。自己第一次面试,面得很糟糕,面对问题很慌…

整个面试分三块进行:

  1. 基础知识,主要问了计网和数据库以及算法
  • TCP 的三次握手和四次挥手过程

腾讯的面试官大大也问了这个问题,并且特别提醒了我要注意记住 tcp 连接和断开时客户端和服务器端的状态,真是超级感谢啊,这点原来还真的一直没有注意过。

首先我们来回顾一下 TCP 的数据传输单元,TCP 传送的数据单元称为报文段。一个 TCP 报文段分为 TCP 首部和 TCP 数据两部分,整个 TCP 报文段都封装在 IP 数据报中的数据部分,TCP 首部长度是4的整数倍,其中有固定的20个字节,剩余的可变动的就是选项和填充「最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。」,20个固定的字节包括了源端口号(2 字节)、目的端口(2字节)、seq序列号(4字节)、确认号ack(4字节)、以及确认位ACK 等等。

其次,我们来详细讲解一下三次握手、四次挥手的过程:

  • 三次握手

image-20200318195601159

首先,在三次握手建立连接的阶段,是不会传输 TCP 报文段的,传输的是 传输控制块(TCB),传输控制块 TCB(Transmission Control Block)存储了每一个连接中的一些重要信息,如:TCP 连接表,指向发送和接收缓存的指针,指向重传队列的指针,当前的发送和接收序号等等。

  1. 最开始的 Client 和 Server 都是处于 Closed,由于服务器端不知道要跟谁建立连接,所以其只能被动打开,然后监听端口,此时 Server 处于 Listen 状态;
  2. 而 Client 会主动打开,然后构建好 TCB 「SYN= 1,seq = x」,发送给服务器端,此时 Client 会将状态置为 SYN_SEND 「同步已发送」;
  3. 服务器端收到客户端发来的同步请求后,会将状态置为 SYN_RECV「同步已接收」,同时会构建好 TCB「SYN = 1,seq = y,ACK = 1,ack = x + 1」发送给客户端;
  4. 客户端接收到了服务器端发来的传输控制块之后,会将自己的状态改为 ESTABLISHED「建立连接」,然后发送确认报文(ACK= 1,seq = x + 1,ack = y + 1);
  5. 服务器端在收到了客户端发来的报文之后,也将状态置为 ESTABLISHED「建立连接」,至此,三次握手结束,当然在这里,可以带 tcp 报文段信息过来了,因为此时客户端已经可以保证是可靠的传输了,所以在这一端可以发送报文段了。

几个问题:

  1. 为何不直接在第一次握手就带上报文段消息,非要第三次才可以带?

因为 TCP 是要保证数据的不丢失且可靠,如果在第一次就带上报文段消息,此次建立连接很有可能就会失败,那么就不能保证数据的不丢失了,在不可靠的机制上进行这种操作,换来的代价太大,每次发送报文段的资源也会增大,得不偿失;

而第三次握手的时候,客户端已经知道服务器端准备好了,所以只要告诉服务器端自己准备好了就okay了,所以此时带上报文段信息没有任何问题。

  1. 可不可以只握手两次?

肯定是不可以的,三次握手主要是解决这样一个常见的问题,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。

如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
————————————————
版权声明:本文为CSDN博主「小书go」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qzcsu/article/details/72861891

  • 四次挥手

image-20200318195630545

  1. 最开始客户端和服务器端都是 ESTABLISHED 的状态,然后客户端会主动关闭,而服务器端则是被动关闭。
  2. 客户端发送一个 FIN 报文段,seq = 结束的报文段序号 + 1「假设为 u」,告诉服务器端,客户端需要关闭了,此时将客户端的状态变为 FIN-WAIT-1,等待服务器端的反馈;
  3. 服务器在接收到了客户端发来的 FIN 包之后,会发一条 ack报文反馈给客户端,其中报文中包括 ACK = 1,seq = v,ack = u+1,告诉客户端收到了客户端要关闭的消息了,同时服务器端会通知应用进程需要关闭连接了,并将自己的状态置为 CLOSE-WAIT;
  4. 由于服务器端可能还有一些数据没处理完,所以需要一段时间的等待,当处理完了之后,会再发一条报文,其中 FIN = 1,ACK = 1,seq = w,ack = u+1,告知客户端,服务器端现在可以关闭了,并将服务器端的状态由 CLOSE-WAIT 变为 LAST-ACK;
  5. 客户端在收到了服务器端发来的消息之后,会发一条ack报文「ACK = 1,seq = u+1,ack = w+1」回去,告知服务器端,客户端已经知道了你准备好关闭了,此时会将客户端的状态由 FIN-WAIT-2 置为 TIME-WAIT,在两个最长报文段传输时间过后,会自动将客户端的状态由 TIME-WAIT 置为 CLOSED。
  6. 服务器端收到消息之后,就将状态由 LAST-ACK 置为了 CLOSED,自此,四次挥手全部结束。

一个很常见的问题,为何不能三次挥手呢?

  • 首先如果去掉最后一次挥手,那么服务器端就不知道自己要关闭的报文有没有传输成功,可能半路上就失败了,但是此时客户端不知道,导致客户端一直在等待服务器关闭,但是此时服务器端直接就关闭了;
  • 如果中间的两次挥手合并,那是肯定不行的,因为此时服务器端可能还有很多报文未处理完,此时直接关闭肯定会对传输有很大影响。

为什么客户端在收到 服务器端发来的 FIN 包后要等 2 个最长报文段传输时间?

防止最后自己发去的 ack 没传送到服务器,如果服务器没收到客户端的 ack,肯定会选择重发一次 FIN 包,那么此时如果客户端已经关闭了,客户端就不能再发 ack 确认收到了,至于为何是 2 个报文段传输时间,因为刚好一去一回嘛… 2 个最长报文传输时间没有 FIN 包发来,就说明服务器已经关闭了,客户端也就可以安心关闭了。

这文章整挺好:https://blog.csdn.net/qzcsu/article/details/72861891

  • http 和 tcp 的区别

tcp 是传输层协议,http是应用层协议,http在传输层就是使用的 tcp。

  • 排序算法有哪些

这个简单。排序算法分为比较算法和非比较算法,其中比较算法包括交换排序「冒泡和快排」、选择排序「简单选择排序和堆排序」、插入排序「直接插入排序、希尔排序」、归并排序「二路归并和多路归并」,非比较排序有计数排序、桶排序、基数排序。 「公式: 不稳定的有:快些选堆」

  • 冒泡排序。稳定的,平均时间复杂度为 O(n²),最好时间复杂度那肯定就是一次循环 O(n),最坏时间复杂度为 O(n²)。空间复杂度 O(1)。
  • 快速排序。不稳定,平均时间复杂度为O(nlogn),最好的时间复杂度为O(nlogn),最坏就是选定的基准值在最边上,这样就是O(n²),注意哦,快排的空间复杂度平均是 O(logn),最差 O(n)。
  • 简单选择排序。不稳定,平均、最好、最坏时间复杂度都为O(n²)。空间复杂度 O(1)。
  • 堆排序。不稳定,平均、最好、最坏的时间复杂度为O(nlogn)。空间复杂度 O(1)。
  • 直接插入排序。稳定。最好O(n),平均、最坏时间复杂度O(n²)。空间复杂度 O(1)。
  • 希尔排序。不稳定。最好O(n),平均O(n1.3),最坏肯定是O(n²)。空间复杂度O(1)。
  • 归并排序。稳定。最好、最坏、最差时间复杂度O(nlogn),空间复杂度O(n)。
  • 计数排序。稳定,空间换时间。适合数比较集中在一起的,这样k就少了,时间复杂度为 O(n+k),空间复杂度也为O(n+k)。「个人还是觉得其实空间复杂度为O(k),因为我可以把值放回去的时候可以放到原数组上,所以是O(k)。」
  • 桶排序,桶越多,时间复杂度很简单,为O(n+k),空间复杂度最坏为O(n+k),其中 n 是因为桶内部所有元素得排序, k 是指桶的数量。
  • 基数排序,时间复杂度O(n*k),k为最大数的位数,空间复杂度为O(n)。
  • 堆排序的稳定性,如何实现堆排序,具体细节

这个很简单,就不详细说了。

  • 归并排序的稳定性,如何实现归并排序,具体细节

简单。

  • 说一下jdk自带的排序用到了哪些排序算法,展开讲一下
  1. Arrays.sort() & Collections.sort()
  2. JDK中的自带的排序算法实现原理精彩总结

jdk层面实现的sort总共是两类,一个是 Arrays.sort(), Collections.sort();

  1. Arrays.sort()

    1. 如果数组内元素是基本数据类型,最主要采用的是双轴快速排序「其实就是三路快排一模一样的思路,只不过三路快排中间是 = pivot1,而双轴快速排序是(pivot1,pivot2),具体戳链接:https://www.cnblogs.com/nullzx/p/5880191.html

      总结就是数组长度小于47的时候是用直接插入算法,大于47并且小于286是采用双轴快速排序,大于286如果连续性好「也就是元素大多有序,有一个flag专门用来记录数组元素的升降次数,代表这个数组的连续性」采用的是归并排序,否则还是依旧采用双轴快速排序。

    2. 如果数组内元素是对象,采用的是TimSort.sort(),跟 Collections.sort()一样,都是采用的这个函数,这是归并排序算法和插入排序的结合。

  2. Collections.sort(),采用 TimSort.sort()。

TimSort.sort() 大概原理:

  1. 当待排序元素小于32个时,采用二分插入排序,是插入排序的一种改进,可以减少插入排序比较的次数。当找到插入位置时,直接利用System.copy()函数即可。
  2. 当待排序元素大于等于32个时,进行归并排序(和传统的归并不一样),首先将排序元素分区,每个分区的大小区间为[16,32),然后依次对每个分区进行排序(具体实现依然是二分插入排序),排完序的分区压入栈(准确的说不是栈,而是一个数组,用来记录排序好的分区),当栈内的分区数满足条件时,进行分区合并,合并为一个更大的分区,当栈中只剩一个分区时,排序完成。
  • mysql如何优化

建索引

  • 索引的建立的原则有哪些

除了运用最左前缀、索引下推、考虑索引长度,还有哪些是建立索引需要考虑的 「这个实在想不到了」

  • 红黑树和平衡二叉树的区别,各自的优势特点,以及红黑树如何进行添加数据「具体说一下旋转过程,我只说了我博客上写了,具体的给忘了…」

这个二面真得复习复习。

  1. java方面
  • 讲一下双亲委派模型,为什么要设计双亲委派模型

双亲委派模型:一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委托,直到顶层的启动类加载器。如果父类加载器能够完成类加载,就成功返回,如果父类加载器无法完成加载,那么子加载器才会尝试自己去加载。

好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系

这种双亲委派模式的设计原因:可以避免类的重复加载,另外也避免了java的核心API被篡改。

  • 违反双亲委派模型,我不小心说了jdbc,然后问我jdbc是如何连接到数据库的,具体流程是什么,我就说了个反射…没复习到位

见我写的 《从双亲委派模型到jdbc》

  • 讲一下jmm,为何这样设计

java memeory model ,java 内存模型,设计的目的是屏蔽掉各种硬件和操作系统之间的差异性,实现让 Java 在各种平台下都能能到一致的并发效果。jmm 中,分为主内存和工作内存,其中每个线程拥有自己的工作内存,而主内存是所有线程共享的。这里我遇到疑问了,在周志华老师那本书中,先是讲主内存中存储了所有的变量,那必然就包括了线程的局部变量,那难道我线程使用自己的局部变量,也要从主内存中拷贝一份副本到工作内存中呢?这是不是和说主内存是共享区域产生了矛盾呢?书中还说可以类比,主内存就是跟堆差不多,而工作内存类似于栈,那之前说的主内存存储了所有的变量,这句话是不是有问题呢?个人觉得主内存不可能存储所有的变量…应该就是类似于堆存储共享变量…

image-20200319233302589

  • 为何要有工作内存,有了主内存和工作内存不是更麻烦啊,要不断的复制移动数据,为何不能直接对主内存操作「这个也没答上来」

这就跟为何要提出寄存器和缓存一样的道理,如果所有的操作都在内存中完成,那速度实在是太慢了,只有工作在寄存器和缓存中,速度才能让人满意,而这里的主内存就类比为内存,工作内存就类比为寄存器和缓存。

  • 什么是线程安全 「多线程方面一个问题都没问…血亏」

多个线程访问一个对象,无需考虑环境和额外同步,调用这个对象的行为就能得到正确的答案,就说明这个对象是线程安全的。

  • 举三个例子分别描述 jmm的三个特性「原子性、有序性、可见性」导致的线程安全问题

不遵循原子性:volatile 变量的自加,复合操作,导致线程不安全;

不遵循有序性:比如共享变量,多个线程同时访问,不按序,每个都拷贝一份到自己的工作内存,必然会导致线程不安全;

不遵循可见性:普通变量,跟有序性一样的例子,每个都从主内存拷贝一份变量的副本到工作内存,必然会导致线程不安全。

  • 讲一下 RunTimeException 的造成的原因「非检查型异常」,并说一下为什么不处理 RunTimeException?

RuntimeException是Exception子类。而Exception还有其它类型的异常,我们统一称为非Runtime异常。RuntimeException的特点是非检查型异常,也就是Java系统中允许可以不被catch,在运行时抛出。而其它定非运行时异常如果抛出的话必须显示的catch,否则编译不过。

RuntimeException常见异常:

1 NullPointerException,空指针异常。

2 NumberFormatException,字符串转化成数字时。

3 ArrayIndexOutOfBoundsException, 数组越界时。

4 StringIndexOutOfBoundsException, 字符串越界时。

5 ClassCastException,类型转换时。

6 UnsupportedOperationException,该操作不支持,一般子类不实现父类的某些方法时。

7 ArithmeticException,零作为除数等。

8 IllegalArgumentException,表明传递了一个不合法或不正确的参数

运行时出现错误,说明你的代码有问题,程序已经无法继续运行,所以对RuntimeException的处理时不必要的。之所以不处理,目的就是要是程序停止,修正代码。

  1. 项目
  • 主要就问了下我最近在做什么项目,到什么阶段了,有多少人用;
  • 问我爬虫的实现「面试官貌似没有做过这方面的东西」
  • 我提了一嘴 xxl-job,面试官应该也没用过,就没有深究,本来还想讲讲kafka的,结果直接没问…白准备了

最后就问了一个很常见的算法问题:

256M 的内存如何对 16g的数组进行排序

多路归并,因为没要求存储,只要求了内存,可以多路归并,加入每个元素都是 1M,则可以最多分成 256 组,然后进行归并。

具体描述:采用外部排序,先将16 g数组分成 256 M 一组,然后分别读入内存进行内部排序「比如说可以使用快排」,将这些组内元素全部排好序之后,然后运用败者树和置换-选择排序,进行多路归并,即可。

这里其实可以引申出好多问题:

  1. 海量数据 求最大的 K个数问题,如何解决?
  • 按位划分区域,可以尽快的缩小范围,比如最高位 0 分一堆,1 分成一堆而且不用排序,这是第一选择。
  • 最经典的方法当然是 堆 了,比如要求前1000个最大的数,那就直接建一个 1000 大小的小根堆,然后遍历,只要发现后面的数比小根堆的根节点大,就把根节点和该数交换,重新调整堆,遍历完之后,堆中的数自然就是最大的 1000 个数了;
  • 当然能使用堆排序的前提是内存中要能够放得下这个 K,如果放不下呢?那就只能外部排序了,排序完之后拿到第 K 大的数即可,当然排序前可以和方法一搭配一下。
  1. 海量数据求中位数,如何解决?
  1. 可以按照位来分组,比如说最高位是0的一组,是 1 的一组,这样可以统计出那一组更少,这样就排除了一大半,然后继续这样排查,最终缩小范围后直接内部排序。
  2. 直接外部排序,然后取中间值,最笨的方法。
  1. 在海量数据中找出出现频率最高的前k个数,例如,在搜索引擎中,统计搜索最热门的10个查询词;在歌曲库中统计下载最高的前10首歌等。
  1. 如果重复率很高,可以采用前缀树,因为 trie 树适用于数据量大,重复多,但是数据种类小必须得可以放入内存;
  2. 按照 hash 进行分组,这样就能避免相同的数分到不同区域去了,导致不好统计。hash 分组完毕后,然后用前缀树 或者 hashmap 来计算每个组的前 k 个频率最高的数,最后对各个组的前 k 个数进行统计即可。
  1. 给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?

这里我们把40亿个数中的每一个用32位的二进制来表示
假设这40亿个数开始放在一个文件中。

然后将这40亿个数分成两类:
1.最高位为0
2.最高位为1
并将这两类分别写入到两个文件中,其中一个文件中数的个数<=20亿,而另一个>=20亿(这相当于折半了);
与要查找的数的最高位比较并接着进入相应的文件再查找

再然后把这个文件为又分成两类:
1.次最高位为0
2.次最高位为1

并将这两类分别写入到两个文件中,其中一个文件中数的个数<=10亿,而另一个>=10亿(这相当于折半了);
与要查找的数的次最高位比较并接着进入相应的文件再查找。
…….
以此类推,就可以找到了,而且时间复杂度为O(logn)。

大概统计一下,海量数据求 TopK 的普遍方法:
  • 最快的不需要排序就能排除一大堆的数据的方法就是看 “位”,比如最高位为 0 的分一块,为 1 的分一块,这样迅速就分出一大块不需要的了,尤其适合找中位数,等分的差不多了就可以进行内部排序了。
  • 堆排序,适用于求海量数据最大 K or 最小的 K 个数
  • 分治hash,适用于那些内存很小,数据很大,但是又想求最大的 K 个众数的问题,可以先 hash 到很多个组,然后在组内部使用 hashmap 或者 前缀树 「google等字符」,取到组内前 K 个众数,最后进行组间比较久okay了;
  • 当然不能忘了万能法,那就是外部排序,然后再进行相应的处理。

最后的最后,让我们再来做个附加题:

先来看几个比较常见的例子

  • 字处理软件中,需要检查一个英语单词是否拼写正确
  • 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上
  • 在网络爬虫里,一个网址是否被访问过
  • yahoo, gmail等邮箱垃圾邮件过滤功能

这几个例子有一个共同的特点: 如何判断一个元素是否存在一个集合中?

这里必须介绍一下 bitmap 这个方法了,例如我要从海量数据中找一个数是否出现过,就可以用位图的思路去做,如果数字是 7 ,那就在第 7 位 置 1,如果该位置已经是 1 了,那就代表出现过,不用更改。

如果问题变为从海量数据中找一个数是否出现过一次,那这个时候就得用 2 bitmap 来表示了,也就是一个数如果出现一次,置为 01 ,出现过两次,置为 10,然后再出现,都是10,这个时候如果我们只用一位,是不能表示出出现的次数的。

至于我们常说的布隆过滤器,其实也就是在bitmap之前进行一个hash,例如将字符串进行hash成数组,然后使用位图,解决这类问题。

2020.3.18 09:28 - 11:12 「105 分钟」 腾讯一面

视频面。本次面试分为两大块:

  1. 牛客网写代码 「LeetCode easy难度」,这个阶段大概 5-10 分钟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
翻转数列
小Q定义了一种数列称为翻转数列:
给定整数n和m, 满足n能被2m整除。对于一串连续递增整数数列1, 2, 3, 4..., 每隔m个符号翻转一次, 最初符号为'-';。
例如n = 8, m = 2, 数列就是: -1, -2, +3, +4, -5, -6, +7, +8.
而n = 4, m = 1, 数列就是: -1, +2, -3, + 4.
小Q现在希望你能帮他算算前n项和为多少。


输入描述
输入包括两个整数n和m(2 <= n <= 109, 1 <= m), 并且满足n能被2m整除。

输出描述
输出一个整数, 表示前n项和。



示例1
输入
8 2
输出
8
  1. 正式进入面试 「100分钟」

腾讯看来的确全部是 c++,面试官也是说基本上都是 c++,没有专门搞 java 的组,所以大家 java 投腾讯还是务必慎重,最开始问我的技术栈是什么,c++是否了解,用的比较多?得知我说基本没咋用过之后,就开始尝试问计算机网络方面的问题。

  • 问的很深入,比如说三次握手四次挥手,客户端服务器端各自的状态是什么,对整个建立连接和关闭连接的具体流程是什么,深入到 OSI 模型去讲;

这个我在上面已经详细说过了…

  • 然后主要问的是 Socket 编程,讲套接字编程的具体实现流程,代码如何写,如何实现类似于 Nginx 的多服务器的 Socket 编程;

preview

  • 然后谈到 select、epoll等相关 c++ 的知识,我是在 java 层面去讲的如何实现的 「NIO」;

​ 嘿嘿嘿,这个没问题了,可以看我的最新的文章 —《零拷贝及其周边》

  • 再者就是聊到数据库,这个是必问的,问我索引如何建立、如何优化索引,然后是一些具体问题对索引的分析;

用 key or index 建立索引,优化索引的方法:尽量复用索引,利用好最左前缀和索引下推原则,尽量减少回表次数,利用覆盖索引。

  • 然后谈到事务的四个特性,如何实现原子性、隔离性、一致性、持久性的,内部机制具体如何实现

原子性:利用 undolog,回滚机制,完成要么全部成功,要么全部失败;

隔离性:主要是靠一致性视图+当前行的 row_id_transaction,来完成的。

一致性:主要靠加锁防止事务冲突,一致性是另外三个的顶层,只要他们三完成了他才有可能完成,还有mvcc的加持,以及 undolog、redolog。

持久性:redolog保证crash-safe,bin-log保证归档。

  • 借此谈到 undolog、redolog、binlog,以及 mvcc 的实现;

  • Redolog 到底是保存在磁盘中的还是在内存中?

redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。

详细分析 Mysql 中的三个日志:redolog、undolog、binlog

  • 谈一谈 mysql 的运行机制,整个运行过程是怎样的,如何处理的;

这个简单,略

  • mysql的索引实现,B+的优点等等;

简单,略

  • 全程谈 一致性问题 谈的很多,包括了 mysql 主从复制的一致性如何保证,我说不太清楚,但是借此讲了 kafka 中的高可用机制「ISR」,以及 kafka中的 ack 机制和 kafka 中的消息语义「如何保证数据的一致性」;

Mysql 保证主从一致性:

主库接收到客户端的更新请求后,执行内部事务的更新逻辑,同时写binlog。

备库B跟主库A之间维持了一个长连接。主库A内部有一个线程,专门用于服务备库B的这个长连接。一个事务日志同步的完整过程是这样的:

  1. 在备库B上通过change master命令,设置主库A的IP、端口、用户名、密码,以及要从哪个位置开始请求binlog,这个位置包含文件名和日志偏移量。
  2. 在备库B上执行start slave命令,这时候备库会启动两个线程,就是图中的io_thread和sql_thread。其中io_thread负责与主库建立连接。
  3. 主库A校验完用户名、密码后,开始按照备库B传过来的位置,从本地读取binlog,发给B。
  4. 备库B拿到binlog后,写到本地文件,称为中转日志(relay log)。
  5. sql_thread读取中转日志,解析出日志里的命令,并执行。

这里需要说明,后来由于多线程复制方案的引入,sql_thread演化成为了多个线程。

image-20200325005851525

因为语言上还是有很多区别的,在后面又问了几个有关于 c++ 的问题,答得不是很好

  • 虚函数是什么 「没答上来」;

略。

  • 因为看到我博客有些滑动窗口算法,就问了 tcp 滑动窗口底层的代码实现;
  • 进程、线程、协程的区别,我说完之后,又延伸到线程是如何保证同步的,借此谈到了线程安全,然后我自己拓展了 synchronized「详细介绍了锁升级过程」、lock体系、CAS 的实现以及 final 关键字和 ThreadLocal;

协程的应用场景主要在于 :I/O 密集型任务。

这一点与多线程有些类似,但协程调用是在一个线程内进行的,是单线程,切换的开销小,因此效率上略高于多线程。当程序在执行 I/O 时操作时,CPU 是空闲的,此时可以充分利用 CPU 的时间片来处理其他任务。在单线程中,一个函数调用,一般是从函数的第一行代码开始执行,结束于 return 语句、异常或者函数执行(也可以认为是隐式地返回了 None )。 有了协程,我们在函数的执行过程中,如果遇到了耗时的 I/O 操作,函数可以临时让出控制权,让 CPU 执行其他函数,等 I/O 操作执行完毕以后再收回控制权。

简单来讲协程的好处:

  • 跨平台
  • 跨体系架构
  • 无需线程上下文切换的开销
  • 无需原子操作锁定及同步的开销
  • 方便切换控制流,简化编程模型
  • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序:这一点和事件驱动一样,可以使用异步IO操作来解决

最后再贴个图来总结一下,更清楚:

img

作者:程序猿杂货铺
链接:https://juejin.im/post/5d5df6b35188252ae10bdf42
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 进行间通信 IPC 有哪些方式「我只说了信号量、共享区域、管道这几个,面试官也没追问」
  1. 管道。管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据,管道一端的进行顺序的将进程数据写入缓冲区,另一端的进则顺序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后在缓冲区就不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写入或满的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。管道是一种半双工通信方式,数据只能单向流动。需要进行通信时,需要建立2个管道。
  2. 信号量。进程之间通信的机制,例如 Semphore ?
  3. 共享内存。进程的不同虚拟内存映射到用一个物理内存上,实现共享。
  • 内存泄露问题如何排查,主要问linux如何进行排查「没答上来,就说可以用可视化界面」
  • 内存泄露会发生什么情况,系统会假死吗?

最后谈了谈一些数据结构和算法:

  • 两个堆如何实现队列「貌似堆不就是优先级队列嘛…」,两个队列如何实现堆;「面试官表述不清楚,可能就是想指堆栈???」
  • 两个栈如何实现队列,两个队列如何实现栈;
  • 链表如何查找是否有环;
  • 链表如何确定环的起点;
  • 两条链表找公共处的起点。

还有一些细枝末节的问题,印象已经不深了,大概就是这些吧。

总结一下:面试官非常擅长挖掘面试者的优势,对面试者不太懂的全部不问,基本上我会什么就问什么,所以大概面试了半小时后,就开始对着我的博客问,所以整体上给人的感觉是很好的。大家如果要面腾讯的话建议多看看c++,并且对常见的 计网 和 os 的问题尽量往深处走,面试官只看中你对问题的深度,不会的问题他不会追问。

最大的收获:

  1. 面试官对自己方方面面的建议。并且给自己推荐了三本书《unix网络编程卷一》《unix网络编程卷二》《linux内核》

部分拓展

  1. 如果是第一次访问 www.taobao.com,客户端「浏览器」会首先去 dns 服务器查找对应的ip,如果是第一次访问 这个网站,那么首先会去走 http 协议,客户端「浏览器」和服务器的 80 端口进行 tcp 连接,然后 服务器 80 端口会返回一个 301/302「具体区别下文会讲」重定向,在服务器响应头中还会添加 HTTP-Strict-Transport-Security,里面有 max-age,用户访问时,服务器种下这个头,下次如果使用 http 访问,只要 max-age 未过期「客户端检验」,客户端会进行内部跳转,可以看到 307 Redirect Internel 的响应码。然后就直接变成和 443 端口建立连接,走 https 连接。

  2. 如果不是第一次访问,那在拿到ip之后客户端直接校验自己的 max-age字段,如果没过期直接 307 内部跳转,直接和服务器的 443 端口进行 https 的连接了。剩下的就是https的过程了。

    作者:蜗牛的北极星之旅
    链接:https://juejin.im/post/5d8a34ea6fb9a04dfa09561b
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。告诉我们需要建立 https

  • 301、302、307的区别

301重定向是永久的重定向,搜索引擎在抓取新的内容的同时也将旧的网址替换为了重定向之后的网址。

302重定向只是暂时的重定向,搜索引擎会抓取新的内容而保留旧的地址,因为服务器返回302,所以,搜索搜索引擎认为新的网址是暂时的。所以302,会导致劫持问题:A站通过重定向到B站的资源xxoo,A站实际上什么都没做但是有一个比较友好的域名,web资源xxoo存在B站并由B站提供,但是B站的域名不那么友好,因此对搜索引擎而言,可能会保存A站的地址对应xxoo资源而不是B站,这就意味着B站出了资源版权、带宽、服务器的钱,但是用户通过搜索引擎搜索xxoo资源的时候出来的是A站,A站什么都没做却被索搜引擎广而告之用户,B站做了一切却不被用户知道,价值被A站窃取了。这里 A 就相当于是我们在输入框输入的域名,然后对 B 重定向 302,导致自己的域名不会被替换却还享用 B 的资源。

307意思就是客户端内部自己做了重定向,就比如 www.baidu.com ,我在有了 HSTS 的 max-age 之后会自动从 http://www.baidu.com 跳转到 https://www.baidu.com。

  • https 的形成过程

在传输层和应用层中间有个 ssl/tls 层

流程:

  1. 验证过程「具体流程:https://www.jianshu.com/p/b0b6b88fe9fe」
  • 客户端(通常是浏览器)先向服务器发出加密通信的请求,与服务器的443端口建立连接;
  • 服务器收到请求,然后响应,确认加密方法,如 RSA非对称加密,然后将服务器的证书发过去;
  • 客户端验证服务器的证书,然后使用公钥加密发送消息;
  • 服务器用私钥解密,得到明文。
  1. 正常传输

跟正常的http传输一致,只不过是密文传输。

  • 对称加密算法和非对称加密算法的区别

https://blog.csdn.net/u013320868/article/details/54090295

对称加密,加密解密时间更快,但是密钥管理是个大问题;

非对称加密,更安全,但是加密解密时间较长一些。

  • RSA 算法流程

https://zhuanlan.zhihu.com/p/44185847

之所以难解,是因为位数很长,并且去对一个超级大的数进行因式分解,如果没有私钥的话几乎不可能做到。

主要用到的数学知识有欧拉函数、费马小定理等等

  • Mysql 的高可用机制是如何实现的

见下面写的👇

2020.3.26 16:00 - 17:00 「60 分钟」 美团一面

电话面。 跟前两面风格完全不一样!

  • 先问我学过什么科目?

我说计算机网络、操作系统、编译原理、数据结构、计算机组成原理、数据库等等;

  • 对什么科目感兴趣?

我说计算机网络吧

  • 为什么喜欢这门课?

我说因为上课听得懂,这门课的体系也比较清晰,分层来学,循序渐进,分数考得高,有成就感所以就喜欢了。

  • 这门课有哪些让你记忆深刻的地方?

我说 tcp、ip吧,tcp 比较记忆深刻因为他很重要,出现的地方比较多,ip的话应该我们现实生活中也总是提起,所以记忆比较深刻一些。

  • 那我们现实中说到的 ip 和 你这里的 ip 有什么区别?

我说一个是 ip 协议,主要功能是定义IP地址格式,数据包的格式,分组转发规则,而我们现实中的 ip,是指的 ip 地址,这是主机在网络中的标识,一般我们接触的都是 IPv4,即 32 位的地址。

  • 那你讲讲 IPV4 有什么吧 「我自己加的问题」

IPv4 分为网络号和主机号,网络号就是主机或者路由器所连接到的网络的标识,主机号就是主机或者路由器在这个网络内的标识。

  • 既然谈到了路由器,那路由器有 ip 地址吗,路由器又有什么功能呢?

路由器肯定是有 ip 地址的,并且路由器总是有两个或两个以上的 ip 地址,路由器的每一个端口都有一个不同网络号的 ip 地址,因为路由器最主要的功能就是分组转发路由,通过路由表对报文进行相应的转发。

  • 那网关又是什么?和路由器有什么关系呢?

这个真的难…平时还真不会去思考这些问题…

preview

如上图所示,路由器其实就是实现了网关的功能,网关是一个逻辑概念,指的是网络的出口和入口「网络边界」,而路由器则是一个物理概念,实现了网关的功能,是不同网关的沟通桥梁和物理基础。

  • 你说的 ip 协议是什么?属于哪一层?

我们通常使用的协议是 IPv4 协议,属于网络层。

  • 介绍一下 IPv4 协议有哪些内容,然后说说网络层的一些其他协议吧?

首部固定20字节,包括版本,首部长度,源地址,目的地址等等。

网络层的其他协议包括 ARP「地址解析协议,用于IP地址和MAC地址的映射」、NAT「网络地址转换,对外隐藏内部的 ip」、ICMP「网络报文控制协议,允许主机和路由器报告差错和异常情况」、CIDR「子网划分协议,无分类域间路由选择,没有子网概念,但是有用子网掩码」

还要 DHCP,不过这个是应用层协议,基于 UDP,用于给主机动态分配 IP 地址,我们的笔记本突然接入 wifi 获得的 IP 就是 DHCP 协议获取的。

第一段落告终,因为我实在是听不清对面面试官说话,声音太低了,并且由于他使用的公司的 vpn,压根听不清…我所有的注意力基本上都集中在听他说话上了…根本没心思思考问题…莫名的紧张…

然后他换了电话打过来,终于听得清楚了,也终于不用尽全力听他讲话了,于是就不紧张了,然后我们就继续聊了下去。

  • 除了计算机网络,还对哪门课程比较有印象?

数据库吧,自己因为做项目也一直有用。

  • 那你平时用的是什么数据库?

Mysql、mongodb

  • 这两个数据库有什么区别?

一个是关系型数据库,一个是非关系型数据库。

  • 那什么是关系型数据库,什么是非关系型数据库?为什么要分成这两种数据库呢?各自的优势和使用场景在哪呢?

关系型数据库指采用了关系模型来组织数据的数据库,关系模型可以简单的理解为一个二维表,所以里面的字段名称和字段类型都是在建表的时候就确定好了的;

非关系型数据库则是结构不固定,集合内数据字段可以不一样,数据比较松散,一般以键值对的形式存储,比如一般都是json数据直接存储。

适合使用SQL开发的项目:

  • 可以预先定义逻辑相关的离散数据的需求
  • 数据一致性是必要的{acid}
  • 具有良好的开发者经验和技术支持的标准的成熟技术

适合使用NoSQL开发的项目:

  • 不相关,不确定和逐步发展的数据需求
  • 更简单或者更宽松的能够快速开始编程的项目
  • 速度和可扩展性至关重要的

非关系型数据库的优势:

  1. 性能
    NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。

  2. 可扩展性
    同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

关系型数据库的优势:

  1. 复杂查询
    可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
  2. 事务支持
    使得对于安全性能很高的数据访问要求得以实现。

对于这两类数据库,对方的优势就是自己的弱势,反之亦然。

但是近年来这两种数据库都在向着另外一个方向进化。例如:
NOSQL数据库慢慢开始具备SQL数据库的一些复杂查询功能的雏形,比如Couchbase的index以及MONGO的复杂查询。对于事务的支持也可以用一些系统级的原子操作来实现例如乐观锁之类的方法来曲线救国。
SQL数据库也开始慢慢进化,比如HandlerSocker技术的实现,可以在MYSQL上实现对于SQL层的穿透,用NOSQL的方式访问数据库,性能可以上可以达到甚至超越NOSQL数据库。可扩展性上例如Percona Server,可以实现无中心化的集群。

虽然这两极都因为各自的弱势而开始进化出另一极的一些特性,但是这些特性的增加也会消弱其本来具备的优势,比如Couchbase上的index的增加会逐步降低数据库的读写性能。所以怎样构建系统的短期和长期存储策略,用好他们各自的强项是架构师需要好好考虑的重要问题。

作者:陈鼎星
链接:https://www.zhihu.com/question/24225007/answer/81501685
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 那你讲讲 mysql 中你印象深刻的地方吧
  1. 第一个,对 mysql 支持的 RR 隔离级别非常的印象深刻,竟然可以做到修改了但不去读这种隔离级别;

  2. 还有就是 Mysql 的高可用机制;「必须疯狂转入自己熟悉的地方啊」

  3. Mysql 的 Write Ahead Logging 也是一个很大的特点,有去使用 redolog、undolog、binlog;
  4. Mysql 的锁也是一个很大的特点,里面有丰富的锁,跟 juc 下的锁有的一拼,甚至更丰富;
  5. 还有就是 Mysql 中的索引,能提高检索速度。

『机会来了就要把握住,这种问题是最适合展现自己的学习深度』

  • 那你分别讲讲这五个吧

其实面试官并没有让我讲这五个,只是让我讲讲 rr 级别如何实现的,但是为了复习,我还是把这五个再串一遍吧。

  • 先讲第一个,mysql 如何实现的 RR 隔离级别

主要是采用了事务的一致性视图,和当前行的一个 row_tranc_id,根据一致性视图里面的低水位和高水位和 row_tranc_id 进行比较,判断是否需要用 undolog 拿到上一个值,undolog 在这里就是实现 mvcc 的基础。这里有一个值得注意的地方,就是如果 select 是不加悲观锁的去读,没有问题,是rr级别的读取,但是如果 select 显式的加锁,比如说加了行锁中的读锁「在语句最后加 in shared mode」或者写锁「for update」,这样跟 update 一样强制去进行一个加锁,导致只能去当前读,此时 mvcc 是失效的。

  • 再讲第二个,mysql 的高可用机制是如何实现的?

这里我在腾讯面试部分也有提到,但是腾讯那部分主要侧重讲了主从一致是如何实现的,而高可用则是建立在主从一致的基础上的。

先上一个自己画的图:

高可用机制

mysql 的高可用的实现,基础就是能够做到主备切换,而主备切换的前提是要保证主从的一致性,这样才能切换,不然肯定会影响数据,但是要保证主从一致性,就会带来主从延迟的情况,具体的主从一致性的实现可以看我上面讲的,主从延迟可能会有图中那些原因,针对不可避免的主从延迟,主备切换有两种策略:

  1. 可靠性优先策略。具体步骤是等延迟小于某个数时,例如 5 秒,将主库 A 改成只读状态,然后再看主从延迟「seconds_behind_master」,等其变为 0 后,将备库 B 改成可读写状态,然后把业务切换到备库 B 上,这个策略的好处就是能保证主从的完全一致性,但是会有少量时间导致系统处于不可写的状态,不过影响不大,这也是最常用的策略;
  2. 可用性优先策略。不等主备数据同步,直接把连接切到备库B,并且让备库B可以读写,那么系统几乎就没有不可用时间了。好处就是没有不可用时间,但是容易导致数据逻辑出现问题。
  • 再讲第三个,Mysql 的 Write Ahead Logging

Mysql 中常见的三个日志文件分别是 undolog、redolog以及binlog,redolog 主要是负责 crash-safe,也就是崩溃恢复的工作,binlog主要是做一个归档的作用,redolog和binlog是两阶段提交的,这也是常用的分布式的手段吧,大家都okay了才commit,redolog是可擦除的,存储的是物理日志,但是binlog是顺序写,存储的是逻辑日志,并且 redolog 是 Innodb 独有的,binlog则是 server端有的。undolog 主要是负责 mvcc 和回滚,在数据修改的时候,不仅记录了redo,还记录了相对应的undo,如果因为某些原因导致事务失败或回滚了,可以借助该undo进行回滚。undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。

  • 再讲第四个,Mysql 中的锁

Mysql 从锁的范围上讲分为全局锁、表级锁以及行锁。

全局锁称为 Flash Tables with Read Lock,主要用来全库逻辑备份,这个命令可以使整个库处于只读状态。使用该命令之后,数据更新语句、数据定义语句和更新类事务的提交语句等操作都会被阻塞。如果这个命令在主库操作的话,会导致业务停摆,如果再备库操作的话,会导致备库无法写从主库传来的binlog,造成主备延迟,所以我们很少使用它,一般都是使用mysql自带的 mysqldump 去进行全库逻辑备份,因为 mysqldump 有 mvcc 的支持,所以可以一边备份一边读写,但是这个必须要数据库引擎支持rr这个隔离级别,像 MyISAM 就不行。

表级锁又分为表锁和元数据锁MDL,其中表锁需要我们去显式加锁,例如 lock tables … read/write,如果一个线程对其显式加了表读锁,那么其他线程和该线程只能读该表,如果该线程加了表写锁,那么其他线程啥也干不了,所以这个锁表的粒度还是太大,一般不会采用。

表级锁的另外一种是元数据锁MDL,这是系统默认加上的,对表进行 DML 是加读锁,对表进行 DDL 是加写锁,读写互斥,读共享,相当于 ReadWriteLock,但是这里没有写降级的过程。

行锁是我们最经常用的了,也分为读锁和写锁,这里的读单指 select,这里的写指的是 update、delete、insert等等,当然我们也可以对 select 进行显式的加锁,此时 select 就从乐观读锁「跟 StampedLock 类似,一个是通过 version 判断数据有没有变化,一个是通过 stamp 判断」变成了悲观读锁或者写锁了,此时mvcc是失效的,是跟 update等语句一样强制去当前读的。

  • 再讲第五个,Mysql 的索引?

索引有很多,常见的就是聚集索引和非聚集索引,聚集索引就是元素的逻辑位置和物理位置保持一致,也称为主键索引,是B+树,因为支持范围查询,并且索引节点只有指针和索引,这样单个节点就能容纳更多的指针域,从而使得树的高度下降。

非聚集索引要注意减少回表的次数,常用的方法就是覆盖索引、使用最左前缀原则尽量减少索引的建立,同时注意可以使用索引下推来减少回表的次数。

  • 好勒,数据库差不多了,最后讲一讲范式吧

mysql中常用的范式就是三大范式

第一范式:确保每列的原子性,比如说地址这一属性,有的业务可能对城市用的比较多,我们就就应该细分,这样在对用户使用城市进行分类的时候就非常方便,也提高了数据库的性能。

第二范式:确保表中的每列都和主键相关,去除部分依赖。最典型的场景就是多对多的场景,比如订单-商品表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如果此时把商品信息也写在这个表中,就不符合第二范式了,这样会造成数据冗余,分表会更好。

第三范式:确保没有传递依赖,也就是每个非主属性都要依赖于主属性,例如订单表,和用户是一对多的关系,此时用户的id会是这个表的外键,如果在这里加上用户的名字,就违反了第三范式,也是会造成数据的冗余的。

参考: https://www.cnblogs.com/linjiqin/archive/2012/04/01/2428695.html

2020.3.28 15:30 - 16:30 「60 分钟」腾讯二面

电话面。这一轮面试貌似没遇到什么大问题,问的问题也不难,没什么太大印象了…

只记得一个关于内存的。

  • GC roots 有哪些类型?

就是那些确保存活的对象,例如

  • 栈中本地变量表中引用的对象;
  • native 方法栈中引用的对象;
  • 方法区(non-heap)中的类静态属性引用的变量;
  • 方法区(non-heap)中的常量引用的对象;
  • 为什么要选定这些对象为 GC roots?

因为 gc 的目的是回收那些不用的对象,这些对象可以确保需要用到,所以肯定就不能回收。

  • 如何判断一个对象不可达?举个具体的例子?

当一个对象到 GC roots 的对象没有任何引用链相连,这个对象就是不可用的,最典型的例子就是

1
2
3
> A a = new A();
> a = null;
>

此时 a 开始指向的对象,已经没有 GC roots的对象指向它了,所以它应该被标记为不可达,我们始终注意的是,回收的永远都是堆上的不可用对象,当然它也有自救的机会,就是 A 这个类重写 finalize() 方法,然后在里面加上 B.b = this,这里假设 b 是类 B 的一个静态变量,这样这个对象依旧不会被回收,因为有 GC roots 到它的引用链。

  • 根搜索算法是如何去实现的呢?

HotSpot首先需要枚举所有的GC Roots根节点,虚拟机栈的空间不大,遍历一次的时间或许可以接受,但是方法区的空间很可能就有数百兆,遍历一次需要很久。更加关键的是,当我们遍历所有GC Roots根节点时,我们需要暂停所有用户线程,因为我们需要一个此时此刻的”虚拟机快照”,找到此时此刻的可达性分析关系图。基于这种情况,HotSpot实现了一种叫做OopMap的数据结构,存储GC Roots对象,同时并不是每个指令都会对OopMap进行修改,这样OopMap很庞大,这里Hotspot引入了安全点,safePoint,只会在Safe Point处记录GC Roots信息。这也就是 CMS 常说的初始阶段的第一步,先把 GC roots 找到,然后去标记那些与 GC roots 相关的对象,我个人认为在 gc roots 中会标记引用了他的对象的符号引用的位置,这样就能够实现找到 GC roots 相关的对象了,这个跟类加载过程的加载过程的第三个动作一样,在堆上 去 new 一个对象作为一个类的入口,那这个对象肯定是知道方法区中类的位置的,所以就是说引用了 gc roots的对象的引用链都会有记录。

  • 上面说的虚拟机快照是什么东西?

快照,就是存储在这个点上的所有vm的状态,包括内存和硬盘,当然也就包括了方法区中的 GC roots 根节点。

  • 回收的过程是怎样的?

这里以 cms 收集器为代表。

初始标记:先去判断对象的可达性,如果不可达,标记为 dead,然后看是否有继承 finalize 方法,没有的话直接标记为需要 gc,如果继承了看是否是第一次执行,是第一次执行把其标记为 alive,否则标记为 dead。

并发标记:safepoint到达之后,一边继续标记还可以一边让用户并行;

最终标记:在让用户程序并行的过程中,还会产生 gc 的对象,所以还需要再标记一下;

并发清除:多线程清除。

  • 为什么要将对象分为新生代老年代
  • 早期提出这个分代的思想我认为主要是解决 stop the world的时间长度,因为如果时间太长很影响用户体验,所以只去扫可能很快就死亡的那一些块区域是很正常的想法,而老年代由于逃过了很多次gc,说明他是有在被活跃使用的,所以我们在平时的gc的时候可以不去考虑他,这样就加快了gc的速度;
  • 后期由于引入了并行机制,也就是用户进程和垃圾回收进程可以同步进行,并且可以多线程进行回收,那 stop the world 这个时间就不是重点了,重点就在于GC能够应付的应用内存分配速率(allocation rate),也就是说 gc 的回收速度要跟上应用的分配内存的速度,所以这个时候,gc肯定是选择疯狂回收那种回收率很高的区域,其他回收率不高的肯定得等等。

所以,前者是从时间角度考虑,所以我们需要分代,因为扫小区域比扫大区域时间更短;

而后者则是从追赶用户分配内存的角度考虑,需要分代,这样扫的区域的回报率更高。

放一个链接,写的还是比较有条理的:https://allenwu.itscoder.com/java-gc

2020.3.29 15:00 - 17:30 「150 分钟」 腾讯三面

视频面。这一面前面面的还算okay,但是最后代码撕了一个多小时,生死未卜了…

主要问题还是在计算机网络方面吧。

  • 大端小端是什么?详细叙述一下?

大端和小端是指数据在内存中的存储模式,它由 CPU 决定:

1) 大端模式(Big-endian)是指将数据的低位(比如 1234 中的 34 就是低位)放在内存的高地址上,而数据的高位(比如 1234 中的 12 就是高位)放在内存的低地址上。这种存储模式有点儿类似于把数据当作字符串顺序处理,地址由小到大增加,而数据从高位往低位存放。

2) 小端模式(Little-endian)是指将数据的低位放在内存的低地址上,而数据的高位放在内存的高地址上。这种存储模式将地址的高低和数据的大小结合起来,高地址存放数值较大的部分,低地址存放数值较小的部分,这和我们的思维习惯是一致,比较容易理解。

为什么有大小端模式之分?

计算机中的数据是以字节(Byte)为单位存储的,每个字节都有不同的地址。现代 CPU 的位数(可以理解为一次能处理的数据的位数)都超过了 8 位(一个字节),PC机、服务器的 CPU 基本都是 64 位的,嵌入式系统或单片机系统仍然在使用 32 位和 16 位的 CPU。

对于一次能处理多个字节的CPU,必然存在着如何安排多个字节的问题,也就是大端和小端模式。以 int 类型的 0x12345678 为例,它占用 4 个字节,如果是小端模式(Little-endian),那么在内存中的分布情况为(假设从地址 0x 4000 开始存放):

内存地址 0x4000 0x4001 0x4002 0x4003
存放内容 0x78 0x56 0x34 0x12

如果是大端模式(Big-endian),那么分布情况正好相反:

内存地址 0x4000 0x4001 0x4002 0x4003
存放内容 0x12 0x34 0x56 0x78

我们的 PC 机上使用的是 X86 结构的 CPU,它是小端模式;51 单片机是大端模式;很多 ARM、DSP 也是小端模式(部分 ARM 处理器还可以由硬件来选择是大端模式还是小端模式)。

  • cookie 和 session的区别

cookie是为会话存储的键值信息,不可跨域名(只能拿到当前域名下的cookie,包含父级域名),有有效期限,是在客户端的浏览器保存。

session是基于内存的缓存技术,用来保存针对每个用户的会话数据,通过session ID 来区分用户,存储于服务器端。

浏览器在第一次请求时,无cookie,然后服务器收到请求后,创建一个 session,用sessionid 标识,将其放入cookie中,然后客户端以后请求都带上cookie,服务器端收到消息后,解析里面的sessionid即可。

  • java nio 中如何解决半包、粘包问题?

对于粘包问题先读出包头即包体长度n,然后再读取长度为n的包内容,这样数据包之间的边界就清楚了。
对于半包问题先读出包头即包体长度n,由于此次读取的缓存池长度小于n,这时候就需要先缓存这部分的内容,等待下次read事件来时拼接起来形成完整的数据包

  • 数据库中 varchar 和 char 的区别是什么?

字符串数据类型

MySQL数据类型 含义
char(n) 固定长度,申请的长度就是最终的长度,类似于静态数组,英文占一个字节,汉字占两个字节
varchar(n) 可变长度,类似于可变数组—列表,英文和汉字都占两个字节,实际长度是它的值的实际长度+1
text 存储可变长度的非Unicode数据,最大长度为2^31-1个字符。text列不能有默认值,存储或检索过程中,不存在大小写转换,后面如果指定长度,不会报错误,但是这个长度是不起作用的,意思就是你插入数据的时候,超过你指定的长度还是可以正常插入。
  • 经常变化的字段用varchar;
  • 知道固定长度的用char;
  • 尽量用 varchar;
  • 超过255字节的只能用varchar或者text;
  • 能用varchar的地方不用text;
  • 超长的,例如存储整个html用text。

然后就一直在聊项目了
最后出了一道算法题,就是 LeetCode 上的求下一个排列数的变体题,我竟然…紧张了…

2020.3.30 17:24 - 18:30 「66 分钟」 阿里二面

电话面。这一面,项目为主,基础知识为辅,重点是需要将自己做的项目用比较清楚的话语表述清楚,让面试官能够最短时间了解到你做的项目,同时切忌注意,自己不了解不深入的知识点尽量不要提及,这是大忌。

  • 当将业务水平分库后,转账业务如何保证事务的一致性?

这是典型的分布式事务,理论有 CAP 「C (一致性),A (可用性),P (分区容错性),只能选择 AP or CP」,BASE「Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。是对CAP中AP的一个扩展」

常见的分布式解决方案:

  1. 2PC,两阶段提交,事务管理器来协调,全部okay了才okay,这样效率很低,因为是如果没成功就一直阻塞。
  2. TCC(Try-Confirm-Cancel)最大努力交付,在更新多个资源时,将多个资源的提交尽量延后到最后一刻处理,这样的话,如果业务流程出现问题,则所有的资源更新都可以回滚,事务仍然保持一致。唯一可能出现问题的情况是在提交多个资源时发生了系统问题,比如网络问题等,但是这种情况是非常罕见的,一旦出现这种情况,就需要进行实时补偿,将已提交的事务进行回滚。
  3. 事务补偿机制。在数据库分库分表后,如果涉及的多个更新操作在某一个数据库范围内完成,则可以使用数据库内的本地事务保证一致性;对于跨库的多个操作,可通过补偿和重试,使其在一定的时间窗口内完成操作,这样就可以实现事务的最终一致性,突破事务遇到问题就滚回的传统思路。

参考:https://juejin.im/post/5b5a0bf9f265da0f6523913b#heading-16

https://cloud.tencent.com/developer/news/200316

说实话…还没看太懂…

  • zookeeper中的两阶段提交是怎么去做的?

CAP理论中,zookeeper就是CP,放弃可用性,追求一致性和分区容错性,追求的是强一致。

  • 在项目中假如一级调度器挂了,怎么处理?

我说没处理…正常应该是类似于 Mysql 一样有个主备切换的机制。

  • SpringBoot中的 AOP 分为几类,

AOP 主要是两种方式,一种是直接通过 JDK 动态代理,一种是通过cglib。

先来回顾一下 Spring 中 AOP 的流程:

  1. 代理的创建。

    1. 需要创建代理工厂,代理工厂需要 3 个重要的信息:拦截器数组,目标对象接口数组,目标对象。

    2. 创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。

    3. 当调用 getProxy 方法的时候,会根据接口数量大余 0 条件返回一个代理对象(JDK or Cglib)。

    注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器。用于控制整个 AOP 的流程。

  2. 代理的调用

    1. 当对代理对象进行调用时,就会触发外层拦截器。
    2. 外层拦截器根据代理配置信息,创建内层拦截器链。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器。而这个拦截器链设计模式就是职责链模式。
    3. 当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法。最后返回。

AOP过程

参考:https://www.jianshu.com/p/e18fd44964eb

  • 讲讲 cglib 如何使用并实现的?

动态代理再熟悉不过了,是只能代理接口,cglib现在我们来具体看一下。

我们知道,动态代理是代理类实现被代理类的接口,而cglib则是代理类继承被代理类,也就是子类增强父类的手段。cglib其实也就是字节码增强类库。

具体如何使用 cglib:https://zhuanlan.zhihu.com/p/37886319

  • 动态代理和cglib的区别?
  • 一个是代理类实现接口,一个是代理类继承类,我觉得差不太多。
  • 动态代理用到的接口有 InnvocationHandler,通过实现其 invoke 方法增强方法,并且通过 Proxy.newInstace() 实现代理过程。而cglib则使用 MethodInterceptor 接口,通过实现其 intercept() 方法增强方法,并且通过 Enhancer.create() 方法实现代理过程。

下面我放一下自己写的动态代理和 cglib 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// 动态代理,总共有四个类

// 1. 接口
package AOP.Proxy;

public interface Person {
void play();
void dance();

}

// 2. 实现类,也就是要被代理的类
package AOP.Proxy;

public class Universities implements Person {
@Override
public void play() {
System.out.println("I like play computer");

}

@Override
public void dance() {
System.out.println("I like dance");
}
}




// 3. 实现 InnocationHandler 接口,完成代理的任务
package AOP.Proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Dynamic implements InvocationHandler {
// 整容的人是谁,我得知道
private Object obj;
public Dynamic(Object obj){
this.obj = obj;
}
// 整容的过程
public Object myDynamic(){
return Proxy.newProxyInstance(this.obj.getClass().getClassLoader(),this.obj.getClass().getInterfaces(),this);
}
// 整容的地方交代清楚
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始为" + method.getName() + "方法进行代理");
Object result = method.invoke(obj,args);
System.out.println("结束" + method.getName() + "方法的代理");
return result;
}
}


// 4. 测试类
// 这里我采用了两种方法测试,一种是直接在 test 类中写出整容的过程
// 另外一种是直接调用我在 Dynamic 已经包装好整容过程的方法,建议使用这种
// 因为这样代码就少了,下面 cglib 就是采用方法二哈
package AOP.Proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class test {
public static void main(String[] args) {
// 相当于做生意的过程,比如我去医院整容,首先我要确保我有钱(也就是有接口)
Universities person = new Universities();
// 其次,我得去医院找到相应的医生,也就是把我想代理的对象,即我本人,告知给医生
// 医生肯定得有能整容的技术,那就是得继承 InvocationHandler
InvocationHandler dynamic_person = new Dynamic(person);
// 进行交易的过程,一手交钱一手交换,医生拿到钱,会返回一个有钱的处理好的美女,也就是代理完成了
// 这里必须强调代理返回的是 接口对象,也就是医生只会对有钱人进行代理,没钱的代理就失败了
// 如果最开始我没钱,我就去找医生了,那在这一步交易的过程就会出错,因为医生只会处理有钱人,并且返回有钱人的代理好的对象
// 有钱人得到代理后的对象,就可以为所欲为了
Person pp = (Person) Proxy.newProxyInstance(person.getClass().getClassLoader(),person.getClass().getInterfaces(),dynamic_person);

pp.dance();
System.out.println("-----------");


Person pp2 = (Person) new Dynamic(person).myDynamic();
pp2.dance();



}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// cglib 实现
//1. 导包,在pom.xml 导包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.0</version>
</dependency>

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.0</version>
</dependency>

// 2. 需要被代理的类
package AOP.Cglib;

public class SomeService {
public String doFirst() {
System.out.println("执行doFirst()方法");
return "abc";
}

public void doSecond() {
System.out.println("doSecond()方法");
}
}

// 3. 进行代理过程
package AOP.Cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibFactory implements MethodInterceptor {

private Object object;

public CglibFactory(Object object) {
this.object = object;
}

public Object myCglibCreator() {
Enhancer enhancer = new Enhancer();
//将目标类设置为父类,cglib动态代理增强的原理就是子类增强父类,cglib不能增强目标类为final的类
//因为final类不能有子类
enhancer.setSuperclass(this.object.getClass());
//设置回调接口,这里的MethodInterceptor实现类回调接口,而我们又实现了MethodInterceptor,其实
//这里的回调接口就是本类对象,调用的方法其实就是intercept()方法
enhancer.setCallback(this);
//create()方法用于创建cglib动态代理对象
return enhancer.create();
}

//回调接口的方法
//回调接口的方法执行的条件是:代理对象执行目标方法时会调用回调接口的方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

Object result = methodProxy.invokeSuper(o, objects);

//这里实现将返回值字符串变为大写的逻辑
if(result != null) {
result = ((String) result).toUpperCase();
}
return result;
}
}

// 4. 测试
package AOP.Cglib;

public class Test {
public static void main(String[] args) {
SomeService target = new SomeService();

SomeService proxy = (SomeService) new CglibFactory(target).myCglibCreator();

String result = proxy.doFirst();
System.out.println(result);
proxy.doSecond();
}
}
  • Spring中 Ioc 和 DI 讲一下?

参考:https://juejin.im/post/5df5bab0e51d45582427104e

https://www.jianshu.com/p/17b66e6390fd

IoC(Inversion of Control 控制反转):是一种面向对象编程中的一种设计原则,用来减低计算机代码之间的耦合度。其基本思想是:借助于“第三方”实现具有依赖关系的对象之间的解耦。

DI(Dependence Injection 依赖注入):将实例变量传入到一个对象中去(Dependency injection means giving an object its instance variables)。

也就是说 DI 是 Ioc 的 实现,Ioc 是一种设计原则。

Spring 作者 Rod Johnson 设计了两个接口用以表示容器。

  • BeanFactory
  • ApplicationContext

BeanFactory 粗暴简单,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”

ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。

Ioc 的过程:

a. 加载配置文件,解析成 BeanDefinition 放在 Map 里。

b. 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。

  • ORM框架有哪些?有什么好处?什么是mysql注入?$ 和 # 有什么区别

JPA是orm框架标准,主流的orm框架都实现了这个标准。

MyBatis没有实现JPA,他和orm框架的设计思路完全不一样。
MyBatis是拥抱sql,而orm则更靠近面向对象,不建议写sql,实在要写推荐你写hql代替。

Mybatis是sql mapping框架而不是orm框架,当然orm和Mybatis都是持久层框架。

所以说hibernate是典型的ORM框架,好处就是我们可以用面向对象的思想去对数据库进行操作,我觉得主要就是比较省代码,解决面向对象的设计方式和关系型数据库之间的关联,Java主要面向对象设计,因此在分析业务的时候会以对象的角度来看待问题。然而数据库是关系型的,对于Java程序员而言是不符合面向对象设计的,因此才会出现ORM这种东西。有了ORM,Java开发人员在整个代码设计都将遵循对象的思维模式,这就是好处。

mysql注入:参数进行转义与过滤

如何解决:使用 Prepare Statement

$ 和 # 的区别:

Sql: delete from student where name=${name}

假如 name = jerome OR 1 = 1

在 Mybatis 中,如果写 ${name},那就是直接将 name 拼接到 sql 中,结局就是全删;

如果写 #{name},则 sql 变成 delete from student where name= ’jerome OR 1 = 1‘,这样只有name = jerome OR 1 = 1 的才会删除,否则不会删除任何东西。

2020.3.31 20:02 - 20:45 「43 分钟」 阿里三面

从这一面开始面试官就是阿里 P9 了,所以说实话压力还是非常大的,这一面还是跟前面一样是电话面。这一面基本上没有问任何基础,全部在讲项目,建议大家一定要有一个讲的很溜的项目,我因为提及了一下毕设,然后就被一直抓着问,而自己本身其实是没有好好准备这个项目的,所以有些问题竟然被问了后,答得不是特别理想。但是还好,答得也没有很差,后面的项目介绍的还是让面试官很满意的。

介绍完后,面试官可能觉得才半小时,于是就问了几个问题,只是探寻一下我知识的广度吧,全部没有深入。

例如:

  • 了解 tomcat 吗?「当然了这是我在讲双亲委派模型引申出来的」
  • 用过 nginx 吗?
  • spring 中用过吗?

这些,我因为说不太了解 or 用过没深入,所以面试官也就索性没有问下去。

最后,花了10分钟介绍了下自己的部门,然后就到了反转环节了,我觉得大家可以好好抓住反转环节,因为我们往往可以从这个环节探到面试官对本次面试的看法,比如,我问的第一个问题就是:

”我觉得今天的表现不是很好,第一个项目没讲的非常清楚,您后面问的几个知识点我也不太会,没深入了解过。“

很让我意外的是,面试官给我的回答让我备受鼓舞:

”前面可能是你太着急太紧张了,所以讲的不是很好,但是后面讲的很清楚了,也能听得出来全是自己用心做了的,我也听明白了,所以不用担心,至于后面的知识点不会也非常正常,你还年轻的很,不会是非常正常的,你要都会了我还需要在这吗?你还年轻,是非常有潜力的。“

至此,开始期待四面哈哈哈哈。

2020.4.01 23:04 - 00:05 「61 分钟」 阿里四面

在四面之前,出现了个小插曲。就是面试官上午估计是有跟我打过电话,但是貌似跟饿了么的外卖员跟我打电话冲突了「应该都是走的阿里的电话系统」,导致我压根没接到电话「外卖员还说给我打电话我一直不接,我说我压根没接到电话…」,最后还是下午5点的时候二面的面试官打电话给我叫我注意一下电话…我当时一脸懵逼…所以说关键时刻不要点饿了么外卖…….

然后约的 8 点半,但是我因为有携程的笔试推到了晚上 10 点半,然后 10 点我就开始坐在桌前等,说真的有点紧张,毕竟这面是交叉面,又是一个 P9 大佬…等啊等,一直等到晚上 11 点面试官都没有出现,我一度怀疑是我听错了时间,突然,11:04,面试官非常抱歉的打开时视频,终于看到了…于是面试正式开始…

视频面。有点太晚了,所以自己在面之前也是有跟面试官交代,可能反应会慢一些…面试官还是非常理解的,然后这一面可以说是这半个多月来面的最棒的一次了,下面是主要内容:

面试依旧是从项目出发,注意,阿里后面四面基本上全部围绕项目展开,所以务必要有一个吃透的项目,了解其中用到的技术栈,并且延伸到操作系统级别的调用,同时要关注项目中遇到的难点以及自己是如何解决的,这是最最最高频的问题了,基本上每一面的面试官都有问这个问题。

同时,一定要将项目中的某些部门转移到自己擅长的领域,比如我讲的项目中其实就是一个爬虫项目,但是可以扩展的地方太多了,我就举几个例子:

  1. 讲到爬虫,那务必要讲反爬措施,这是一个可以展开的点,可以讲很久,讲反爬如何解决;
  2. 中间件用到kafka,这又是一大块可以讲的,kafka 的通信机制,以及内部构造,以及高可用机制和吞吐量大;
  3. 谈到 kafka 的吞吐量大,又可以总结一波为何吞吐量大,然后延伸到 NIO 和 零拷贝技术,又可以讲一堆;
  4. 讲到 NIO,又可以讲 IO 那一大块知识点,同时谈到 NIO 必定会谈到 select/epoll,这样又可以讲很多。

总之,这些知识,自己都是可以提前准备好的,然后到时候面试抛出来就行了,面试官一般都会顺着你的思路进行下去的,要把话语权掌握在自己的手上,一定要记得特意的抛出某些知识点「这里的特意是指跟你的项目挂钩的」

同时,我发现从三面到四面,都有去关心,自己在项目中用到的设计模式以及自己看源码发现的一些设计模式。

我之前只准备了三种设计模式,并且自己亲自写了一些小demo:

  1. 单例模式,四面就问了这个,谈什么是饿汉模式和懒汉模式;
1
2
3
4
5
6
7
8
9
10
> // 饿汉
> public class Singleton {
> // 类加载时就初始化
> private static final Singleton instance = new Singleton();
> private Singleton() {}
> public static Singleton getInstance(){
> return instance;
> }
> }
>
1
2
3
4
5
6
7
8
9
10
11
12
> // 懒汉
> public class Singleton {
> private static Singleton instance;
> private Singleton () {}
> public static Singleton getInstance() {
> if (instance == null) {
> instance = new Singleton();
> }
> return instance;
> }
> }
>
  1. 模板方法模式,这个是IO延伸过来的知识点,然后我顺带提了一下 AQS;

IO 和 AQS 都用了模板方法的设计模式。

  1. 代理模式,提了一下动态代理,以及Spring中的 AOP。

上面已经总结了

然后就是一些比较简单的问题:

  1. 为何要分新生代和老年代,如何区分的?

这个我在《深入理解java虚拟机笔记》中有非常详细的谈到,并且我在 “腾讯二面”这部分也有很详细的总结!可以往上翻翻看到。

  1. Full gc 如何排查?
  1. 先看一下相关的图形化界面,看每一次 full gc后对象的存活率
  2. 如果存活率高,可能是老年代的内存太小了,导致很容易触发 full gc;
  3. 如果存活率低,说明可能是很多大对象进入到老年代区域,主要是可能自己代码写的有点问题,或者说明可能是 eden 区太小了,导致年轻代很容易就进入了老年代区域。
  1. 讲一讲 synchronized 和 Lock 的区别,以及 ThreadLocal?

Synchronized 和 lock 最典型的几个区别:

  1. synchronized 关键字,jvm层面;lock 是类,jdk层面;
  2. synchronized 不支持中断,lock支持;
  3. synchronized如果阻塞了会一直等待,而lock可以定时取消等待;
  4. synchronized只能有一个条件队列,而lock可以有多个。

ThreadLocal 主要谈到 ThreadLocalMap,注意它是线性探测法解决的冲突,并且加载因子为2/3,初始值16,当然还得谈到其内存泄漏问题,解决方案就是使用弱引用以及用一些方法处理脏 Entry ,当然并不能完全避免内存泄漏,但是只要你不和线程池搞在一块就应该不会产生内存泄漏。

  1. 网络中的加密算法有了解吗?

必须了解。

从https「上面已经讲过了」,到 RSA 加密算法,再到对称非对称加密算法,再到 token 等等。

  1. 讲讲非对称和对称加密算法「这块我讲了md5算是,很明显面试官说不是」

严格来说:MD5、sha-1只是散列算法,或者叫摘要算法,不能算加密算法。

也就是说,MD5 是不可逆的,你根本解不了,而对称非对称加密算法是可逆的,可以通过密文得到明文,也可以通过明文得到密文。

加密对应解密,即加密后的密文可以解密成明文,但是MD5无法从密文(散列值)反过来得到原文,即没有解密算法。

大家知道加密算法分为对称加密和非对称加密,不管对称加密和非对称加密,都是能够从密文解密得到明文的。从这点上讲MD5不是加密算法,更谈不上属于对称加密、非对称加密。所以不要再讨论MD5是属于对称加密、非对称加密了,MD5既不属于对称加密也不属于非对称加密,MD5根本就没法解密,也没有秘钥(加盐并不是秘钥),所以可以认为MD5不属于加密算法。

一些人认为MD5处理后看不到原文,即已经将原文加密,所以认为MD5属于加密算法。如果这么看的,那么求余也可以算加密算法了。

作者:GeCoder
链接:https://www.zhihu.com/question/68735830/answer/327762693
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

所以,md5 并不是加密算法,那典型的加密算法有哪些呢?

加密技术通常分为两大类:”对称式”和”非对称式”。

对称性加密算法:对称式加密就是加密和解密使用同一个密钥。信息接收双方都需事先知道密匙和加解密算法且其密匙是相同的,之后便是对数据进行加解密了。对称加密算法用来对敏感数据等信息进行加密。

非对称算法:非对称式加密就是加密和解密所使用的不是同一个密钥,通常有两个密钥,称为”公钥”和”私钥”,它们两个必需配对使用,否则不能打开加密文件。发送双方A,B事先均生成一堆密匙,然后A将自己的公有密匙发送给B,B将自己的公有密匙发送给A,如果A要给B发送消息,则先需要用B的公有密匙进行消息加密,然后发送给B端,此时B端再用自己的私有密匙进行消息解密,B向A发送消息时为同样的道理。

散列算法:散列算法,又称哈希函数,是一种单向加密算法。在信息安全技术中,经常需要验证消息的完整性,散列(Hash)函数提供了这一服务,它对不同长度的输入消息,产生固定长度的输出。这个固定长度的输出称为原输入消息的”散列”或”消息摘要”(Message digest)。散列算法不算加密算法,因为其结果是不可逆的,既然是不可逆的,那么当然不是用来加密的,而是签名。

对称性加密算法有:AES、DES、3DES
用途:对称加密算法用来对敏感数据等信息进行加密

DES(Data Encryption Standard):数据加密标准,速度较快,适用于加密大量数据的场合。

3DES(Triple DES):是基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。

AES(Advanced Encryption Standard):高级加密标准,是下一代的加密算法标准,速度快,安全级别高;AES是一个使用128为分组块的分组加密算法,分组块和128、192或256位的密钥一起作为输入,对4×4的字节数组上进行操作。众所周之AES是种十分高效的算法,尤其在8位架构中,这源于它面向字节的设计。AES 适用于8位的小型单片机或者普通的32位微处理器,并且适合用专门的硬件实现,硬件实现能够使其吞吐量(每秒可以到达的加密/解密bit数)达到十亿量级。同样,其也适用于RFID系统。

非对称性算法有:RSA、DSA、ECC

RSA:由 RSA 公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的。RSA在国外早已进入实用阶段,已研制出多种高速的RSA的专用芯片。

DSA(Digital Signature Algorithm):数字签名算法,是一种标准的 DSS(数字签名标准),严格来说不算加密算法。

ECC(Elliptic Curves Cryptography):椭圆曲线密码编码学。ECC和RSA相比,具有多方面的绝对优势,主要有:抗攻击性强。相同的密钥长度,其抗攻击性要强很多倍。计算量小,处理速度快。ECC总的速度比RSA、DSA要快得多。存储空间占用小。ECC的密钥尺寸和系统参数与RSA、DSA相比要小得多,意味着它所占的存贮空间要小得多。这对于加密算法在IC卡上的应用具有特别重要的意义。带宽要求低。当对长消息进行加解密时,三类密码系统有相同的带宽要求,但应用于短消息时ECC带宽要求却低得多。带宽要求低使ECC在无线网络领域具有广泛的应用前景。

散列算法(签名算法)有:MD5、SHA1、HMAC
用途:主要用于验证,防止信息被修。具体用途如:文件校验、数字签名、鉴权协议

MD5:MD5是一种不可逆的加密算法,目前是最牢靠的加密算法之一,尚没有能够逆运算的程序被开发出来,它对应任何字符串都可以加密成一段唯一的固定长度的代码。

SHA1:是由NISTNSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1设计时基于和MD4相同原理,并且模仿了该算法。SHA-1是由美国标准技术局(NIST)颁布的国家标准,是一种应用最为广泛的Hash函数算法,也是目前最先进的加密技术,被政府部门和私营业主用来处理敏感的信息。而SHA-1基于MD5,MD5又基于MD4。

HMAC:是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。也就是说HMAC是需要一个密钥的。所以,HMAC_SHA1也是需要一个密钥的,而SHA1不需要。

其他常用算法:

Base64:其实不是安全领域下的加密解密算法,只能算是一个编码算法,通常用于把二进制数据编码为可写的字符形式的数据,对数据内容进行编码来适合传输(可以对img图像编码用于传输)。这是一种可逆的编码方式。

HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL(SSL使用40 位关键字作为RC4流加密算法,这对于商业信息的加密是合适的。),因此加密的详细内容就需要SSL。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间),提供了身份验证与加密通讯方法,现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。它的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。Https 就可以使用上面的对称加密算法和非对称加密算法进行消息的加密。

项目应用总结:

  1. 加密算法是可逆的,用来对敏感数据进行保护。散列算法(签名算法、哈希算法)是不可逆的,主要用于身份验证。
  2. 对称加密算法使用同一个密匙加密和解密,速度快,适合给大量数据加密。对称加密客户端和服务端使用同一个密匙,存在被抓包破解的风险。
  3. 非对称加密算法使用公钥加密,私钥解密,私钥签名,公钥验签。安全性比对称加密高,但速度较慢。非对称加密使用两个密匙,服务端和客户端密匙不一样,私钥放在服务端,黑客一般是拿不到的,安全性高。
  4. Base64不是安全领域下的加解密算法,只是一个编码算法,通常用于把二进制数据编码为可写的字符形式的数据,特别适合在http,mime协议下的网络快速传输数据。UTF-8和GBK中文的Base64编码结果是不同的。采用Base64编码不仅比较简短,同时也具有不可读性,即所编码的数据不会被人用肉眼所直接看到,但这种方式很初级,很简单。Base64可以对图片文件进行编码传输。
  5. https协议广泛用于万维网上安全敏感的通讯,例如交易支付方面。它的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
  6. 大量数据加密建议采用对称加密算法,提高加解密速度;小量的机密数据,可以采用非对称加密算法。在实际的操作过程中,我们通常采用的方式是:采用非对称加密算法管理对称算法的密钥,然后用对称加密算法加密数据,这样我们就集成了两类加密算法的优点,既实现了加密速度快的优点,又实现了安全方便管理密钥的优点。
  7. MD5标准密钥长度128位(128位是指二进制位。二进制太长,所以一般都改写成16进制,每一位16进制数可以代替4位二进制数,所以128位二进制数写成16进制就变成了128/4=32位。16位加密就是从32位MD5散列中把中间16位提取出来);sha1标准密钥长度60位(比MD5摘要长32位),Base64转换后的字符串理论上将要比原来的长1/3。

以上内容来自于:https://www.cnblogs.com/sochishun/p/7028056.html

个人理解的对称加密算法的过程:

A 与 B 商定好加密算法,然后 A 如果给 B 发消息,会先运用加密算法,进行加密,B 收到后,会运用逆加密算法,进行解密,也就是其密钥是一样的。

个人理解的非对称加密算法的过程:

A 和 B 各自拥有一对公钥和私钥,也就是说二者发送消息的加密算法可以互不相同,且对方可以不用知道。例如 A 要给 B 发消息,要拿到 B 的公钥,然后进行加密即可,B 收到后用自己的私钥就可以解开了。

  1. 介绍BIO、NIO、AIO

这个简单…详细的叙述见我 《零拷贝及其周边》

BIO:普通的同步阻塞 I/O

NIO:同步非阻塞 I/O

AIO :异步非阻塞 I/O

  1. 虚拟内存

这个是知识盲区,需要好好科普一下。

我的天啊!!!!自己的《零拷贝及其周边》早就介绍过虚拟内存,面试的时候竟然没有想起来,还知识盲区呢???真的是智障啊啊啊啊。。。。。这个没答上来真的太可惜了,不过还好面试官没有深究…

虚拟内存

虚拟内存是计算机系统内存管理的一种技术。 它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间)。而实际上,虚拟内存通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换,加载到物理内存中来。 目前,大多数操作系统都使用了虚拟内存,如 Windows 系统的虚拟内存、Linux 系统的交换空间等等。

离开进程谈虚拟内存没有任何意义,不同进程里的同一个虚拟地址指向的物理地址是不一样的。每个用户进程维护了一个单独的页表(Page Table),虚拟内存和物理内存就是通过这个页表实现地址空间的映射的,页表(Page Table)里面的数据由操作系统维护。

引入虚拟内存的好处

在进程和物理内存之间,加了一层虚拟内存的概念,好处有:

  1. 提供更大的地址空间,因为虚拟内存还可以放在磁盘上或者寄存器中,而物理内存并不行,而且虚拟地址空间是连续的,我们不需要操心具体是如何存放的,操作系统会帮我们映射好;
  2. 安全性更好,虚拟内存设有读写属性,并且不同进程互不影响;
  3. 可以懒加载,只有在需要读相应的文件的时候,才将它真正的从磁盘上加载到内存中来,而在内存吃紧的时候又可以将这部分内存清空掉,提高物理内存利用效率,并且所有这些对应用程序是都透明的;
  4. 可以共享内存,动态库只需要在内存中存一份就够了,然后将它映射到不同进程的虚拟地址空间中,让进程觉得自己独占了这个文件。进程间的内存共享也可以通过映射同一块物理内存到进程的不同虚拟地址空间来实现共享。

如果想了解更底层的话,戳链接:https://sylvanassun.github.io/2017/10/29/2017-10-29-virtual_memory/#comments

话说最拿手的数据库竟然一点都没问,感觉有点意犹未尽哈哈哈哈哈…

2020.4.06 17:30 - 17:45 「15 分钟」 腾讯 hr 面

  1. 自我介绍
  2. 项目中遇到的最大的困难以及如何解决的
  3. 项目中如何分工
  4. 未来 3-5 年的规划
  5. 优缺点
  6. 现在面试了几家公司,各自具体流程是什么
  7. 如果给你发offer,你会怎么选

2020.4.08 14:30 - 15:00 「30 分钟」阿里 hr 面

就是正常的聊天

  1. 自我介绍
  2. 做的项目讲一下,在里面的职责
  3. 然后就是介绍我要去的部门和架构组了

等offer,希望一切好运!!!

祝大家都能够找到心仪的实习吧!!加油各位!!!

Thank you for your accept. mua!
-------------本文结束感谢您的阅读-------------