大数据开发工程师面试《一》Shopee虾皮技术面

1、项目问题

1 作了哪些项目
2 使用什么技术
3 哪一个是你主导的项目,一共开发多少个接口,项目多长时间,数据库有多少个表html

2、技术问题

1 用本身擅长的语言实现非递归单链表反转 现场手写
2 Hadoop和spark的主要区别
3 Hadoop中一个大文件进行排序,如何保证总体有序?sort只会保证单个节点的数据有序
4 Hive中有哪些udf
5 Hadoop中文件put get的过程详细描述
6 Java中有哪些GC算法
7 Java中的弱引用 强引用和软引用分别在哪些场景中使用java

3、技术问题解析

1 用java实现非递归单链表反转node

思路:算法

由于在对链表进行反转的时候,须要更新每个node的“next”值,可是,在更新 next 的值前,咱们须要保存 next 的值,不然咱们没法继续。因此,咱们须要两个指针分别指向前一个节点和后一个节点,每次作完当前节点“next”值更新后,把两个节点往下移,直到到达最后节点。数据库

实现代码以下: 缓存

class Node {
    char value;
    Node next;
}

public Node reverse(Node current) {
    //initialization
    Node previousNode = null;
    Node nextNode = null;
    
    while (current != null) {
        //save the next node
        nextNode = current.next;
        //update the value of "next"
        current.next = previousNode;
        //shift the pointers
        previousNode = current;
        current = nextNode;            
    }
    return previousNode;

}

public class Test{
  public static void main(String[] args) {
    Node head = new Node(0);
    Node node1 = new Node(1);
    Node node2 = new Node(2);
    Node node3 = new Node(3);

    head.setNext(node1);
    node1.setNext(node2);
    node2.setNext(node3);

    // 打印反转前的链表
    Node h = head;
    while (null != h) {
      System.out.print(h.getData() + " ");
      h = h.getNext();
    }
    // 调用反转方法
    // head = reverse1(head);
    head = reverse(head);

    System.out.println("\n**************************");
    // 打印反转后的结果
    while (null != head) {
      System.out.print(head.getData() + " ");
      head = head.getNext();
    }
  }
}

 

2 Hadoop和spark的主要区别-这个问题基本都会问到框架

记住3点最重要的不一样之处:dom

  • spark消除了冗余的 HDFS 读写: Hadoop 每次 shuffle 操做后,必须写到磁盘,而 Spark 在 shuffle 后不必定落盘,能够 cache 到内存中,以便迭代时使用。若是操做复杂,不少的 shufle 操做,那么 Hadoop 的读写 IO 时间会大大增长,也是 Hive 更慢的主要缘由了。
  • spark消除了冗余的 MapReduce 阶段: Hadoop 的 shuffle 操做必定连着完整的 MapReduce 操做,冗余繁琐。而 Spark 基于 RDD 提供了丰富的算子操做,且 reduce 操做产生 shuffle 数据,能够缓存在内存中。
  • JVM 的优化: Hadoop 每次 MapReduce 操做,启动一个 Task 便会启动一次 JVM,基于进程的操做。而 Spark 每次 MapReduce 操做是基于线程的,只在启动 Executor 是启动一次 JVM,内存的 Task 操做是在线程复用的。每次启动 JVM 的时间可能就须要几秒甚至十几秒,那么当 Task 多了,这个时间 Hadoop 不知道比 Spark 慢了多。

3 Hadoop中一个大文件进行排序,如何保证总体有序函数

 在Spark中使用算子sortByKey()能够实现按关键字排序,那Hadoop中实现全排序呢?oop

咱们知道Mapreduce框架在feed数据给reducer以前会对map output key排序,这种排序机制保证了每个reducer局部有序,hadoop 默认的partitioner是HashPartitioner,它依赖于output key的hashcode,使得相同key会去相同reducer,可是不保证全局有序,若是想要得到全局排序结果(好比获取top N, bottom N),Hadoop提供TotalOrderPartitioner类用于实现全局排序的功能,而且解决了OOM和数据倾斜的问题。TotalOrderPartitioner 类提供了三个采样器,分别是:

  • SplitSampler 分片采样器,从数据分片中采样数据,该采样器不适合已经排好序的数据
  • RandomSampler随机采样器,按照设置好的采样率从一个数据集中采样
  • IntervalSampler间隔采样机,以固定的间隔从分片中采样数据,对于已经排好序的数据效果很是好。

具体实现能够参考https://zhuanlan.zhihu.com/p/43851100

 

4 Hive中有哪些UDF

Hive中有3种UDF:

  • UDF(User-Defined-Function)用户自定义函数,输入一个数据而后产生一个数据; 
  • UDAF(User-Defined Aggregation Function)用户自定义聚合函数,多个输入数据而后产生一个输出参数; 
  • UDTF(User-Defined Table-generating Function)用户自定义表生成函数,输入一行数据生成N行数据。

你写过哪些UDF?在哪一种状况下会使用该UDF?--本身能够扩展这个问题


