等待了一天拍鲤,雖然還沒(méi)有接收到正式的電話通知鲸鹦,但是經(jīng)過(guò)詢(xún)問(wèn)HR得知通過(guò)了作業(yè)部分,于是就開(kāi)始準(zhǔn)備一下明天的面試逛揩。
ThoughtWorks畢竟還是一家軟件公司柠傍。所以技術(shù)的廣度比較重要。經(jīng)過(guò)閱讀多篇面經(jīng)辩稽,也不準(zhǔn)備工作細(xì)分一下惧笛。
面試只要分為三部分,Coding面搂誉,技術(shù)面和Hr面徐紧。
自我介紹
XXX, 我應(yīng)聘的崗位是軟件開(kāi)發(fā)工程師。正如簡(jiǎn)歷上所見(jiàn)炭懊,在大學(xué)的前三年中并级,我分別用C語(yǔ)言和Python開(kāi)發(fā)了自己的項(xiàng)目,其中C語(yǔ)言開(kāi)發(fā)的Linux內(nèi)核相關(guān)的項(xiàng)目在2016年成功申請(qǐng)了Google校園合作項(xiàng)目并獲得1萬(wàn)元的經(jīng)費(fèi)資助侮腹。此外嘲碧,我還利用課余時(shí)間用Java實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的Bean工廠,實(shí)現(xiàn)了類(lèi)似與spring的AOP和IOC機(jī)制父阻,在Python項(xiàng)目開(kāi)發(fā)的過(guò)程總愈涩,利用Python元類(lèi)的機(jī)制實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的ORM框架。由于自己對(duì)于這些框架都有簡(jiǎn)易的實(shí)現(xiàn)加矛,所以我對(duì)整個(gè)Web開(kāi)發(fā)的流程還算比較熟悉履婉。目前我正在學(xué)習(xí)Linux下的網(wǎng)絡(luò)編程,嘗試使用Linux系統(tǒng)提供的API實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Http服務(wù)器斟览。
Codding面
能夠很清晰地講解出自己的設(shè)計(jì)思路毁腿,能快速根據(jù)新需求寫(xiě)好代碼。這一部分需要練習(xí)一下怎么講。
- 這是一個(gè)標(biāo)準(zhǔn)輸入已烤,輸出的程序鸠窗。輸入和輸出格式固定。
- 根據(jù)題目特點(diǎn)胯究,創(chuàng)建了三個(gè)抽象類(lèi) reader稍计,parser,printer裕循。reader負(fù)責(zé)讀取數(shù)據(jù)臣嚣,parser負(fù)責(zé)解析數(shù)據(jù),printer負(fù)責(zé)打印數(shù)據(jù)费韭。這三個(gè)類(lèi)都是抽象類(lèi)茧球,不關(guān)心具體實(shí)現(xiàn),具體實(shí)現(xiàn)可以多樣化星持。
技術(shù)面
我提交作業(yè)的語(yǔ)言是C++抢埋,但是我深知ThoughtWorks很有可能沒(méi)有C++崗位,和群碩一樣督暂,可能需要臨時(shí)轉(zhuǎn)換語(yǔ)言乔遮,所以這里要展示技術(shù)的廣度和深度辨赐。我就大體分為三個(gè)部分來(lái)準(zhǔn)備鳞绕,C++氧急,Java和Python。
C++:主要準(zhǔn)備網(wǎng)絡(luò)編程方面八回,包括socket酷愧,IO復(fù)用,進(jìn)程間通信缠诅,以及多線程等溶浴。
C++是我這次面試的主要語(yǔ)言,我想從幾個(gè)方面入手來(lái)準(zhǔn)備:
- C++基礎(chǔ)管引,主要設(shè)計(jì)內(nèi)存布局士败,多態(tài)繼承和其他基礎(chǔ)內(nèi)容;
- socket褥伴,客戶(hù)端和服務(wù)端的幾個(gè)API以及調(diào)用順序谅将,阻塞和非阻塞;
- IO復(fù)用重慢,主要了解Epoll的調(diào)用過(guò)程和兩種工作模式饥臂。進(jìn)程間通信,socket似踱,管道通信隅熙,共享內(nèi)存等志衣,信號(hào)量,環(huán)境變量猛们,信號(hào)等概念;
- 多線程狞洋,主要是同步鎖
- 網(wǎng)絡(luò)基礎(chǔ)弯淘,三次握手四次揮手〖茫滑動(dòng)窗口庐橙,Nagle算法等。
- 再?gòu)?fù)習(xí)幾個(gè)基礎(chǔ)算法和數(shù)據(jù)結(jié)構(gòu)借嗽,排序算法态鳖,紅黑樹(shù)等。
C++基礎(chǔ)部分
- 指針和引用的區(qū)別: 指針是一個(gè)變量恶导,引用是別名浆竭。指針可以初始化為空,引用必須初始化為具體的值惨寿。
- C++內(nèi)存布局邦泄。
普通類(lèi):類(lèi)成員函數(shù)不占用內(nèi)存空間(為什么?)
含有虛函數(shù):類(lèi)里面有虛函數(shù)時(shí)裂垦,會(huì)多維護(hù)一張?zhí)摫硭衬遥加靡粋€(gè)指針的空間
(以下為基類(lèi)中含有虛函數(shù)的情況)
單繼承:不管派生類(lèi)中有無(wú)虛函數(shù),都只維護(hù)一張?zhí)摫斫堵#绻缮?lèi)中有虛函數(shù)特碳,則虛表中回存在對(duì)應(yīng)的表項(xiàng)
菱形繼承(非虛繼承):派生類(lèi)中含有兩個(gè)虛指針,同時(shí)維護(hù)兩張?zhí)摫?br> 菱形繼承(虛繼承):中間層基類(lèi)不在重復(fù)晕换,但是會(huì)對(duì)出一個(gè)vbptr指針午乓,所以在最底層的派生類(lèi)中有兩個(gè)vbptr和一個(gè)vfptr,同時(shí)維護(hù)三張?zhí)摫怼?/li> - C++函數(shù)參數(shù)壓棧順序:從右向左届巩。this指針為C++成員函數(shù)的第一個(gè)參數(shù)硅瞧,存放在寄存器ECX中,以便快速訪問(wèn)恕汇。壓棧的過(guò)程中會(huì)先壓下一條指令的地址(返回地址)腕唧,保存現(xiàn)場(chǎng)。
socket部分
-
server端API:
socket() // 創(chuàng)建socketfd bind() // 綁定到本地套接字地址 listen() // 在指定端口監(jiān)聽(tīng)來(lái)自客戶(hù)端的請(qǐng)求 accept() // 接受請(qǐng)求瘾英,返回的套接字用于數(shù)據(jù)傳輸 recv() //接收數(shù)據(jù)(有阻塞非阻塞之分) send() // 發(fā)送數(shù)據(jù)
-
client端API:
socket() // 創(chuàng)建socketfd connect() //發(fā)起連接 send() // 發(fā)送數(shù)據(jù) recv() // 接收數(shù)據(jù)
其中三次握手發(fā)生在客戶(hù)端發(fā)起connect()時(shí)枣接,服務(wù)端accept()返回時(shí)結(jié)束
-
阻塞IO和非阻塞IO
通過(guò)簡(jiǎn)易的步驟可以把套接字設(shè)置城非阻塞類(lèi)型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; }
針對(duì)阻塞IO如果系統(tǒng)調(diào)用不能立即完成,當(dāng)前程序會(huì)被掛起缺谴,知道系統(tǒng)調(diào)用完成但惶,程序被再次喚醒。掛起期間不耗費(fèi)系統(tǒng)資源。阻塞的系統(tǒng)調(diào)用會(huì)導(dǎo)致CPU空閑時(shí)間過(guò)長(zhǎng)膀曾,CPU利用率低县爬。非阻塞IO系統(tǒng)調(diào)用不管有沒(méi)有完成都會(huì)立即返回一般非阻塞IO都會(huì)和IO復(fù)用一塊使用,提高CPU利用率添谊。
IO復(fù)用财喳。
select和poll都是輪詢(xún)的,效率偏低斩狱,現(xiàn)在準(zhǔn)要復(fù)習(xí)下Linux2.6以后內(nèi)核提供的epoll
epoll原理:當(dāng)調(diào)用epoll_create()時(shí)耳高,內(nèi)核會(huì)在cache區(qū)建立一個(gè)紅黑樹(shù),用于存儲(chǔ)被epoll管理的fd所踊,同時(shí)會(huì)建立一個(gè)鏈表泌枪,用于管理已就緒的事件。發(fā)生epoll_wait()系統(tǒng)調(diào)用時(shí)秕岛,就將內(nèi)核中的已就緒事件的鏈表拷貝到用戶(hù)空間碌燕。
LT和ET模式:LT模式是epoll的默認(rèn)工作模式。LT模式下瓣蛀,調(diào)用epoll_wait()后陆蟆,事件沒(méi)有被應(yīng)用程序處理也不會(huì)再通知。ET模式下惋增,發(fā)生epoll_wait()調(diào)用后叠殷,epoll還會(huì)檢查事件有沒(méi)有被處理,如果沒(méi)有被處理诈皿,還會(huì)加入到鏈表中林束。下一次調(diào)用epoll_wait()時(shí)會(huì)再次通知應(yīng)用程序。多進(jìn)程編程
信號(hào)量:信號(hào)量比較通俗的解釋是當(dāng)一個(gè)進(jìn)程要訪問(wèn)關(guān)鍵代碼段時(shí)稽亏,如果信號(hào)量為非0壶冒,則可以直接訪問(wèn),執(zhí)行P操作截歉,如果發(fā)現(xiàn)信號(hào)量為0胖腾,則將當(dāng)前進(jìn)程掛起。關(guān)鍵代碼段執(zhí)行完之后執(zhí)行V操作瘪松。
進(jìn)程間通信: socket(實(shí)踐過(guò))咸作, 管道通信(實(shí)踐過(guò)), 共享內(nèi)存宵睦。多線程編程:
線程同步: 信號(hào)量记罚, 互斥鎖。-
計(jì)算機(jī)網(wǎng)絡(luò):
三次握手:- 客戶(hù)端向服務(wù)端發(fā)送一個(gè)SYN壳嚎,用于同步序號(hào)桐智,SYN不攜帶數(shù)據(jù)末早,但是占用一個(gè)序號(hào)。
- 服務(wù)器發(fā)送第二個(gè)段说庭,包括一個(gè)SYN和一個(gè)ACK然磷,不攜帶數(shù)據(jù),但是占用一個(gè)序號(hào)
- 客戶(hù)端發(fā)送第三個(gè)段刊驴,僅僅是一個(gè)ACK样屠,如果不懈怠數(shù)據(jù)則不占用序號(hào)。
四次揮手
- 客戶(hù)端向服務(wù)端發(fā)送一個(gè)FIN缺脉,表示不再發(fā)送數(shù)據(jù)。
- 服務(wù)端收到后向客戶(hù)端發(fā)送一個(gè)ACK悦穿。此時(shí)服務(wù)端還可以向客戶(hù)端傳輸數(shù)據(jù)攻礼。
- 服務(wù)端發(fā)送完數(shù)據(jù)后,向客戶(hù)端發(fā)送一個(gè)FIN栗柒,表示不再傳輸數(shù)據(jù)礁扮。
- 客戶(hù)端接受后返回一個(gè)ACK,連接關(guān)閉瞬沦。
Nagle算法:
發(fā)送方綜合征太伊,如果應(yīng)用程序產(chǎn)生數(shù)據(jù)非常慢,不足以填充TCP的發(fā)送窗口逛钻,那么就會(huì)產(chǎn)生糊涂窗口綜合征僚焦。Nagle的解決方案是就算應(yīng)用程序只產(chǎn)生了一個(gè)字節(jié),也立即發(fā)送它曙痘,利用網(wǎng)絡(luò)傳輸?shù)臅r(shí)間延遲來(lái)抵消應(yīng)用程序產(chǎn)生數(shù)據(jù)的延遲芳悲。同樣的,接收方糊涂窗口綜合征還有一個(gè)對(duì)應(yīng)的Clark算法边坤。
TCP擁塞控制策略:
傳統(tǒng)的Taho狀態(tài)機(jī)中名扛,一共有兩個(gè)狀態(tài),慢啟動(dòng)和擁塞避免茧痒。慢啟動(dòng):慢啟動(dòng)階段肮韧,擁塞窗口大小指數(shù)上升,當(dāng)出現(xiàn)3個(gè)重復(fù)ACK的時(shí)候旺订,閥值設(shè)置為當(dāng)前擁塞窗口數(shù)量的一半弄企,擁塞窗口置為1,進(jìn)入擁塞避免階段耸峭。
擁塞避免:擁塞避免階段桩蓉,擁塞窗口加性上升當(dāng)超過(guò)三個(gè)以上重復(fù)ACK時(shí),閥值設(shè)置為當(dāng)前擁塞窗口數(shù)量的一半劳闹,擁塞窗口置為1院究,進(jìn)入慢啟動(dòng)階段洽瞬。
(在擁塞控制策略中,認(rèn)為收到3個(gè)重復(fù)ACK是輕微擁塞)
Java:主要反射機(jī)制业汰,曾將寫(xiě)過(guò)Bean工廠和MVC伙窃,重點(diǎn)了解一下IOC和AOP。
Spring的AOP機(jī)制只要通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)样漆,動(dòng)態(tài)代理通過(guò)實(shí)現(xiàn)
InvocationHandler
接口實(shí)現(xiàn)为障,相當(dāng)于通過(guò)這個(gè)這個(gè)接口重寫(xiě)了被代理類(lèi)的invoke方法
。所以在被代理對(duì)象發(fā)生方法調(diào)用時(shí)放祟,調(diào)用的就是重寫(xiě)的這個(gè)invoke方法鳍怨。
下面是一個(gè)動(dòng)態(tài)代理簡(jiǎn)單實(shí)現(xiàn):
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("**********動(dòng)態(tài)代理**************");
Object res;
System.out.println("準(zhǔn)備調(diào)用");
res = method.invoke(target, args);
System.out.println("已調(diào)用");
return res;
}
}
其中兩個(gè)println
語(yǔ)句在工程上都可以替換,返回值還是調(diào)用原方法得到的返回值跪妥。
下面給出測(cè)試代碼:
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無(wú)非就是兩個(gè)概念鞋喇,一個(gè)是依賴(lài)注入,一個(gè)是控制反轉(zhuǎn)眉撵。
控制反轉(zhuǎn)就是程序之間的關(guān)系本來(lái)由代碼控制侦香,在spring中,程序之間的關(guān)系由容器來(lái)控制纽疟,也就是在XML文件中配置類(lèi)之間的關(guān)系罐韩。
依賴(lài)注入
在javaweb中,Service層是以來(lái)Dao層的污朽,如果不用依賴(lài)注入散吵,那么在每個(gè)Service類(lèi)中都需要new一個(gè)Dao出來(lái)。如果使用依賴(lài)注入蟆肆,只需要在XML配置文件中配置兩個(gè)類(lèi)之間的關(guān)系即可错蝴。
Python:裝飾器和元類(lèi)等高級(jí)特性,ORM框架颓芭。
type()函數(shù)可以動(dòng)態(tài)創(chuàng)建類(lèi)顷锰。metaclass可以攔截類(lèi)的創(chuàng)建過(guò)程
class ListMetaClass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
如果定義了元類(lèi),那么類(lèi)的創(chuàng)建過(guò)程中都會(huì)調(diào)用new()函數(shù)亡问,name
代表類(lèi)名官紫,bases
代表父類(lèi),Python支持多重繼承州藕,所以bases是一個(gè)元組束世。attrs
是一個(gè)dict,存儲(chǔ)類(lèi)成員變量和成員函數(shù)的名稱(chēng)和地址的映射床玻。通過(guò)attrs
屬性可以在類(lèi)創(chuàng)建期動(dòng)態(tài)添加屬性毁涉。ORM框架就是在類(lèi)的創(chuàng)建期根據(jù)類(lèi)名和成員變量名來(lái)拼接sql。
利用以上元類(lèi)可以創(chuàng)建一個(gè)MyList類(lèi)锈死,并動(dòng)態(tài)添加一個(gè)add方法:
class MyList(list, metaclass = ListMetaClass):
pass
還有項(xiàng)目部分:XXX
Hr面:
主要設(shè)計(jì)到企業(yè)文化贫堰,暫時(shí)想到的有開(kāi)源分享精神和公益穆壕。還有HR問(wèn)到的個(gè)人問(wèn)題也要準(zhǔn)備下。