专栏名称: 人工智能与大数据技术
分享大数据、云计算、人工智能等高科技先进技术
目录
相关文章推荐
数据派THU  ·  深入解析图神经网络:Graph ... ·  21 小时前  
数据派THU  ·  【ICLR2025】AdaWM:基于自适应世 ... ·  2 天前  
大数据分析和人工智能  ·  DeepSeek找到了未来最赚钱的6个行业 ·  2 天前  
数据派THU  ·  使用Python实现基于矩阵分解的长期事件( ... ·  5 天前  
软件定义世界(SDX)  ·  Flywheel:2024电商消费趋势年度报告 ·  1 周前  
51好读  ›  专栏  ›  人工智能与大数据技术

专科生作业帮大数据面经(已拿offer,附详细答案)

人工智能与大数据技术  · 公众号  · 大数据  · 2019-11-26 09:25

正文

来自公众号: 大数据肌肉猿

这位同学是学习群的同学,之前已经写了一篇阿里一面的面经:# 专科生阿里大数据一面面经「已过」「附详细答案」


如今又收到了作业帮的大数据offer, 他是甘肃职业学校的专科生,而且是罕见的警犬专业 ,但他学习非常刻苦认真,值得我们学习!


以下答案由我重新整理:

一面

1、先是两分钟左右的自我介绍,你有什么优势?


2、数据库索引结构有哪些?


(1)生成索引,建立二叉查找树 生成索引

(2)建立B-Tree 生成索引

(3)建立B+-Tree 生成索引

(4)建立Hash,基于InnoDB和MyISAM的Mysql不显示支持哈希(5) 位图数据结构,BitMap,少量主流数据库使用,如Oracle,mysql不支持;


3、紧接着又问了如何定位并优化慢查询sql?


(1)根据慢日志定位慢查询SQL

(2)使用explain等工具分析SQL

(3)修改SQL或者尽量让SQL走索引以优化查询效率


4、索引是建立的越多越好吗?


(1)数据量小的表不需要建立索引,建立会增加额外的索引开销;(2)数据变更需要维护索引,因此更多的索引意味着更多的维护成本;(3)更多的索引也意味着需要更多的空间


5、说一下你知道的垃圾收集算法和垃圾收集器


垃圾收集算法(垃圾回收的方法论):


1.复制算法:此种算法是将空间分成两部分,每次使用其中的一部分。在垃圾回收时,将正在使用的内存中存活的对象复制到未使用的内存中,然后清除正在使用的内存。这种算法不会产生碎片,但会造成空间的利用率低。
2.记清除法:此种算法是将垃圾收集分为两个阶段,标记阶段和清除阶段。标记阶段是将所有需要回收的对象进行标记,然后标记结束后,对标记的对象进行回收。这种算法会产生大量碎片,效率低下。


3.标记整理法:此种算法是将所有需要回收的对象进行标记后,将所有存活的对象移到另外一端,其他不需要保留的全部清理。此种算法解决了标记清除法所产生的碎片问题。


4.分代法:根据对象生命周期的长短进行分块,在根据每块区间不同的特点,使用不同的算法进行垃圾回收,从而提高垃圾回收效率。比如:Java虚拟机中的堆采用了这种方法分成了新生代和老年代,然后对于不同的代采用不同的垃圾回收算法。新生代采用了复制算法,老年代采用了标记清除法。


5.分区法:将空间分成连续多个不等的小区间,每个区间单独使用,独立回收。优点是一个可以控制多个区间的回收。


6.引用计数法:此种算法是对对象设置一个引用计数器,每增加一个变量对它的引用,计数器就加1,反之,减1。只有当计数器的值为0时,该对象才会被回收。该算法简单,也有缺点,对对象操作频繁,增加了系统消耗;也无法处理循环引用的情况。


垃圾收集器(垃圾回收的具体体现):

1.新生代:Serial、ParNew、parallel Scaveng
2.老年代:Serail old、ParNew old、CMS


========================================


1.新生代回收器的详细介绍:


a.Serial:它是一个单线程的垃圾回收器,单线程的意义是:只会使用一个CPU或者一条垃圾收集线程去完成垃圾收集工作。而在它进行垃圾收集工作的时候,其他线程必须暂停,直到垃圾收集结束。


b.ParNew:它是serail的多线程版本,基本操作和Serial一样。该收集器一般和CMS搭配工作。


c.parallel Scavenge:此收集器的目的是达到一个可控制的吞吐量。


吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾回收的时间)