5 Hadoop中数据读写流程分析,即文件在put get过程当中具体发生了什么

 i) hadoop fs -put 操做为例:

  • 当接收到 PUT 请求时,尝试在 NameNode 中 create 一个新的 INode 节点,这个节点是根据 create 中发送过去的 src 路径构建出的目标节点,若是发现节点已存在或是节点的 parent 存在且不为 INodeDirectory 则异常中断,不然则返回包含 INode 信息的 HdfsFileStatus 对象。

  • 使用 HdfsFileStatus 构造一个实现了 OutputStream 接口的 DFSOutputStream 类,经过 nio 接口将须要传输的数据写入 DFSOutputStream。

  • 在 DFSOutputStream 中写入的数据被以必定的 size(通常是 64 k)封装成一个 DFSPacket,压入 DataStreamer 的传输队列中。

  • DataStreamer 是 Client 中负责数据传输的独立线程,当发现队列中有 DFSPacket 时,先经过 namenode.addBlock 从 NameNode 中获取可供传输的 DataNode 信息,而后同指定的 DataNode 进行数据传输。

  • DataNode 中有一个专门的 DataXceiverServer 负责接收数据,当有数据到来时,就进行对应的 writeBlock 写入操做,同时若是发现还有下游的 DataNode 一样须要接收数据,就经过管道再次将发来的数据转发给下游 DataNode,实现数据的备份,避免经过 Client 一次进行数据发送。

整个操做步骤中的关键步骤有 NameNode::addBlock 以及 DataNode::writeBlock, 接下来会对这两步进行详细分析。

 ii) hadoop fs -get操做:

GET 操做的流程,相对于 PUT 会比较简单,先经过参数中的来源路径从 NameNode 对应 INode 中获取对应的 Block 位置,而后基于返回的 LocatedBlocks 构造出一个 DFSInputStream 对象。在 DFSInputStream 的 read 方法中,根据 LocatedBlocks 找到拥有 Block 的 DataNode 地址,经过 readBlock 从 DataNode 获取字节流。


6 Java中有哪些GC算法

现代虚拟机中的垃圾搜集算法:

  • 标记-清除
  • 复制算法(适合新生代)
  • 标记-压缩(适合老年代)
  • 分代收集(新生代采用复制算法,老年代采用标记-压缩算法)

标记 -清除算法

“标记-清除”(Mark-Sweep)算法,如它的名字同样,算法分为“标记”和“清除”两个阶段:首先标记出全部须要回收的对象,在标记完成后统一回收掉全部被标记的对象。之因此说它是最基础的收集算法,是由于后续的收集算法都是基于这种思路并对其缺点进行改进而获得的。

它的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另一个是空间问题,标记清除以后会产生大量不连续的内存碎片,空间碎片太多可能会致使,当程序在之后的运行过程当中须要分配较大对象时没法找到足够的连续内存而不得不提早触发另外一次垃圾收集动做。

复制算法

“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用过的内存空间一次清理掉。

这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂状况,只要移动堆顶指针,按顺序分配内存便可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,持续复制长生存期的对象则致使效率下降。

标记-压缩算法

复制收集算法在对象存活率较高时就要执行较多的复制操做,效率将会变低。更关键的是,若是不想浪费50%的空间,就须要有额外的空间进行分配担保,以应对被使用的内存中全部对象都100%存活的极端状况,因此在老年代通常不能直接选用这种算法。

根据老年代的特色,有人提出了另一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法同样,但后续步骤不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存

分代收集算法

GC分代的基本假设:绝大部分对象的生命周期都很是短暂,存活时间短。

“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就能够根据各个年代的特色采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少许存活,那就选用复制算法,只须要付出少许存活对象的复制成本就能够完成收集。而老年代中由于对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。


7 Java中的弱引用、强引用、软引用和虚引用是什么,他们分别在哪些场景中使用

  • 强引用(”Strong”Reference),咱们日常典型编码 Object obj=newObject() 中的obj就是强引用。经过关键字new建立的对象所关联的引用就是强引用强引用是使用最广泛的引用。若是一个对象具备强引用,那垃圾回收器毫不会回收它。当JVM 内存空间不足,JVM 宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具备强引用的“存活”对象来解决内存不足的问题。只要还有强引用指向一个对象,就能代表对象还“活着”,垃圾收集器不会碰这种对象。对于一个普通的对象,若是没有其余的引用关系,只要超出对象的生命周期范围或者显式地将相应(强)引用赋值为null,就是能够被垃圾收集的了,固然具体回收时机仍是要看垃圾收集策略。
  • 软引用(SoftReference),是一种相对强引用弱化一些的引用,可让对象豁免一些垃圾收集,只有当JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出OutOfMemoryError以前,清理软引用指向的对象。软引用一般用来实现内存敏感的缓存,若是还有空闲内存,就能够暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。软引用能够和一个引用队(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,咱们能够调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。若是队列为空,将返回一个null,不然该方法返回队列中前面的一个Reference对象。【应用场景】:软引用一般用来实现内存敏感的缓存。若是还有空闲内存,就能够暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
  • 弱引用经过WeakReference类实现。弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。因为垃圾回收器是一个优先级很低的线程,所以不必定会很快回收弱引用的对象。弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。【应用场景】:弱应用一样可用于内存敏感的缓存。
  • 虚引用,你不能经过它访问对象。幻象引用仅仅是提供了一种确保对象被finalize之后,作某些事情的机制。虚引用只是用来得知对象是否被GC。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之关联的引用队列中。【应用场景】:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收以前会收到一条系统通知。

经过表格来讲明一下,以下:

 

引用类型

被垃圾回收时间

   用途

   生存时间

强引用

历来不会

对象的通常状态

JVM中止运行时终止

软引用

在内存不足时

对象缓存

内存不足时终止

弱引用

在垃圾回收时

对象缓存

gc运行后终止

虚引用

任什么时候候

跟踪对象被垃圾回收器回收的活动

Unknown

 

 

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

原创文章,转载请务必将下面这段话置于文章开头处(保留超连接)。
本文转发自程序媛说事儿,原文连接http://www.javashuo.com/article/p-xkazofhw-ho.html=================================================================================

相关文章
相关标签/搜索