大数据开发面试的总结-第二篇

(1)反转二叉树的实现;
可用递归实现,代码包含递归,根节点作为遍历条件,左右子节点分别进行兑换。

class Node(object):                #定义树的结点
    def __init__(self, data=-1, lchild=None, rchild=None):
        self.data = data
        self.lchild = lchild
        self.rchild = rchild
class Binsearchtree(object):
    def __init__(self, data):
        self.root = Node(data) 
    def inverttree(self, treenode):      #真正的翻转只有这8行代码
        if treenode == None:
            return None
        temp = treenode.lchild
        treenode.lchild = treenode.rchild
        treenode.rchild = temp
        self.inverttree(treenode.lchild)

树的翻转其实就是递归遍历每一个结点,把所有结点的左孩子和右孩子互换,因为没有定义的孩子的data为None,所以只有一个孩子的结点也不会出现问题
树的结构如下:
在这里插入图片描述

(2)两个文本文件A和文本B,A中的每一行去B中查找与之相似度最大的N条;
思路:针对A,B文件,选取其中一个相对较小的文件进行广播join,例如广播A去joinB,然后根据计算的每一个部分的相似度进行排序,取前N条,最终再进行全排序;(这个地方是先局部有序,再全局有序取最大的N条)

扩展:还有一种划分分区的方式,暂时还未用到这种场景:针对每一个分区,实现总体在每个分区上的边界是有序的,分区内部不一定有序–先从总体再到局部的思想,当然此时的分区边界值元素即是每个分区的最大值;

(3) 给定一列,如何取该数的第2大的数,第n大的数呢?(高频,好几家公司的面试官都有考这个)
有很多种思路(涉及通过查询或删除的方式):
思路1:先查询出最大,然后查询小于该值的最大值;
思路2:先查询出最大的,去掉该最大值,然后将数据进行排序(降序),查询出当前的第一条数据。
思路3: 给每一个记录一个行号,使用RANK函数,去掉身高值最高的记录(重复值的rank函数获得值相同)。
其中rank函数考虑到了over子句中排序字段值相同的情况,值相同的时候RANK函数的到的值相等。

查询出可能与最大相同的第二名
思路1:将数据按照(降序)排列,查询出前面的两项,然后将这两项按照身高升序,查询第一项目。
思路2:=给每一个记录一个行号ROW_NUMBER使用函数,去掉最大的记录(重复值的ROW_NUMBER函数获得值不同)。

(4)排序算法,熟悉哪些?哪些是稳定的;
快排:不稳定,Nlog(N)
二分类排序:稳定,Nlog(N)

(5)hive sql和spark sql的区别
hive sql底层是基于mapreduce框架计算实现的,mapreduce框架与spark计算框架的区别:
Hadoop MapReduce采用了多进程模型,而Spark采用了多线程模型:

Apache Spark的高性能一定程度上取决于它采用的异步并发模型(这里指server/driver 端采用的模型),这与Hadoop 2.0(包括YARN和MapReduce)是一致的。Hadoop 2.0自己实现了类似Actor的异步并发模型,实现方式是epoll+状态机,而Apache Spark则直接采用了开源软件Akka,该软件实现了Actor模型,性能非常高。尽管二者在server端采用了一致的并发模型,但在任务级别(特指 Spark任务和MapReduce任务)上却采用了不同的并行机制:Hadoop MapReduce采用了多进程模型,而Spark采用了多线程模型。

注意,本文的多进程和多线程,指的是同一个节点上多个任务的运行模 式。无论是MapReduce和Spark,整体上看,都是多进程:MapReduce应用程序是由多个独立的Task进程组成的;Spark应用程序的 运行环境是由多个独立的Executor进程构建的临时资源池构成的。

多进程模型便于细粒度控制每个任务占用的资源,但会消耗较多的启动时间,不适合运行低延迟类型的作业,这是MapReduce广为诟病的原因之一。而多线程模型则相反,该模型使得Spark很适合运行低延迟类型的作业。总之,Spark同节点上的任务以多线程的方式运行在一个JVM进程中,可带来以下好处:

1)任务启动速度快,与之相反的是MapReduce Task进程的慢启动速度,通常需要1s左右;

2)同节点上所有任务运行在一个进程中,有利于共享内存。这非常适合内存密集型任务,尤其对于那些需要加载大量词典的应用程序,可大大节省内存。

3) 同节点上所有任务可运行在一个JVM进程(Executor)中,且Executor所占资源可连续被多批任务使用,不会在运行部分任务后释放掉,这避免 了每个任务重复申请资源带来的时间开销,对于任务数目非常多的应用,可大大降低运行时间。与之对比的是MapReduce中的Task:每个Task单独 申请资源,用完后马上释放,不能被其他任务重用,尽管1.0支持JVM重用在一定程度上弥补了该问题,但2.0尚未支持该功能。