d.GC自适应策略:JVM会根据当前系统运行情况收集性能监控情况,动态调整这些参数以提供最适合的停顿时间或者吞吐量。


================
老年代垃圾收集器:
1.Serial Old:是Serail 的老版本,也是单线程收集器,该收集器主要是给Client模式下的虚拟机使用的。
Note:分代收集算法:新生代采用复制算法,并暂停所有用户线程。     老年代采用标记整理法,并暂停所有用户线程。
2.Parallel Old:是Parallel的老版本,使用多线程和标记整理算法进行垃圾回收。
3.CMS:是一种以获取最短回收停顿时间为目标的收集器。该收集器是基于"标记清除"算法实现的。


================
新生代和老年代垃圾回收器:
G1

G1收集器所具备的特点有哪些
1.并发和并行:使用多线程来缩短暂停时间,G1收集器通过并行的方式让Java继续执行。
2.分代收集:
G1收集器将整个Java堆划分为多个大小相等的独立区域(Region)。G1收集器之所以可以有计划地避免在整个Java堆中进行全区域的垃圾收据,是因为G1收集器跟踪各个Region里面的垃圾堆积的价值大小(回收获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。即Grabage-First。

在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是通过Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set。在新建对象时,JVM会将相关的引用信息记录到被引用对象所属的Region的Remembered Set中。当进行回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对堆进行扫描也不会有遗漏。


3、空间整合:采用标记整理算法实现

4、可预测的停顿:建议可预测的停顿时间模型,让使用者明确在y一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒,达到了实时的垃圾收集器。


G1垃圾收集器的收集阶段分为以下几步:

1、初始标记(只是标记一下GC Roots能直接关联到的对象,并修改可以得Region中创建新对象,这阶段需要停顿线程,但耗时很短)
2、并发标记(从GC Roots开始对堆中对象进行可达性分析,找出存活对象)
3、最终标记(修正在并发标记期间因月洪湖程序继续运行而导致标记产生变动的那一部分标记记录)
4、筛选回收(首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region)


6、堆内存是怎么分配的?
Java 堆主要分为2个区域-年轻代与老年代,年轻代包括Eden 区和 Survivor 区,Survivor 区又分From区和 To区。如下图:


Eden区
对象会优先在新生代 Eden 区中进行分配,当 Eden 区空间不足时,虚拟机会使用复制算法发起一次 Minor GC(Young GC),清除掉垃圾对象。之后,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区(From 区内存不足时,直接进入 Old 区)。


Survivor区
Survivor 区相当于是 Eden 区和 Old 区的一个缓冲区。如果没有Survivor 区域,Old区将很快被填满,就会触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。Survivor 的存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。
Survivor 又分为2个区,一个是 From 区,一个是 To 区。每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(To 区内存不足时,直接进入 Old 区)。


为什么要将Survivor区分成From和To两个区?
为了解决内存碎片化的问题。Minor GC 执行后,Eden 区会清空,存活的对象放到了 Survivor 区,而之前 Survivor 区中的对象,可能也有一些是需要被清除的。这时候JVM要使用标记清除算法去清除垃圾对象,而标记清除算法最大的问题就是内存碎片,由于在Eden区中有很多对象是“朝生夕死”的,所以必然会让内存产生严重的碎片化。Survivor 有2个区域,每次 Minor GC时,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,再将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。这样一来,总有一个Survivor区域是空闲的。这样就解决了内存碎片的问题。


Old区
Old区据着2/3的堆内存空间,当对象从新生代中存活下来,就会被拷贝到这里。Major GC 会清理Old区中的对象,每次Major GC 都会触发“Stop-The-World”。内存越大,执行的时间也就越长。由于老年代中对象存活率很高,采用复制算法效率很低,所以老年代垃圾收集采用的是标记整理算法。


内存分配策略

内存分配策略主要有以下几点:


对象优先分配在Eden区,如果Eden区没有足够的空间进行分配时,虚拟机执行一次MinorGC。
大对象直接进入老年代(需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄(Age Count)计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,直到达到阀值(默认15次),对象进入老年区。
动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。


7、你看过flink源码? (因为简历中有写看过部分源码)直接问我Flink作业的执行过程了


参考:
https://www.cnblogs.com/ooffff/p/9486451.html

8、然后就问了 java中强引用,软引用,弱引用,虚引用有什么用?

强引用:
最普遍 的引用:Object obj = new  Object();
宁愿抛出 OOM也不愿意回收强引用的对象
通过将对象 设置为null 来弱化引用,使其被回收,或者等待生命周期超时。

软引用(Soft Reference):
对象处在有用 但非必须的状态
当内存空间不足时,会回收它 ,内存足够时,会允许它存在。
作用:可以用来实现高速缓存

弱引用 (Weak Reference):
比软引用更弱
GC时一定会被回收
但是由于GC线程优先级较低,也不一定会立即被回收
适用于引用 偶尔被使用且不影响垃圾收集的对象

虚引用 (PhantomReference):
它不会决定 对象 的生命周期
任何 时候都有可能 被垃圾回收器回收,
跟踪对象被垃圾收集器回收 的活动,起到一个哨兵的作用
不能单独使用,必须和 引用队列ReferenceQueue联合使用;

9、Http和https的区别是什么,https的数据传输过程?

区别:
1. Https需要到CA 申请证书,Http不需要
2. Https 密文传输,Http明文传输
3. 连接方式不同,Https默认使用的是 443端口,Http使用 80端口
4. Htttps = http + SSL(加密+认证+完整性保护),较Http安全;

Https 数据传输过程:
(1). 浏览器将支持的加密算法信息发给服务器
(2). 服务器选择一套浏览器支持的加密算法,以证书的形式回发给浏览器
(3). 浏览器验证 证书的合法性,并结合证书 公钥 加密信息发给服务器
(4). 服务器使用 私钥解密信息,验证哈希,加密响应消息回发给 浏览器
(5). 浏览器解密响应信息,并对消息进行验证,之后进行加密交互数据

10、GET请求和POST请求的区别?

Http报文层面:GET请求将请求信息放在URL,POST放在报文体中
数据库层面:GET 符合幂等性和安全性,POST不符合
其它层面:GET可以被缓存,被存储,而POST不行。( GET可以将请求缓存在浏览器的本地,书签等,而POST不行 ,非幂等。)


11、算法只问了一道关于二叉树按层打印的算法题。

12、结尾就问我有什么想问他的,

(我就只问了面试表现如何,他也就说了还不错。)

二面

1、又是自我介绍环节
…….

2、问了TCP/IP的三次握手和四次挥手?

握手流程 :
1. 第一次握手,建立连接时,客户端发送SYN包 ( syn=j )到服务器,并进入SYN_SEND状态,等待服务器确认。
2. 第二次握手:服务器收到SYN 包,必须确认客户的SYN (ack=j+1),同时自己也发送一个SYN包 (syn=k),即 SYN+ACK包,此时服务器进入 SYN_RECV 状态;
3. 第三次握手:客户端收到服务器的SYN+ACK 包,向服务器发送确认包 ACK(ack=k+1), 此包发送完毕,客户端和服务器进入ESTAB -LISTEN 状态,完成三次握手。

问题、隐患:
首次握手的隐患----SYN超时
针对SYN Flood的防护措施:

1. SYN队列满后,通过tcp_syncookies 参数回发SYN Cookie
2. 若为正常连接则Client会回发 SYN Cookie, 直接建立连接

挥手流程:
1. 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入 FIN_WAIT_1 状态;
2. 第二次挥手:Server收到 FIN后,发送一个ACK给 Client,确认序号 为收到 序号 +1 (与SYN相同,一个FIN占用一个程序号),Server进入 CLOSE_WAIT 状态;
3. 第三次挥手:Server发送一个FIN后,用来关闭Server到Client的数据传送,Server 进入 LAST_ACK 状态;
4. 第四次挥手:Client收到 FIN 后,Client进入 TIME_WAIT 状态,接着发送一个ACK 给Server,确认序号为 收到序号+1,Server进入CLOSED 状态,完成四次挥手。

问题,隐患:
(1)为什么会有TIME_WAIT状态

原因:
1. 确保有足够的时间让对方 收到ACK 包
2. 避免新旧连接 混淆

(2)服务器出现大量CLOSE_WAIT状态的原因
对方关闭socket连接,我方忙于读或写,没有及时关闭连接
1. 检查代码,特别是释放资源的代码
2.  检查配置,特别是处理请求的线程配置

(3)为什么需要四次握手才能断开连接:
因为全双工,发送方和接收都需要FIN报文和ACK 报文


5、当你在浏览器输入网址后 会发生什么?
(有些模糊只想起说了https的数据传输过程 答到一般面试官就说跳过了)

6、HashMap的put方法的执行逻辑?

public V put(K key,V value){
// 调用hash(key)计算出key的hash值
return putVal(hash(key),key,value,false,true);
        }
static final int hash(Object key){
int h;
// 如果key为null,则hash值为0,否则调用key的hashCode()方法
// 并让高16位与整个hash异或,这样做是为了使计算出的hash更分散
return(key==null)?0:(h=key.hashCode())^(h>>>16);
        }
final V putVal(int hash,K key,V value,boolean onlyIfAbsent,
        boolean evict
)
{
        Node[]tab;
        Node p;
int n,i;
// 如果桶的数量为0,则初始化
if((tab=table)==null||(n=tab.length)==0)
// 调用resize()初始化
        n=(tab=resize()).length;
// (n - 1) & hash 计算元素在哪个桶中
// 如果这个桶中还没有元素,则把这个元素放在桶中的第一个位置
if((p=tab[i=(n-1)&hash])==null)
// 新建一个节点放在桶中
        tab[i]=newNode(hash,key,value,null);
else{
// 如果桶中已经有元素存在了
        Node e;
        K k;
// 如果桶中第一个元素的key与待插入元素的key相同,保存到e中用于后续修改value值
if(p.hash==hash&&
        ((k=p.key)==key||(key!=null&&key.equals(k))))
        e=p;
else if(p instanceof TreeNode)
// 如果第一个元素是树节点,则调用树节点的putTreeVal插入元素
        e=((TreeNode)p).putTreeVal(this,tab,hash,key,value);
else{
// 遍历这个桶对应的链表,binCount用于存储链表中元素的个数
for(int binCount=0;;++binCount){
// 如果链表遍历完了都没有找到相同key的元素,说明该key对应的元素不存在,则在链表最后插入一个新节点
if((e=p.next)==null){
        p.next=newNode(hash,key,value,null);
// 如果插入新节点后链表长度大于8,则判断是否需要树化,因为第一个元素没有加到binCount中,所以这里-1
if(binCount>=TREEIFY_THRESHOLD-1// -1 for 1st
        treeifyBin(tab,hash);
break;
        }
// 如果待插入的key在链表中找到了,则退出循环
if(e.hash==hash&&
        ((k=e.key)==key||(key!=null&&key.equals(k))))
break;
        p=e;
        }
        }
// 如果找到了对应key的元素
if(e!=null){ // existing mapping for key
// 记录下旧值
        V oldValue=e.value;
// 判断是否需要替换旧值
if(!onlyIfAbsent||oldValue==null)
// 替换旧值为新值
        e.value=value;
// 在节点被访问后做点什么事,在LinkedHashMap中用到
        afterNodeAccess(e);
// 返回旧值
return oldValue;
        }
        }
// 到这里了说明没有找到元素
// 修改次数加1
        ++modCount;
// 元素数量加1,判断是否需要扩容
if(++size>threshold)
// 扩容
        resize();
// 在节点插入后做点什么事,在LinkedHashMap中用到
        afterNodeInsertion(evict);
// 没找到元素返回null
return null






请到「今天看啥」查看全文