1. 程式人生 > 其它 >golang北京小廠面試覆盤

golang北京小廠面試覆盤

下面是一家小廠的golang研發崗面試題,薪酬範圍15k-20k,面試時間是2022-3-12,希望能幫到大家


 

1.自報家門部分

面試官:做一下自我介紹,做過golang的專案的?簡單聊聊專案

參考:無

 


 

2.golang基礎部分

面試官:golang的資料型別有哪些?

參考:go分基礎資料型別和派生資料型別。基礎資料型別有數字型(int,uint,float,complex,rune(類似int32,常用作處理漢字),byte(類似uint8),uintptr(存放一個指標),字元型,布林型,字串型,派生資料型別有指標,陣列,結構體,管道,函式(居然也是資料型別),介面,切片,map。

補充:uintptr和unsafe.Pointer的區別

  • unsafe.Pointer型別可以和任意指標型別互轉
  • uintptr可以做指標運算,這一點有時候很重要,但是依賴平臺,同一型別變數在不同的平臺佔用的儲存空間大小不一樣,在用uintptr做指標運算的時候,偏移量也會相應的不一樣(後面有例子說明)。
type MyStruct struct {
    i int
    j int
}

func myFunction(ms *MyStruct) {
    ptr := unsafe.Pointer(ms)
    for i := 0; i < 2; i++ {
        c := (*int)(unsafe.Pointer((uintptr(ptr) + uintptr(8*i))))
        *c += i + 1
        fmt.Printf("[%p] %d\n", c, *c)
    }
}

func main() {
    a := &MyStruct{i: 40, j: 50}
    myFunction(a)
    fmt.Printf("[%p] %v\n", a, a)
}

  執行結果:

 

面試官:切片和陣列的區別是什麼?

參考:1.陣列是定長的,切片可以擴從長度

2.陣列是值型別,切片是引用型別,切片的底層是陣列,切片底層的資料結構有三個欄位,分別是陣列指標,len,cap。

雖然go只有值傳遞,但是切片由於存放了陣列指標,所以傳切片時,底層陣列的改變會帶動切片本身的改變

3.數字宣告完就可以使用,而切片需要內建函式make()後才會分配記憶體

面試官:結構體可以比較嗎?

參考:分情況,如果結構體中不含有不可比較的資料型別,那麼結構體就可以比較,反之亦然。其中,

不可比較的資料型別有:SliceMapFunction

可比較的資料型別有:Integer

Floating-pointStringBooleanComplex(複數型)PointerChannel(小心這個)InterfaceArray

面試官:那不可比較的結構體有沒有什麼方法比較呢?

參考:可以使用 reflect.DeepEqual 進行比較,DeepEqual函式用來判斷兩個值是否深度一致,不同型別的值永遠不會深度相等。

參考連結:https://www.cnblogs.com/dashu-saycode/p/14286228.html

面試官:map為什麼無序?什麼方法可以變得有序?

參考:map無序有兩點原因:1.map在遍歷時,並不是從0號桶開始遍歷的,每次遍歷會從隨機一個桶的隨機一個cell開始遍歷

2.map遍歷時,按序遍歷bucket,同時按序遍歷buckct中和其overflow bucket中的cell,但是map擴容後,會發生key的搬遷,這造成原來在某個bucket中的key可能去了別的bucket,因此,mao遍歷無序

什麼方法變得有序:利用slice對map的key排序,而後進行遍歷

面試官:map是執行緒安全的嗎?

參考:不是,map和slice都不是執行緒安全的,而且多個執行緒同時訪問時,map會報錯(slice不會),如果想要實現執行緒安全,可以使用讀寫鎖,

var counter = struct{
    sync.RWMutex
    m map[string]int
}{m: make(map[string]int)}

counter.RLock()
n := counter.m["煎魚"]
counter.RUnlock()
fmt.Println("煎魚:", n)

  也可以使用sync.Map,它採取了 “空間換時間” 的機制,冗餘了兩個資料結構,分別是:read 和 dirty,減少加鎖對效能的影響。他適合讀多寫少的場景,若出現寫多/併發多的場景,會衝突變多,效能急劇下降。

面試官:瞭解defer嗎?

參考:defer常用作資料庫連線,檔案控制代碼關閉,他會逆序執行(類似於棧),並在函式退出時釋放資源

面試官:瞭解select嗎?select和switch有什麼區別?

參考:

  1. select語句只能用於通道的讀寫操作,
  2. 每個 case 都必須是一個通訊
  3. select中的case條件(非阻塞)是併發執行的,如果多個case同時滿足,公平的選擇一個,其他被忽略。如果一個也沒有滿足,進入default,沒有default,select 將阻塞。
  4. 對於空的select{},會引起死鎖,對於for中的select{}, 也有可能會引起cpu佔用過高的問題,對於case條件語句中,如果存在通道值為nil的讀寫操作,則該分支將被忽略,可以理解為從select語句中刪除了這個case語句。

         select和switch只是從結構上看著相似,其實用法大不相同,switch只會選擇一個滿足且僅滿足的case條件執行,而 select 就是監聽 IO 操作,當 IO 操作發生時,觸發相應的動作每個case語句裡必須是一個IO操作,確切的說,應該是一個面向channel的IO操作

面試官:講一講協程

參考:Go 協程是與其他函式或方法一起併發執行的函式或方法。Go 協程可以看作是輕量級執行緒。與執行緒相比,建立一個 Go 協程的成本很小。因此在 Go 應用中,常常會看到有數以千計的 Go 協程併發地執行。

面試官:那協程比執行緒有哪些優勢?

參考:

  • 相比執行緒而言,Go 協程的成本極低。堆疊大小隻有若干 kb,並且可以根據應用的需求進行增減。而執行緒必須指定堆疊的大小,其堆疊是固定不變的。
  • Go 協程會複用(Multiplex)數量更少的 OS 執行緒。即使程式有數以千計的 Go 協程,也可能只有一個執行緒。如果該執行緒中的某一 Go 協程發生了阻塞(比如說等待使用者輸入),那麼系統會再建立一個 OS 執行緒,並把其餘 Go 協程都移動到這個新的 OS 執行緒。所有這一切都在執行時進行,作為程式設計師,我們沒有直接面臨這些複雜的細節,而是有一個簡潔的 API 來處理併發。
  • Go 協程使用通道(Channel)來進行通訊。通道用於防止多個協程訪問共享記憶體時發生競態條件(Race Condition)。通道可以看作是 Go 協程之間通訊的管道。

 3.mysql基礎部分

面試官:mysql連線查詢你知道多少?

參考:交叉連線(笛卡爾連線),內連線,外連線(左連線和右連線),多表連線查詢

笛卡爾連線:交叉連線,

select*from line,vehicle

內連線:預設的連線型別,只有滿足連線條件的記錄才能出現在查詢結果中

SElECT fieldlist FROM table1 【INNER】JOIN table2 ON 
table1.column1=table2.column2 【where condition】

左連線:結果集包括左表的所有記錄和右表中滿足連線條件的記錄

left join

右連線:結果集包括右表的所有記錄和左表中滿足連換條件的記錄

right join

面試官:左連線和右連線的區別是什麼?

左連線   (left join)     
select *  from table1 left join tbale2 on table1.id=table2.id
這條sql語句返回結果   table1表中的資料全部返回   table2表中的資料只返回滿足where條件的(左表資料全部返回)   右連結   (right join)
select * from table1 right join table2 on table1.id=table2.id
這條sql語句返回結果   table2表中的資料全部返回    table1表中的資料只返回滿足where條件的(右表資料全部返回)

面試官:mysql的鎖有哪些?什麼區別?

參考:按照對資料操作的鎖粒度來分,分為行級鎖,間隙鎖,臨鍵鎖,頁級鎖,表級鎖。

行級鎖:表示只針對當前操作的行進行加鎖

間隙鎖:鎖定一個範圍,但不包括記錄本身.間隙鎖的目的是為了讓其他事務無法在間隙中新增資料,防止同一事務的兩次當前讀

臨鍵鎖:它是記錄鎖和間隙鎖的結合,鎖定一個範圍,並且鎖定記錄本身

頁級鎖:採取了折中的頁級鎖,一次鎖定相鄰的一組記錄

表級鎖:mysql中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分mysql引擎支援

按照共享策略:可以分為共享鎖,排它鎖,意向共享鎖,意向排它鎖

讀鎖(共享鎖):Shared Locks(S鎖),針對同一份資料,多個讀操作可以同時進行而不會互相影響
寫鎖(排它鎖):Exclusive Locks(X鎖),當前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖
IS鎖:意向共享鎖、Intention Shared Lock。當事務準備在某條記錄上加S鎖時,需要先在表級別加一個IS鎖。
IX鎖:意向排他鎖、Intention Exclusive Lock。當事務準備在某條記錄上加X鎖時,需要先在表級別加一個IX鎖。
IS、IX鎖是表級鎖,它們的提出僅僅為了在之後加表級別的S鎖和X鎖時可以快速判斷表中的記錄是否被上鎖,以避免用遍歷的方式來查看錶中有沒有上鎖的記錄。

加鎖策略上分:樂觀鎖和悲觀鎖

悲觀鎖認為對於同一個資料的併發操作,一定是會發生修改的(或者增刪改多,查少),哪怕沒有修改,也會認為修改。因此對於同一個資料的併發操作,悲觀鎖採取加鎖的形式。悲觀的認為,不加鎖的併發操作一定會出問題。

樂觀鎖:則認為對於同一個資料的併發操作,是不會發生修改的(或者增刪改少,查多)。在更新資料的時候,會採用不斷嘗試更新的方式來修改資料。也就是先不管資源有沒有被別的執行緒佔用,直接取申請操作,如果沒有產生衝突,那就操作成功,如果產生衝突,有其他執行緒已經在使用了,那麼就不斷地輪詢。樂觀鎖就是不加鎖。好處就是減少上下文切換,壞處是浪費CPU時間

其他鎖:自增鎖

自增鎖:主要用於事務中插入自增欄位,也就是我們最常用的自增主鍵id。通過innodb_autoinc_lock_mode引數可以設定自增主鍵的生成策略。防止併發插入資料的時候自增id出現異常。

面試官:sql注入是什麼?怎麼預防sql注入?

 參考:SQL注入簡單來說就是通過在表單中填寫包含SQL關鍵字的資料來使資料庫執行非常規程式碼的過程。SQL資料庫的操作是通過SQL語句來執行的,這就導致如果我們在程式碼中加入了某些SQL語句關鍵字(比如說DELETE、DROP等),這些關鍵字就很可能在資料庫寫入或讀取資料時得到執行。

如何防止SQL注入

1、檢查變數資料型別和格式

如果SQL語句是類似where id={$id}這種形式,資料庫裡所有的id都是數字,那麼就應該在SQL被執行前,檢查確保變數id是int型別;如果是其他的型別比如日期、時間等也是一個道理。只要是有固定格式的變數,在SQL語句執行前,應該嚴格按照固定格式去檢查,確保變數是我們預想的格式,這樣很大程度上可以避免SQL注入攻擊。
2、過濾特殊符號
對於無法確定固定格式的變數,一定要進行特殊符號過濾或轉義處理。

3、繫結變數,使用預編譯語句

實際上,繫結變數使用預編譯語句是預防SQL注入的最佳方式,使用預編譯的SQL語句語義不會發生改變,在SQL語句中,變數用問號?表示,黑客即使本事再大,也無法改變SQL語句的結構。

補充:為什麼SQL預編譯能有效防禦SQL注入?

1、預編譯語句是什麼?

一條sql在db接收到最終執行完畢返回可以分為下面三個過程:

  1. 詞法和語義解析;
  2. 優化sql語句,制定執行計劃;
  3. 執行並返回結果。

很多情況,同一型別的sql語句可能會反覆執行,如果每次都需要經過上面的詞法語義解析、語句優化、制定執行計劃等,不但影響執行效率也不安全。

所謂預編譯語句就是將這類語句中的值用佔位符替代,可以視為將sql語句模板化或者說引數化,一般稱這類語句叫Prepared Statements

預編譯語句的優勢在於歸納為:一次編譯、多次執行,省去了解析優化等過程;此外預編譯語句能防止sql注入。

2、為什麼Statement會被sql注入?

Statement之所以會被sql注入是因為SQL語句結構發生了變化。
比如:

"select * from tablename where username='"+uesrname+  "'and password='"+password+"'"

在使用者輸入’or true or’之後sql語句結構改變。

  • select * from tablename where username=”or true or” and password=”

這樣本來是判斷使用者名稱和密碼都匹配時才會計數,但是經過改變後變成了或的邏輯關係,不管使用者名稱和密碼是否匹配該式的返回值永遠為true;

3、為什麼Preparement可以防止SQL注入?

原理是採用了預編譯的方法,先將SQL語句中可被客戶端控制的引數集進行編譯,生成對應的臨時變數集,再使用對應的設定方法,為臨時變數集裡面的元素進行賦值,賦值函式setString(),會對傳入的引數進行強制型別檢查和安全檢查,所以就避免了SQL注入的產生。

Preparement樣式為:

select * from tablename where username=? and password=?

該SQL語句會在得到使用者的輸入之前先用資料庫進行預編譯,這樣的話不管使用者輸入什麼使用者名稱和密碼的判斷始終都是並的邏輯關係,防止了SQL注入。

面試官:知道檢視嗎?什麼時候會用到檢視?

參考:檢視從程式碼上看,檢視是一個select語句,從邏輯上看,被當做一個虛擬表看待。避免了程式碼的冗餘;避免了大量重複的sql語句,增加資料的保密性

第一點:使用檢視,可以定製使用者資料,聚焦特定的資料。例如為銷售人員定製特定的檢視

第二點:使用檢視,可以簡化資料操作。

第三點:使用檢視,基表中的資料就有了一定的安全性因為檢視是虛擬的,物理上是不存在的,只是儲存了資料的集合,我們可以將基表中重要的欄位資訊,可以不通過檢視給使用者,檢視是動態的資料的集合,資料是隨著基表的更新而更新。

第四點:可以合併分離的資料,建立分割槽檢視。多個分公司的資料合併到一個總檢視中

面試官:說說你設計表時的一些思路?

參考:在實際開發中最為常見的設計正規化有三個:第一正規化是最基本的正規化。如果資料庫表中的所有欄位值都是不可分解的原子值,就說明該資料庫表滿足了第一正規化;第二正規化在第一正規化的基礎之上更進一層。第二正規化需要確保資料庫表中的每一列都和主鍵相關,而不能只與主鍵的某一部分相關(主要針對聯合主鍵而言)。也就是說在一個數據庫表中,一個表中只能儲存一種資料,不可以把多種資料儲存在同一張資料庫表中;第三正規化需要確保資料表中的每一列資料都和主鍵直接相關,而不能間接相關。總結一下,就是:第一正規化(確保每列保持原子性);第二正規化(確保表中的每列都和主鍵相關);第三正規化(確保每列都和主鍵列直接相關,而不是間接相關)。

另外,在設計表時的規則還包括但不限於:

//規則1:表必須要有主鍵。
//規則2:一個欄位只表示一個含義。
//規則3:總是包含兩個日期欄位:gmt_create(建立日期),gmt_modified(修改日期),且這兩個欄位不應該包含有額外的業務邏輯。
//規則4:MySQL中,gmt_create、gmt_modified使用DATETIME型別。
//規則5:禁止使用複雜資料型別(陣列,自定義型別等)。
//規則6: MySQL中,附屬表拆分後,附屬表id與主表id保持一致。不允許在附屬表新增主鍵欄位。
//規則7: MySQL中,存在過期概念的表,在其設計之初就必須有過期機制,且有明確的過期時間。過期資料必須遷移至歷史表中。
//規則8: MySQL中,不再使用的表,必須通知DBA予以更名歸檔。
//規則9: MySQL中,線上表中若有不再使用的欄位,為保證資料完整,禁止刪除。
//規則10: MySQL中,禁止使用OCI驅動,全部使用THI驅動。

面試官:char和varchar有什麼區別?

參考:VARCHAR 是可變長度的,CHAR 是固定長度


4.redis基礎部分

面試官:reids包括哪些資料型別?

參考:字串(String)、連結串列(lists)、雜湊表(hash)、集合(set)、有序集合(Zset)4

面試官:Redis為什麼這麼快
參考:

1.完全基於記憶體,絕大部分請求是純粹的記憶體操作,非常快速。資料存在記憶體中,類似於HashMap,HashMap的優勢就是查詢和操作的時間複雜度都是O(1);

2.資料結構簡單,對資料操作也簡單,Redis中的資料結構是專門進行設計的;

3.採用單執行緒,避免了不必要的上下文切換和競爭條件,也不存在多程序或者多執行緒導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的效能消耗;

4.使用多路I/O複用模型,非阻塞IO;

多路I/O複用模型是利用 select、poll、epoll 可以同時監察多個流的 I/O 事件的能力,在空閒的時候,會把當前執行緒阻塞掉,當有一個或多個流有 I/O 事件時,就從阻塞態中喚醒,於是程式就會輪詢一遍所有的流(epoll 是隻輪詢那些真正發出了事件的流),並且只依次順序的處理就緒的流,這種做法就避免了大量的無用操作。

這裡“多路”指的是多個網路連線,“複用”指的是複用同一個執行緒。 採用多路 I/O 複用技術可以讓單個執行緒高效的處理多個連線請求(儘量減少網路 IO 的時間消耗)

5.使用底層模型不同,它們之間底層實現方式以及與客戶端之間通訊的應用協議不一樣,Redis直接自己構建了VM 機制 ,因為一般的系統呼叫系統函式的話,會浪費一定的時間去移動和請求;

面試官:redis是單執行緒還是多執行緒?為什麼?

參考:原來是單執行緒的,在redis6實現了多執行緒,使用單執行緒原因如下:

1. CPU不是瓶頸:Redis的所有操作都是基於記憶體的,而CPU不是Redis的瓶頸。在大多數情況下,Redis的瓶頸很可能是機器記憶體或網路頻寬的大小。如果我們想要更高的效能,可以使用單執行緒Redis,我們可以使用叢集(多個程序)解決方案。
2. 併發性:並行性不是支援多個客戶端的唯一策略。Redis使用epoll和事件迴圈來實現併發策略並節省大量時間而無需上下文切換。
3. 易於實現:編寫多執行緒程式可能會更加困難。我們需要為執行緒新增鎖和同步機制。
4. 易於部署:單執行緒應用程式可以部署在至少具有單個CPU核心的任何計算機上。

面試官:併發與並行?
參考:併發性和並行性之間的區別
1. 併發就是一次處理很多事情。並行是關於一次做很多事情。
2. 併發是關於結構;並行是關於執行的。
3. 併發提供了一種構造解決方案的方法,以解決可能(但不一定)可並行化的問題。

我們可以使用餐廳服務員的類比:
什麼是併發
服務員可以為多個客戶提供服務,而一次只能為一個客戶準備菜。
由於廚房提供的菜餚之間會有一定的間隔,因此當顧客人數少於5人時,一位侍者通常可以處理。
什麼是並行
假設廚房一次可以為20位顧客提供餐具。如果一位服務員的顧客數量太大,我們需要更多的服務員。在這種情況下,多個服務員同時工作。我們稱其為並行性。

面試官:redis再記憶體的淘汰演算法是什麼?

參考:為了保證讀取的效率,redis把資料物件都儲存在記憶體當中,它可以支援週期性的把更新的資料寫入磁碟檔案中。而且它還提供了交集和並集,以及一些不同方式排序的操作,他的淘汰演算法包括:

大體分為四種

lru:挑選最近最少使用的資料淘汰

lfu:挑選頻率最低的資料淘汰

random:隨機淘汰

ttl:挑選將要過期的資料淘汰