等待了一天,虽然还没有接收到正式的电话通知,但是经过询问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面
能够很清晰地讲解出自己的设计思路,能快速根据新需求写好代码。这一部分需要练习一下怎么讲。
- 这是一个标准输入,输出的程序。输入和输出格式固定。
- 根据题目特点,创建了三个抽象类 reader,parser,printer。reader负责读取数据,parser负责解析数据,printer负责打印数据。这三个类都是抽象类,不关心具体实现,具体实现可以多样化。
技术面
我提交作业的语言是C++,但是我深知ThoughtWorks很有可能没有C++岗位,和群硕一样,可能需要临时转换语言,所以这里要展示技术的广度和深度。我就大体分为三个部分来准备,C++,Java和Python。
C++:主要准备网络编程方面,包括socket,IO复用,进程间通信,以及多线程等。
C++是我这次面试的主要语言,我想从几个方面入手来准备:
- C++基础,主要设计内存布局,多态继承和其他基础内容;
- socket,客户端和服务端的几个API以及调用顺序,阻塞和非阻塞;
- IO复用,主要了解Epoll的调用过程和两种工作模式。进程间通信,socket,管道通信,共享内存等,信号量,环境变量,信号等概念;
- 多线程,主要是同步锁
- 网络基础,三次握手四次挥手。滑动窗口,Nagle算法等。
- 再复习几个基础算法和数据结构,排序算法,红黑树等。
C++基础部分
- 指针和引用的区别: 指针是一个变量,引用是别名。指针可以初始化为空,引用必须初始化为具体的值。
- C++内存布局。
普通类:类成员函数不占用内存空间(为什么?)
含有虚函数:类里面有虚函数时,会多维护一张虚表,占用一个指针的空间
(以下为基类中含有虚函数的情况)
单继承:不管派生类中有无虚函数,都只维护一张虚表,如果派生类中有虚函数,则虚表中回存在对应的表项
菱形继承(非虚继承):派生类中含有两个虚指针,同时维护两张虚表
菱形继承(虚继承):中间层基类不在重复,但是会对出一个vbptr指针,所以在最底层的派生类中有两个vbptr和一个vfptr,同时维护三张虚表。 - C++函数参数压栈顺序:从右向左。this指针为C++成员函数的第一个参数,存放在寄存器ECX中,以便快速访问。压栈的过程中会先压下一条指令的地址(返回地址),保存现场。
socket部分
-
server端API:
socket() // 创建socketfd bind() // 绑定到本地套接字地址 listen() // 在指定端口监听来自客户端的请求 accept() // 接受请求,返回的套接字用于数据传输 recv() //接收数据(有阻塞非阻塞之分) send() // 发送数据
-
client端API:
socket() // 创建socketfd connect() //发起连接 send() // 发送数据 recv() // 接收数据
其中三次握手发生在客户端发起connect()时,服务端accept()返回时结束
-
阻塞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利用率。
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()时会再次通知应用程序。多进程编程
信号量:信号量比较通俗的解释是当一个进程要访问关键代码段时,如果信号量为非0,则可以直接访问,执行P操作,如果发现信号量为0,则将当前进程挂起。关键代码段执行完之后执行V操作。
进程间通信: socket(实践过), 管道通信(实践过), 共享内存。多线程编程:
线程同步: 信号量, 互斥锁。-
计算机网络:
三次握手:- 客户端向服务端发送一个SYN,用于同步序号,SYN不携带数据,但是占用一个序号。
- 服务器发送第二个段,包括一个SYN和一个ACK,不携带数据,但是占用一个序号
- 客户端发送第三个段,仅仅是一个ACK,如果不懈怠数据则不占用序号。
四次挥手
- 客户端向服务端发送一个FIN,表示不再发送数据。
- 服务端收到后向客户端发送一个ACK。此时服务端还可以向客户端传输数据。
- 服务端发送完数据后,向客户端发送一个FIN,表示不再传输数据。
- 客户端接受后返回一个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问到的个人问题也要准备下。