ThoughtWorks面试准备

等待了一天,虽然还没有接收到正式的电话通知,但是经过询问HR得知通过了作业部分,于是就开始准备一下明天的面试。
ThoughtWorks毕竟还是一家软件公司。所以技术的广度比较重要。经过阅读多篇面经,也不准备工作细分一下。
面试只要分为三部分,Coding面,技术面和Hr面。

自我介绍

XXX, 我应聘的岗位是软件开发工程师。正如简历上所见,在大学的前三年中,我分别用C语言和Python开发了自己的项目,其中C语言开发的Linux内核相关的项目在2016年成功申请了Google校园合作项目并获得1万元的经费资助。此外,我还利用课余时间用Java实现了一个简易的Bean工厂,实现了类似与spring的AOP和IOC机制,在Python项目开发的过程总,利用Python元类的机制实现了一个简易的ORM框架。由于自己对于这些框架都有简易的实现,所以我对整个Web开发的流程还算比较熟悉。目前我正在学习Linux下的网络编程,尝试使用Linux系统提供的API实现一个简易的Http服务器。

Codding面

能够很清晰地讲解出自己的设计思路,能快速根据新需求写好代码。这一部分需要练习一下怎么讲。

  1. 这是一个标准输入,输出的程序。输入和输出格式固定。
  2. 根据题目特点,创建了三个抽象类 reader,parser,printer。reader负责读取数据,parser负责解析数据,printer负责打印数据。这三个类都是抽象类,不关心具体实现,具体实现可以多样化。

技术面

我提交作业的语言是C++,但是我深知ThoughtWorks很有可能没有C++岗位,和群硕一样,可能需要临时转换语言,所以这里要展示技术的广度和深度。我就大体分为三个部分来准备,C++,Java和Python。


C++:主要准备网络编程方面,包括socket,IO复用,进程间通信,以及多线程等。

C++是我这次面试的主要语言,我想从几个方面入手来准备:

  1. C++基础,主要设计内存布局,多态继承和其他基础内容;
  2. socket,客户端和服务端的几个API以及调用顺序,阻塞和非阻塞;
  3. IO复用,主要了解Epoll的调用过程和两种工作模式。进程间通信,socket,管道通信,共享内存等,信号量,环境变量,信号等概念;
  4. 多线程,主要是同步锁
  5. 网络基础,三次握手四次挥手。滑动窗口,Nagle算法等。
  6. 再复习几个基础算法和数据结构,排序算法,红黑树等。

C++基础部分

  1. 指针和引用的区别: 指针是一个变量,引用是别名。指针可以初始化为空,引用必须初始化为具体的值。
  2. C++内存布局。
    普通类:类成员函数不占用内存空间(为什么?)
    含有虚函数:类里面有虚函数时,会多维护一张虚表,占用一个指针的空间
    (以下为基类中含有虚函数的情况)
    单继承:不管派生类中有无虚函数,都只维护一张虚表,如果派生类中有虚函数,则虚表中回存在对应的表项
    菱形继承(非虚继承):派生类中含有两个虚指针,同时维护两张虚表
    菱形继承(虚继承):中间层基类不在重复,但是会对出一个vbptr指针,所以在最底层的派生类中有两个vbptr和一个vfptr,同时维护三张虚表。
  3. C++函数参数压栈顺序:从右向左。this指针为C++成员函数的第一个参数,存放在寄存器ECX中,以便快速访问。压栈的过程中会先压下一条指令的地址(返回地址),保存现场。