尽管Spark的过线程模型带来了很多好处,但同样存在不足,主要有:

1)由于同节点上所有任务运行在一个进程中,因此,会出现严重的资源争用,难以细粒度控制每个任务占用资源。与之相 反的是MapReduce,它允许用户单独为Map Task和Reduce Task设置不同的资源,进而细粒度控制任务占用资源量,有利于大作业的正常平稳运行。

下面简要介绍MapReduce的多进程模型和Spark的多线程模型。

MapReduce多进程模型

mapreduce采用多进程与spark采用多线程比较

1) 每个Task运行在一个独立的JVM进程中;

2) 可单独为不同类型的Task设置不同的资源量,目前支持内存和CPU两种资源;

3) 每个Task运行完后,将释放所占用的资源,这些资源不能被其他Task复用,即使是同一个作业相同类型的Task。也就是说,每个Task都要经历“申请资源—> 运行Task –> 释放资源”的过程。

Spark多线程模型

mapreduce采用多进程与spark采用多线程比较

1) 每个节点上可以运行一个或多个Executor服务;

2) 每个Executor配有一定数量的slot,表示该Executor中可以同时运行多少个ShuffleMapTask或者ReduceTask;

3) 每个Executor单独运行在一个JVM进程中,每个Task则是运行在Executor中的一个线程;

4) 同一个Executor内部的Task可共享内存,比如通过函数SparkContext#broadcast广播的文件或者数据结构只会在每个Executor中加载一次,而不会像MapReduce那样,每个Task加载一次;

5) Executor一旦启动后,将一直运行,且它的资源可以一直被Task复用,直到Spark程序运行完成后才释放退出。

总体上看,Spark采用的是经典的scheduler/workers模式, 每个Spark应用程序运行的第一步是构建一个可重用的资源池,然后在这个资源池里运行所有的ShuffleMapTask和ReduceTask(注 意,尽管Spark编程方式十分灵活,不再局限于编写Mapper和Reducer,但是在Spark引擎内部只用两类Task便可表示出一个复杂的应用 程序,即ShuffleMapTask和ReduceTask),而MapReduce应用程序则不同,它不会构建一个可重用的资源池,而是让每个 Task动态申请资源,且运行完后马上释放资源。

(6)设计模式,他们的实现方式;
传统课本上单例模式分两种,一种饿汉式,一种懒汉式。对应的代码如下:
懒汉式
/**

  • 懒汉模式

  • 单例实例在第一次使用时进行创建
    */
    public class SingletonExample1 {

    // 私有构造函数
    private SingletonExample1() {

    }

    // 单例对象
    private static SingletonExample1 instance = null;

    // 静态的工厂方法
    public static SingletonExample1 getInstance() {
    if (instance == null) {
    instance = new SingletonExample1();
    }
    return instance;
    }
    }
    懒汉式的实例是在第一次使用时创建的,相应的静态工厂办法会先判断有没有实例,没有实例在进行创建。
    然而这种创建方法时线程不安全的,如果有两个线程,同一时刻拿到单例对象,要去静态工厂办法访问,由于工厂办法没有锁,那么很有可能这两个线程最终会拿到两个实例。
    饿汉式
    /**

  • 饿汉模式

  • 单例实例在类装载时进行创建
    */
    public class SingletonExample2 {

    // 私有构造函数
    private SingletonExample2() {

    }

    // 单例对象
    private static SingletonExample2 instance = new SingletonExample2();

    // 静态的工厂方法
    public static SingletonExample2 getInstance() {
    return instance;
    }
    }
    相对于上面那种懒汉式,饿汉式是线程安全的。直接将单例对象用static修饰,把实例对象放到堆内存中,保证了多个线程在访问时的可见性。但是缺点也是很大的,正是由于把实例对象放到堆内存中,这样应用一加载就会看到对应实例,极大浪费内存。

(7)spark job的数量,stage的数量,以及task的数量是怎么确定的;(描述他们的流程:从job提交到task任务的分配的过程)

首先job的划分是遇到action操作时,被发现后经过sparkcontext的runjob方法来到DAGscheduler,这个类中它会通过依赖关系划分出stage,一个stage是一个taskset,里面的每个task对应着rdd的一个分区。task可以理解为并行的分片。一个job任务可以有一个或多个stage,一个stage又可以有一个或多个task。所以一个job的task数量是 (stage数量 * task数量)的总和。

扩展:
并行度:是指指令并行执行的最大条数。在指令流水中,同时执行多条指令称为指令并行。

理论上:每一个stage下有多少的分区,就有多少的task,task的数量就是我们任务的最大的并行度。

(一般情况下,我们一个task运行的时候,使用一个cores)

实际上:最大的并行度,取决于我们的application任务运行时使用的executor拥有的cores的数量。

(8)实际编程题
主要涉及实现效率,以及注意边界条件判断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值