選擇題
1、關於java的異常處理機制,以下說法正確的是:
當某個線程拋出OutOfMemoryError時,其他線程有可能不受影響
當大量拋出RuntimeException時,不會影響系統的吞吐量
java.lang.Exception是java.lang.Error的父類
finally塊代碼一定會被執行
finally塊代碼並不一定會執行,不是很懂java的try、catch、finally。
2、錯誤原因:512+256+128+...+1=1023,不要算錯
3、關於mysql,下面說法不正確的是:
mysql中的utf8類型最大只支持3個bytes
desc關鍵字可以作為table的字段名
filesort是通過讀取磁盤文件進行排序,會極大降低查詢性能
smallint占用2個bytes的存儲空間
utf8類型最大只支持3個bytes,utf8mb4類型最大只支持4個bytes。
filesort並非是對磁盤文件進行排序。好像是先對每個緩沖塊進行快速排序,然后對各個塊進行歸並排序。
5、查找表結構用以下哪一項?
DESC
mysql中,查詢表結構用DESC,可以顯示各列名以及各列的限制(是否主碼,是否非空)。
6、如何強制垃圾回收器立即回收一個對象?
調用System.gc()方法
調用Runtime.gc()方法
將對象賦值null
無法強制垃圾回收器立即執行
System.gc()只是提醒垃圾回收器執行,不是強制其執行。
7、關於sleep()和wait(),以下描述錯誤的一項是?
sleep是Thread類的方法,wait是Object類的方法;
sleep不釋放對象鎖,wait放棄對象鎖;
sleep暫停線程、但監控狀態仍然保持,結束后會自動恢復;
wait后進入等待鎖定池,只有針對此對象發出notify方法后獲得對象鎖進入運行狀態。
notifyAll方法也可以。
參考資料:sleep()和wait()的區別及wait方法的一點注意事項 - shiki0921 - 博客園
sleep是Thread類的方法,導致此線程暫停執行指定時間,給其他線程執行機會,但是依然保持着監控狀態,過了指定時間會自動恢復,調用sleep方法不會釋放鎖對象。
當調用sleep方法后,當前線程進入阻塞狀態。目的是讓出CPU給其他線程運行的機會。但是由於sleep方法不會釋放鎖對象,所以在一個同步代碼塊中調用這個方法后,線程雖然休眠了,但其他線程無法訪問它的鎖對象。這是因為sleep方法擁有CPU的執行權,它可以自動醒來無需喚醒。而當sleep結束指定休眠時間后,這個線程不一定立即執行,因為此時其他線程可能正在運行。
wait方法是Object類里的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池中,同時釋放了鎖對象,等待期間可以調用里面的同步方法,其他線程可以訪問,等待時不擁有CPU的執行權,否則其他線程無法獲取執行權。當一個線程執行了wait方法后,必須調用notify或者notifyAll方法才能喚醒,而且是隨機喚醒,若是被其他線程搶到了CPU執行權,該線程會繼續進入等待狀態。由於鎖對象可以是任意對象,所以wait方法必須定義在Object類中,因為Obeject類是所有類的基類。
8、下列協議中,將MAC地址轉為IP地址的協議是:
RARP
ARP(Address Resolution Protocol)地址解析協議,是根據IP地址獲取物理地址的一個TCP/IP協議。
RARP(Reverse Address Resolution Protocol)是反向地址解析協議。
10、在TCP/IP體系結構中,直接為ICMP提供服務的協議是:
IP
不過啥是ICMP呀。
11、下列關於進程和線程的敘述中,正確的是:
不管系統是否支持線程,進程都是資源分配的基本單位
線程是資源分配的基本單位,進程是調度的基本單位
系統級線程和用戶級線程的切換都需要內核的支持
同一進程中的各個線程擁有各自不同的地址空間
B選項:明顯線程是用來調度的,進程才是資源分配的。D選項:同一進程的線程貢獻這個進程的資源。
12、若某單處理器多進程系統中有多個就緒態進程,則下列關於處理機調度的敘述中,錯誤的是:
在進程結束時能進行處理機調度
創建新進程后能進行處理機調度
在進程處於臨界區時不能進行處理機調度
在系統調用完成並返回用戶態時能進行處理機調度
好像是這么理解:B選項:假如一開始沒有進程,那么創建新進程完成后就會進行處理機調度。C選項:有時,臨界區是類似進程訪問打印機之類的外設,在等待外設的過程中造成的。這個時候是可以進行處理機調度的。其他情況不清楚是否可以。
13、關於TCP協議的描述,以下錯誤的是?
面向連接
可提供多播服務
可靠交付
報文頭部長,傳輸開銷大
TCP是連接的、可靠的,UDP是無連接的,不可靠的。(TCP是一個虛擬連接,握手的過程就是為了建立虛擬連接,發送還需要對方進行確認,擁有擁塞控制。UDP是無連接的,只顧自己發送,一直盡力去做好,在一些需要實時性的傳輸中,UDP可以沒這么卡)
TCP僅支持單播,不能提供多播與廣播,每次是只面向連接的兩台設備的。
TCP加了很多東西,UDP只是簡單加了報文頭表示這個是UDP協議。
| UDP | TCP | |
|---|---|---|
| 是否連接 | 無連接 | 面向連接 |
| 是否可靠 | 不可靠傳輸,不使用流量控制和擁塞控制 | 可靠傳輸,使用流量控制和擁塞控制 |
| 連接對象個數 | 支持一對一,一對多,多對一和多對多交互通信 | 只能是一對一通信 |
| 傳輸方式 | 面向報文 | 面向字節流 |
| 首部開銷 | 首部開銷小,僅8字節 | 首部最小20字節,最大60字節 |
| 適用場景 | 適用於實時應用(IP電話、視頻會議、直播等) | 適用於要求可靠傳輸的應用(文件傳輸) |
參考資料:
TCP的可靠傳輸機制_網絡_sinat_28557957的博客-CSDN博客
TCP/IP詳解之:廣播和多播 - 墨城煙雨 - 博客園
怎么理解TCP的面向連接和UDP的無連接(不面向連接)?_網絡_infi-CSDN博客
主要看這個:一文搞懂TCP與UDP的區別 - Fundebug - 博客園
15、有關C++程序運行時的函數地址,下列說法正確的是:
每個函數的地址都是固定的,同一個程序重復運行多次,每次函數地址都一樣
每個函數的地址是不固定的,但在操作系統版本、硬件版本不變的情況下是固定的,同一個程序重復在相同軟硬件環境下運行多次,每次函數地址都一樣
地址是否固定要看系統配置和編譯選項,如果開啟了地址隨機化,那地址是每次都變的,如果沒開啟,那么地址每次都一樣
每個函數的地址都是不定的,同一個程序重復運行多次,每次地址都不同
好像地址隨機化是為了對抗反匯編的。默認是沒有的。
17、關於TCP協議狀態描述正確的是
只有執行主動關閉端才會出現TIME_WAIT
當接受到FIN報文時,會進入CLOSING狀態
數據傳輸完成后發送FIN報文后進入TIME_WAIT狀態
client和server端最終都會經歷TIME_WAIT狀態
參考上面的TCP的解除連接的四次揮手。
開始:雙方是建立連接狀態。
1、客戶端發送FIN,自己進入FIN_WAIT狀態。並停止自己發送新的數據。
2、服務端接收FIN,自己進入CLOSE_WAIT狀態,告訴應用層客戶端到服務端的連接已經停止,返回一個ACK。服務端若還有數據發送,客戶端依然需要接收。
3、客戶端接收ACK,然后還要等待服務端發送剩余的數據。
4、服務端發送數據完畢,發送FIN,自己進入LAST_ACK狀態,等待客戶端的ACK。
5、客戶端接收FIN,返回ACK,進入CLOSE_WAIT狀態,然后等待超時(2MSL,兩倍最長報文壽命),若沒有收到服務器繼續發送的FIN(意味着自己返回的ACK丟失了,要重發ACK),則關閉連接。
6、直到服務端收到ACK,關閉連接,否則持續發送FIN。
所以服務器關閉連接會早一點。
【問題1】為什么連接的時候是三次握手,關閉的時候卻是四次握手?
答:因為當Server端收到Client端的SYN連接請求報文后,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端所有的報文都發送完了,才能發送FIN報文,因此不能一起發送。故需要四次握手。
【問題2】為什么TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?
答:雖然按道理,四個報文都發送完畢,可以直接進入CLOSE狀態了,但是必須假想網絡是不可靠的,有可能最后一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文。在Client發送出最后的ACK回復,但該ACK可能丟失。Server如果沒有收到ACK,將不斷重復發送FIN片段。所以Client不能立即關閉,它必須確認Server接收到了該ACK。Client會在發送出ACK之后進入到TIME_WAIT狀態。Client會設置一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那么Client會重發ACK並再次等待2MSL。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個片段在網絡中最大的存活時間,2MSL就是一個發送和一個回復所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那么Client推斷ACK已經被成功接收,則結束TCP連接。
參考資料:(5條消息)TCP的三次握手與四次揮手理解及面試題(很全面)_網絡_lucky_jun-CSDN博客
18、關於epoll和select的區別,哪個說法是錯誤的?
epoll和select都是I/O多路復用的技術,都可以實現同時監聽多個I/O事件的狀態。
epoll相比select效率更高,主要是基於其操作系統支持的I/O事件通知機制,而select是基於輪詢機制。
epoll支持水平觸發和邊沿觸發兩種模式。
select能並行支持I/O比較小,且無法修改。
完全不懂這是什么。
編程題
21、尺取/二分
小明最近在做病毒自動檢測,他發現,在某些library 的代碼段的二進制表示中,如果包含子串並且恰好有k個1,就有可能有潛在的病毒。library的二進制表示可能很大,並且子串可能很多,人工分析不可能,於是他想寫個程序來先算算到底有多少個子串滿足條件。如果子串內容相同,但是開始或者結束位置不一樣,則被認為是不同的子串。
注:子串一定是連續的。例如"010"有6個子串,分別是 "0, "1", "0", "01", "10", "010"。
數據范圍:1<=n<=1e6, 0<=k<=1e6
題解:很明顯的一個尺取的題目,尺取的步驟是用l和r框住恰好k個1,然后數向左向右的全0分別有多少個。需要注意的是k=0的時候要另外處理。搞得好像很多細節的樣子,早知道直接二分就可以了,枚舉每個左端點,然后在前綴和上面二分找出差分為k的一段,這一段都是可行的。或者對連續的0進行壓縮,對於每個1,存放其右側的連續的0的數量。然后特殊處理一下第一個1的左側,每次就是k個1直接尺取,不需要太多情況。
char s[1000005];
void test_case() {
int n, k;
scanf("%d%s", &k, s + 1);
n = strlen(s + 1);
if(k > n) {
puts("0");
return;
}
if(k == 0) {
ll ans = 0;
int cnt = 0;
for(int i = 1; i <= n; ++i) {
if(s[i] == '0')
++cnt;
else {
ans += 1ll * cnt * (cnt + 1) / 2;
cnt = 0;
}
}
ans += 1ll * cnt * (cnt + 1) / 2;
printf("%lld\n", ans);
return;
}
int cnt = 0;
for(int i = 1; i <= n; ++i)
cnt += (s[i] == '1');
if(cnt < k) {
puts("0");
return;
}
ll ans = 0;
int l = 1, r = 0;
cnt = 0;
while(1) {
while(l <= n && s[l] == '0')
++l;
if(l > n)
break;
//printf("[%d,%d] cnt=%d\n", l, r, cnt);
if(r == 0) {
r = l;
cnt = 1;
}
//printf("[%d,%d] cnt=%d\n", l, r, cnt);
while(r + 1 <= n && cnt + (s[r + 1] == '1') < k) {
++r;
cnt += (s[r] == '1');
}
if(r + 1 <= n && s[r + 1] == '1' && cnt + 1 == k) {
++r;
cnt += (s[r] == '1');
}
//printf("[%d,%d] cnt=%d\n", l, r, cnt);
if(cnt == k) {
int pre0 = 0, suf0 = 0;
int pl = l - 1, pr = r + 1;
while(pl >= 1 && s[pl] == '0') {
++pre0;
--pl;
}
while(pr <= n && s[pr] == '0') {
++suf0;
++pr;
}
ans += 1ll * (pre0 + 1) * (suf0 + 1);
//printf("l=%d r=%d\n", pre0, suf0);
}
cnt -= (s[l] == '1');
++l;
}
printf("%lld\n", ans);
}
23、dp
給定一組石頭,每個石頭有一個正數的重量。每一輪開始的時候,選擇兩個石頭一起碰撞,假定兩個石頭的重量為x,y,x<=y,碰撞結果為:
- 如果x==y,碰撞結果為兩個石頭消失
- 如果x!=y,碰撞結果兩個石頭消失,生成一個新的石頭,新石頭重量為y-x
最終最多剩下一個石頭為結束。求解最小的剩余石頭質量是多少。
數據范圍:所有石頭的重量總和不超過10000。
題解:並不是每次取出最大的兩個貪心,這樣會WA,正解是dp。注意到消除石頭的過程,非常像是給其中小的那個加負號,大的那個加正號,然后整體作為一個結果放回去。意思就是最終只需要給這些數字定正負,然后求這個值的絕對值的最小值。可以用個滾動數組記錄dp[i][j]為前i個石頭是否能組合出重量j,dp完成之后,得到一個全部石頭的重量sum,對於可以組合出的重量j(dp[n][j]為true),就嘗試用abs(j-(sum-j))更新答案。
24、暴力
在你面前有n個蓄水池,他們組成了樹形結構(由n-1條邊連接)。蓄水池節點編號從1開始到n。對每個蓄水池節點來說,他的兒子蓄水池節點都擺放在他的下面,並且和它用水管相連,根據重力,水會向下流動。現在我們要在蓄水池上做一些操作:
- 把節點v填滿水。然后v的所有兒子節點水也會被填滿。
- 清空節點v的水。然后v所有的父親節點水都會被清空。
- 詢問每個蓄水池節點是否有水。
初始狀態時候,每個節點都是空的。
現在我們會依次進行一系列操作,我們想提前知道每次操作后的結果,你能幫忙解決嗎?
數據范圍:n<=1000
題解:看樣例瞎猜樹的根是1,然后寫個dfs暴力更新。假如n變得更大,樹剖的 \(O(nlog^2n)\) 肯定是可行的一種方案,但是有沒有可能進行樹上差分呢?感覺是不行的。