socket部分

  1. server端API:

    socket()        // 创建socketfd
    bind()            // 绑定到本地套接字地址
    listen()          // 在指定端口监听来自客户端的请求
    accept()        // 接受请求,返回的套接字用于数据传输
    recv()           //接收数据(有阻塞非阻塞之分) 
    send()          // 发送数据  
    
  2. client端API:

    socket()        // 创建socketfd
    connect()      //发起连接
    send()           // 发送数据
    recv()           // 接收数据
    

    其中三次握手发生在客户端发起connect()时,服务端accept()返回时结束

  3. 阻塞IO和非阻塞IO
    通过简易的步骤可以把套接字设置城非阻塞类型

    int set_nonblocking(int fd)
    {
        int old_option = fcntl(fd, F_GETFL);
        int new_option = old_option | O_NONBLOCK;
        fcntl(fd, F_SETFL, new_option);
        return old_option;
    }
    

    针对阻塞IO如果系统调用不能立即完成,当前程序会被挂起,知道系统调用完成,程序被再次唤醒。挂起期间不耗费系统资源。阻塞的系统调用会导致CPU空闲时间过长,CPU利用率低。非阻塞IO系统调用不管有没有完成都会立即返回一般非阻塞IO都会和IO复用一块使用,提高CPU利用率。

  4. IO复用。
    select和poll都是轮询的,效率偏低,现在准要复习下Linux2.6以后内核提供的epoll
    epoll原理:当调用epoll_create()时,内核会在cache区建立一个红黑树,用于存储被epoll管理的fd,同时会建立一个链表,用于管理已就绪的事件。发生epoll_wait()系统调用时,就将内核中的已就绪事件的链表拷贝到用户空间。
    LT和ET模式:LT模式是epoll的默认工作模式。LT模式下,调用epoll_wait()后,事件没有被应用程序处理也不会再通知。ET模式下,发生epoll_wait()调用后,epoll还会检查事件有没有被处理,如果没有被处理,还会加入到链表中。下一次调用epoll_wait()时会再次通知应用程序。

  5. 多进程编程
    信号量:信号量比较通俗的解释是当一个进程要访问关键代码段时,如果信号量为非0,则可以直接访问,执行P操作,如果发现信号量为0,则将当前进程挂起。关键代码段执行完之后执行V操作。
    进程间通信: socket(实践过), 管道通信(实践过), 共享内存。

  6. 多线程编程:
    线程同步: 信号量, 互斥锁。

  7. 计算机网络:
    三次握手:

    1. 客户端向服务端发送一个SYN,用于同步序号,SYN不携带数据,但是占用一个序号。
    2. 服务器发送第二个段,包括一个SYN和一个ACK,不携带数据,但是占用一个序号
    3. 客户端发送第三个段,仅仅是一个ACK,如果不懈怠数据则不占用序号。

    四次挥手

    1. 客户端向服务端发送一个FIN,表示不再发送数据。
    2. 服务端收到后向客户端发送一个ACK。此时服务端还可以向客户端传输数据。
    3. 服务端发送完数据后,向客户端发送一个FIN,表示不再传输数据。
    4. 客户端接受后返回一个ACK,连接关闭。

    Nagle算法:
    发送方综合征,如果应用程序产生数据非常慢,不足以填充TCP的发送窗口,那么就会产生糊涂窗口综合征。Nagle的解决方案是就算应用程序只产生了一个字节,也立即发送它,利用网络传输的时间延迟来抵消应用程序产生数据的延迟。同样的,接收方糊涂窗口综合征还有一个对应的Clark算法。
    TCP拥塞控制策略:
    传统的Taho状态机中,一共有两个状态,慢启动和拥塞避免。

    慢启动:慢启动阶段,拥塞窗口大小指数上升,当出现3个重复ACK的时候,阀值设置为当前拥塞窗口数量的一半,拥塞窗口置为1,进入拥塞避免阶段。
    拥塞避免:拥塞避免阶段,拥塞窗口加性上升当超过三个以上重复ACK时,阀值设置为当前拥塞窗口数量的一半,拥塞窗口置为1,进入慢启动阶段。
    (在拥塞控制策略中,认为收到3个重复ACK是轻微拥塞)


Java:主要反射机制,曾将写过Bean工厂和MVC,重点了解一下IOC和AOP。

Spring的AOP机制只要通过动态代理实现,动态代理通过实现 InvocationHandler接口实现,相当于通过这个这个接口重写了被代理类的invoke方法。所以在被代理对象发生方法调用时,调用的就是重写的这个invoke方法。

下面是一个动态代理简单实现:

class HelloServiceProxy implements InvocationHandler {

    private Object target;

    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("**********动态代理**************");
        Object res;

        System.out.println("准备调用");
        res = method.invoke(target, args);
        System.out.println("已调用");

        return res;
    }
}

其中两个println语句在工程上都可以替换,返回值还是调用原方法得到的返回值。
下面给出测试代码:

public interface HelloService {
    void say();
}
public class HelloServiceImpl implements HelloService {
    @Override
    public void say() {
        System.out.println("Hello Service impl");
    }
}

public static void main(String[] args) {
    HelloServiceProxy serviceProxy = new HelloServiceProxy();
    HelloService helloService = new HelloServiceImpl();
    helloService = (HelloService)serviceProxy.bind(helloService);
    helloService.say();
}

IOC
IOC无非就是两个概念,一个是依赖注入,一个是控制反转。
控制反转就是程序之间的关系本来由代码控制,在spring中,程序之间的关系由容器来控制,也就是在XML文件中配置类之间的关系。
依赖注入
在javaweb中,Service层是以来Dao层的,如果不用依赖注入,那么在每个Service类中都需要new一个Dao出来。如果使用依赖注入,只需要在XML配置文件中配置两个类之间的关系即可。


Python:装饰器和元类等高级特性,ORM框架。

type()函数可以动态创建类。metaclass可以拦截类的创建过程

class ListMetaClass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

如果定义了元类,那么类的创建过程中都会调用new()函数,name代表类名,bases代表父类,Python支持多重继承,所以bases是一个元组。attrs是一个dict,存储类成员变量和成员函数的名称和地址的映射。通过attrs属性可以在类创建期动态添加属性。ORM框架就是在类的创建期根据类名和成员变量名来拼接sql。

利用以上元类可以创建一个MyList类,并动态添加一个add方法:

class MyList(list, metaclass = ListMetaClass):
    pass

还有项目部分:XXX

Hr面:
主要设计到企业文化,暂时想到的有开源分享精神和公益。还有HR问到的个人问题也要准备下。

你可能感兴趣的:(ThoughtWorks面试准备)