計算機網絡與python知識點總結

文章目錄

計算機網絡相關知識點整理:

1. OSI七層協議模型,TCP/IP四層模型,五層協議的體系結構之間的關係。

一、OSI七層模型
OSI七層協議模型主要是:應用層(Application)、表示層(Presentation)、會話層(Session)、傳輸層(Transport)、網絡層(Network)、數據鏈路層(Data Link)、物理層(Physical)。
二、TCP/IP四層模型
TCP/IP是一個四層的體系結構,主要包括:應用層、運輸層、網際層和網絡接口層。從實質上講,只有上邊三層,網絡接口層沒有什麼具體的內容。
在這裏插入圖片描述在這裏插入圖片描述
三、五層體系結構
五層體系結構包括:應用層、運輸層、網絡層、數據鏈路層和物理層。
五層協議只是OSI和TCP/IP的綜合,實際應用還是TCP/IP的四層結構。爲了方便可以把數據鏈路層和物理層稱爲網絡接口層。

三種模型結構如下:
在這裏插入圖片描述

各層協議如下表:

在這裏插入圖片描述

2. TCP 和 UDP 是什麼?簡述它們有什麼區別?

運輸層中定義了一些傳輸數據的協議和端口號(WWW端口80等),如:
TCP(transmission control protocol –傳輸控制協議,傳輸效率低,可靠性強,用於傳輸可靠性要求高,數據量大的數據)
UDP(user datagram protocol–用戶數據報協議,與TCP特性恰恰相反,用於傳輸可靠性要求不高,數據量小的數據,如QQ聊天數據就是通過這種方式傳輸的)。 主要是將從下層接收的數據進行分段和傳輸,到達目的地址後再進行重組。常常把這一層數據叫做段。

  • TCP是面向連接的傳輸控制協議,而UDP提供了無連接的數據報服務;
  • TCP具有高可靠性,確保傳輸數據的正確性,不出現丟失或亂序;
  • UDP在傳輸數據前不建立連接,不對數據報進行檢查與修改,無須等待對方的應答,所以會出現分組丟失、重複、亂序,應用程序需要負責傳輸可靠性方面的所有工作;
    UDP具有較好的實時性,工作效率較TCP協議高。
  • TCP—傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之後才能傳輸數據。TCP提供超時重發,丟棄重複數據,檢驗數據,流量控制等功能,保證數據能順序地從一端傳到另一端。
  • UDP—用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,不保證數據按順序傳遞,故而傳輸速度很快。

3. 請描述 TCP 三次握手的過程, 爲什麼要三次握手?

三次握手過程如下:
三次握手 建立起 TCP連接 的 reliable,分配初始序列號和資源,在相互確認之後開始數據的傳輸。有 主動打開(一般是client) 和 被動打開(一般是server)。TCP使用3次握手建立一條連接,該握手初始化了傳輸可靠性以及數據順序性必要的信息,這些信息包括兩個方向的初始序列號,確認號由初始序列號生成,使用3次握手是因爲3次握手已經準備好了傳輸可靠性以及數據順序性所必要的信息,該握手的第3次實際上並不是需要單獨傳輸的,完全可以和數據一起傳輸。
詳細過程如下所示:

  • 第一步,Client會進入SYN_SENT狀態,併發送Syn 消息給Server端,SYN標誌位在此場景下被設置爲1,同時會帶上Client這端分配好的Seq號,這個序列號是一個U32的整型數,該數值的分配是根據時間產生的一個隨機值,通常情況下每間隔4ms會加1。除此之外還會帶一個MSS,也就是最大報文段長度,表示Tcp傳往另一端的最大數據塊的長度。
  • 第二步,Server端在收到,Syn消息之後,會進入SYN_RCVD狀態,同時返回Ack消息給Client,用來通知Client,Server端已經收到SYN消息並通過了確認。這一步Server端包含兩部分內容,一部分是回覆Client的Syn消息,其中ACK=1,Seq號設置爲Client的Syn消息的Seq數值+1;另一部分是主動發送Sever端的Syn消息給Client,Seq號碼是Server端上面對應的序列號,當然Syn標誌位也會設置成1,MSS表示的是Server這一端的最大數據塊長度。
  • 第三步,Client在收到第二步消息之後,首先會將Client端的狀態從SYN_SENT變換成ESTABLISHED,此時Client發消息給Server端,這個方向的通道已經建立成功,Client可以發送消息給Server端了,Server端也可以成功收到這些消息。其次,Client端需要回復ACK消息給Server端,消息包含ACK狀態被設置爲1,Seq號碼被設置成Server端的序列號+1。(備註:這一步往往會與Client主動發起的數據消息,合併到一起發送給Server端。)
  • 第四步,Server端在收到這個Ack消息之後,會進入ESTABLISHED狀態,到此時刻Server發向Client的通道連接建立成功,Server可以發送數據給Client,TCP的全雙工連接建立完成。

爲什麼需要三次握手?
爲了防止已失效的連接請求報文段突然又傳送到了服務端,因而產生錯誤。

  • TCP的連接因爲是全雙工的,也就是Client和Server兩端,發送消息兩個方向的連接都要建立成功。如果要保證雙向連接都成功的話,三次通信是最少的次數了。大於三次的話,後面的次數通信就沒有必要了,是在浪費資源。
  • 二次的話,會怎麼樣,可不可以呢?答案是不可以,我們來看下,下面的場景。
  • 在談論這個之前,我們先要知道TCP是基於IP協議的,而IP協議是有路由的,IP協議不能夠保證先發送的數據先到達,這當中依賴於IP協議底層的網絡質量,以及Client與Server之間的路由跳數。
  • Client在發送完Syn消息1,這裏稱作Syn1之後,假設因爲網絡原因,Syn1並沒有到達Server端,這個時候Client端已經超時,Client之後重新發起SYN消息,這裏稱作Syn2。結果由於網絡原因Syn2先到答Server,Server於是與Client基於Syn2建立了連接,結果沒過多久Syn1又到達了Server,Server於是關掉了Syn2建立的那條連接,又重新建立了一條連接。對於Client來說新建立的這條連接是早就過時的,所以Client不會在這條連接上發送任何數據,這就導致了Server端長時間收不到數據,Client新的連接被斷掉了。

4. 請描述 TCP 四次分手的過程, 爲什麼需要四次分手?

TCP 四次分手的過程:A—>B Fin, B—>A ACK, B—>A Fin, A—>B ACK
當客戶端和服務器通過三次握手建立了TCP連接以後,當數據傳送完畢,肯定是要斷開TCP連接的啊。那對於TCP的斷開連接,這裏就有了神祕的“四次分手”。

  • 第一次分手:主機1(可以使客戶端,也可以是服務器端),設置Sequence Number和Acknowledgment Number,向主機2發送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態;這表示主機1沒有數據要發送給主機2了;
  • 第二次分手:主機2收到了主機1發送的FIN報文段,向主機1回一個ACK報文段,Acknowledgment Number爲Sequence Number加1;主機1進入FIN_WAIT_2狀態;主機2告訴主機1,我已經知道你沒有數據要發送了;
  • 第三次分手:主機2向主機1發送FIN報文段,請求關閉連接,同時主機2進入CLOSE_WAIT狀態;
  • 第四次分手:主機1收到主機2發送的FIN報文段,向主機2發送ACK報文段,然後主機1進入TIME_WAIT狀態;主機2收到主機1的ACK報文段以後,就關閉連接;此時,主機1等待2MSL後依然沒有收到回覆,則證明Server端已正常關閉,那好,主機1也可以關閉連接了

爲什麼需要四次分手:
TCP協議是一種面向連接的、可靠的、基於字節流的運輸層通信協議。TCP是全雙工模式,這就意味着,當主機1發出FIN報文段時,只是表示主機1已經沒有數據要發送了,主機1告訴主機2,它的數據已經全部發送完畢了;但是,這個時候主機1還是可以接受來自主機2的數據;當主機2返回ACK報文段時,表示它已經知道主機1沒有數據發送了,但是主機2還是可以發送數據到主機1的;當主機2也發送了FIN報文段時,這個時候就表示主機2也沒有數據要發送了,就會告訴主機1,我也沒有數據要發送了,之後彼此就會愉快的中斷這次TCP連接。如果要正確的理解四次分手的原理,就需要了解四次分手過程中的狀態變化。

  • FIN_WAIT_1: 這個狀態要好好解釋一下,其實FIN_WAIT_1和FIN_WAIT_2狀態的真正含義都是表示等待對方的FIN報文。而這兩種狀態的區別是:FIN_WAIT_1狀態實際上是當SOCKET在ESTABLISHED狀態時,它想主動關閉連接,向對方發送了FIN報文,此時該SOCKET即進入到FIN_WAIT_1狀態。而當對方迴應ACK報文後,則進入到FIN_WAIT_2狀態,當然在實際的正常情況下,無論對方何種情況下,都應該馬上回應ACK報文,所以FIN_WAIT_1狀態一般是比較難見到的,而FIN_WAIT_2狀態還有時常常可以用netstat看到。(主動方)

  • FIN_WAIT_2:上面已經詳細解釋了這種狀態,實際上FIN_WAIT_2狀態下的SOCKET,表示半連接,也即有一方要求close連接,但另外還告訴對方,我暫時還有點數據需要傳送給你(ACK信息),稍後再關閉連接。(主動方)

  • CLOSE_WAIT:這種狀態的含義其實是表示在等待關閉。怎麼理解呢?當對方close一個SOCKET後發送FIN報文給自己,你係統毫無疑問地會迴應一個ACK報文給對方,此時則進入到CLOSE_WAIT狀態。接下來呢,實際上你真正需要考慮的事情是察看你是否還有數據發送給對方,如果沒有的話,那麼你也就可以 close這個SOCKET,發送FIN報文給對方,也即關閉連接。所以你在CLOSE_WAIT狀態下,需要完成的事情是等待你去關閉連接。(被動方)

    LAST_ACK: 這個狀態還是比較容易好理解的,它是被動關閉一方在發送FIN報文後,最後等待對方的ACK報文。當收到ACK報文後,也即可以進入到CLOSED可用狀態了。(被動方)

  • TIME_WAIT: 表示收到了對方的FIN報文,併發送出了ACK報文,就等2MSL後即可回到CLOSED可用狀態了。如果FIN_WAIT_1狀態下,收到了對方同時帶FIN標誌和ACK標誌的報文時,可以直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。(主動方)

  • CLOSED: 表示連接中斷。

5. 四次分手過程中爲什麼等待 2msl?

A—>B Fin, B—>A ACK, B—>A Fin, A—>B ACK
B—>A Fin, A—>B ACK過程中:B收到ACK,關閉連接。但是A無法知道ACK是否已經到達B,於是開始等待?等待什麼呢?假如ACK沒有到達B,B會爲FIN這個消息超時重傳 timeout retransmit ,那如果A等待時間足夠,又收到FIN消息,說明ACK沒有到達B,於是再發送ACK,直到在足夠的時間內沒有收到FIN,說明ACK成功到達。這個等待時間至少是:B的timeout + FIN的傳輸時間,爲了保證可靠,採用更加保守的等待時間2MSL。MSL,Maximum Segment Life,這是TCP 對TCP Segment 生存時間的限制。TTL, Time To Live ,IP對IP Datagram 生存時間的限制,255 秒,所以 MSL一般 = TTL = 255秒A發出ACK,等待ACK到達對方的超時時間 MSL,等待FIN的超時重傳,也是MSL,所以如果2MSL時間內沒有收到FIN,說明對方安全收到FIN。

6. TCP 粘包是怎麼回事,如何處理?UDP 有粘包嗎?

粘包產生原因:
TCP:由於TCP協議本身的機制(面向連接的可靠地協議-三次握手機制)客戶端與服務器會維持一個連接(Channel),數據在連接不斷開的情況下,可以持續不斷地將多個數據包發往服務器,但是如果發送的網絡數據包太小,那麼他本身會啓用Nagle算法(可配置是否啓用)對較小的數據包進行合併(基於此,TCP的網絡延遲要UDP的高些)然後再發送(超時或者包大小足夠)。那麼這樣的話,服務器在接收到消息(數據流)的時候就無法區分哪些數據包是客戶端自己分開發送的,這樣產生了粘包;服務器在接收到數據庫後,放到緩衝區中,如果消息沒有被及時從緩存區取走,下次在取數據的時候可能就會出現一次取出多個數據包的情況,造成粘包現象(確切來講,對於基於TCP協議的應用,不應用包來描述,而應 用 流來描述),個人認爲服務器接收端產生的粘包應該與linux內核處理socket的方式 select輪詢機制的線性掃描頻度無關。

UDP:本身作爲無連接的不可靠的傳輸協議(適合頻繁發送較小的數據包),他不會對數據包進行合併發送(也就沒有Nagle算法之說了),他直接是一端發送什麼數據,直接就發出去了,既然他不會對數據合併,每一個數據包都是完整的(數據+UDP頭+IP頭等等發一次數據封裝一次)也就沒有粘包一說了。

分包產生的原因:可能是IP分片傳輸導致的,也可能是傳輸過程中丟失部分包導致出現的半包,還有可能就是一個包可能被分成了兩次傳輸,在取數據的時候,先取到了一部分(還可能與接收的緩衝區大小有關係),總之就是一個數據包被分成了多次接收。

粘包與分包處理方法:

  • 如果你是連續的整個數據流 比如發送文件 那麼完全不考慮粘包也無所謂 因爲可以建立連接後發送 發送完畢後斷開連接 整個數據流就是整個一個文件 無論數據從那裏切開都無所謂 整個拼接後依舊是整個一個文件的數據。

  • 如果你發送的數據是多次通信 比如把一個目錄下所有的文件名都發送過去 那麼就不能當作一個整體發送了 必須對他們劃分邊界 有一個很簡單的處理方法 就是採用"數據長度+實際數據"的格式來發送數據 這個"數據長度"的格式是固定寬度的 比如4字節 可以表示0~4GB的寬度了 足夠用了 這個寬度說明了後續實際數據的寬度 這樣你就可以把粘包後的數據按照正確的寬度取出來了。
    每次都是取出4字節 隨後按照正確的寬度取出後續部分的就OK了

  • 如果你的所有數據都是固定寬度的 比如不停的發送溫度數據 每個都是1字節 那麼寬度已知了 每次你都取出一個1字節就OK了 所以就不用發送寬度數據了

  • 當然你也可以按照建立連接斷開連接來劃分邊界 每次發送數據都打開關閉一次連接 不過對於頻繁的小數據量是不可取的做法 因爲開銷太大 建立連接和關閉連接也是需要耗費網絡流量的

總結:
一個是採用分隔符的方式,即我們在封裝要傳輸的數據包的時候,採用固定的符號作爲結尾符(數據中不能含結尾符),這樣我們接收到數據後,如果出現結尾標識,即人爲的將粘包分開,如果一個包中沒有出現結尾符,認爲出現了分包,則等待下個包中出現後 組合成一個完整的數據包,這種方式適合於文本傳輸的數據,如採用/r/n之類的分隔符;

另一種是採用在數據包中添加長度的方式,即在數據包中的固定位置封裝數據包的長度信息(或可計算數據包總長度的信息),服務器接收到數據後,先是解析包長度,然後根據包長度截取數據包(此種方式常出現於自定義協議中),但是有個小問題就是如果客戶端第一個數據包數據長度封裝的有錯誤,那麼很可能就會導致後面接收到的所有數據包都解析出錯(由於TCP建立連接後流式傳輸機制),只有客戶端關閉連接後重新打開纔可以消除此問題,我在處理這個問題的時候對數據長度做了校驗,會適時的對接收到的有問題的包進行人爲的丟棄處理(客戶端有自動重發機制,故而在應用層不會導致數據的不完整性);

總之, 粘包的情況是無法絕對避免的 因爲網絡環境是很複雜的 依賴發送和接收緩衝區的控制是不能保證100%的 只要在發送的數據中說明數據的寬度隨後在接收部分按照這個寬度拆開就OK了 寬度全都是統一的已知寬度的情況下拆開更加容易 連在發送端填入寬度數據都可以省去了

7. time_wait 是什麼情況?出現過多的 close_wait 可能是什麼原因?

  • TIME_WAIT 是主動關閉連接的一方保持的狀態,對於服務器來說它本身就是“客戶端”,在完成一個爬取任務之後,它就會發起主動關閉連接,從而進入TIME_WAIT的狀態,然後在保持這個狀態2MSL(max segment lifetime)時間之後,徹底關閉回收資源。

  • TIME_WAIT狀態可以通過優化服務器參數得到解決,因爲發生TIME_WAIT的情況是服務器自己可控的,要麼就是對方連接的異常,要麼就是自己沒有迅速回收資源,總之不是由於自己程序錯誤導致的。

  • CLOSE_WAIT表示被動關閉,關閉 TCP 連接過程中,第 1 次揮手服務器接收客戶端的 FIN 報文段,第 2 次揮手時,服務器發送了 ACK 報文段之後,服務器會進入 close_wait 狀態。

  • 如果一直保持在CLOSE_WAIT狀態,那麼只有一種情況,就是在對方關閉連接之後服務器程序自己沒有進一步發出FIN信號,一般原因都是TCP連接沒有調用關閉方法。換句話說,就是在對方連接關閉之後,程序裏沒有檢測到,或者程序壓根就忘記了這個時候需要關閉連接,於是這個資源就一直被程序佔着。這種情況,通過服務器內核參數也沒辦法解決,服務器對於程序搶佔的資源沒有主動回收的權利,除非終止程序運行,一定程度上,可以使用TCP的KeepAlive功能,讓操作系統替我們自動清理掉CLOSE_WAIT連接。

  • 什麼情況下,連接處於CLOSE_WAIT狀態呢?
    答案一:在被動關閉連接情況下,在已經接收到FIN,但是還沒有發送自己的FIN的時刻,連接處於CLOSE_WAIT狀態。通常來講,CLOSE_WAIT狀態的持續時間應該很短,正如SYN_RCVD狀態。但是在一些特殊情況下,就會出現連接長時間處於CLOSE_WAIT狀態的情況。
    答案二:出現大量close_wait的現象,主要原因是某種情況下對方關閉了socket鏈接,但是我方忙與讀或者寫,沒有關閉連接。 代碼需要判斷socket,一旦讀到0,斷開連接,read返回負,檢查一下errno,如果不是AGAIN,就斷開連接。

8. epoll,select 的區別?邊緣觸發,水平觸發區別?

  • select,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,epoll本質上都是同步I/O,因爲他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。

  • 問題:當需要讀兩個以上的I/O的時候,如果使用阻塞式的I/O,那麼可能長時間的阻塞在一個描述符上面,另外的描述符雖然有數據但是不能讀出來,這樣實時性不能滿足要求

  • 解決方案:.一種較好的方式爲I/O多路轉接(I/O multiplexing)(貌似也翻譯多路複用),先構造一張有關描述符的列表(epoll中爲隊列),然後調用一個函數,直到這些描述符中的一個準備好時才返回,返回時告訴進程哪些I/O就緒。select和epoll這兩個機制都是多路I/O機制的解決方案,select爲POSIX標準中的,而epoll爲Linux所特有的。

  • 區別(epoll相對select優點)主要有三:

    1.select的句柄數目受限,在linux/posix_types.h頭文件有這樣的聲明:#define __FD_SETSIZE 1024 表示select最多同時監聽1024個fd。而epoll沒有,它的限制是最大的打開文件句柄數目。

    2.epoll的最大好處是不會隨着FD的數目增長而降低效率,在selec中採用輪詢處理,其中的數據結構類似一個數組的數據結構,而epoll是維護一個隊列,直接看隊列是不是空就可以了。epoll只會對“活躍”的socket進行操作—這是因爲在內核實現中epoll是根據每個fd上面的callback函數實現的。那麼,只有“活躍”的socket纔會主動的去調用 callback函數(把這個句柄加入隊列),其他idle狀態句柄則不會,在這點上,epoll實現了一個“僞”AIO。但是如果絕大部分的I/O都是“活躍的”,每個I/O端口使用率很高的話,epoll效率不一定比select高(可能是要維護隊列複雜)。

    3.使用mmap加速內核與用戶空間的消息傳遞。無論是select,poll還是epoll都需要內核把FD消息通知給用戶空間,如何避免不必要的內存拷貝就很重要,在這點上,epoll是通過內核於用戶空間mmap同一塊內存實現的。

  • 邊緣觸發,水平觸發區別如下:

  • epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是默認的水平觸發模式,ET是“高速”邊緣模式。

  • LT(level triggered)是缺省的工作方式,並且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表.

    ET (edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核通過epoll告訴你。然後它會假設你知道文件描述符已經就緒,並且不會再爲那個文件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再爲就緒狀態了,但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once)
     LT模式下,只要這個fd還有數據可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作,而在ET(邊緣觸發)模式中,它只會提示一次,直到下次再有數據流入之前都不會再提示了,無 論fd中是否還有數據可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀光,也就是說一直讀到read的返回值小於請求值,或者 遇到EAGAIN錯誤。

9. 簡述一下你瞭解的端口及對應的服務。(至少 5 個)

  • 21 FTP(文件傳輸協議)
    22 SSH
    23 Talnet(遠程)服務
    25 SMTP(簡單郵件傳輸協議)
    53 DNS域名服務器
    80 HTTP超文本傳輸協議
    110 POP3郵件協議3
    443 HTTPS
    1080 Sockets
    1521 Oracle數據庫默認端口
    3306 Mysql服務

10. HTTP 協議是什麼?工作原理是什麼?

  • HTTP協議(HyperText Transfer Protocol,超文本傳輸協議)是用於從WWW服務器傳輸超文本到本地瀏覽器的傳送協議。它可以使瀏覽器更加高效,使網絡傳輸減少。它不僅保證計算機正確快速地傳輸超文本文檔,還確定傳輸文檔中的哪一部分,以及哪部分內容首先顯示(如文本先於圖形)等。HTTP是基於TCP/IP協議的應用程序協議,它並不包括數據包的傳輸,主要規定了客戶端與服務器的通信格式,默認使用的是80端口。
  • 工作原理
    HTTPS在傳輸數據之前需要客戶端(瀏覽器)與服務端(網站)之間進行一次握手,在握手過程中將確立雙方加密傳輸數據的密碼信息,通常情況下會配合數字證書實現。
    TLS/SSL協議不僅僅是一套加密傳輸的協議,更是一件經過藝術家精心設計的藝術品,TLS/SSL中使用非對稱加密,對稱加密,數字證書以及HASH算法四種技術。
    一個事務分爲四個過程:建立連接、瀏覽器發出請求信息、服務器發出響應信息、關閉連接。每次連接只處理一個請求和響應。對每一個文件的訪問,瀏覽器與服務器都要建立一次單獨的連接。
    1 ) 、地址解析
    如用客戶端瀏覽器請求這個頁面:http://localhost:8080/simple.htm 從中分解出協議名、主機名、端口、對象路徑等部分,對於我們的這個地址,解析得到的結果如下:
    協議名:http
    主機名:localhost.com
    端口:8080
    對象路徑:/index.htm 在這一步,需要域名系統DNS解析域名localhost.com,得主機的IP地址。
    2)封裝HTTP請求數據包
    把以上部分結合本機自己的信息,封裝成一個HTTP請求數據包
    3)封裝成TCP包,建立TCP連接(TCP的三次握手)
    在HTTP工作開始之前,客戶機(Web瀏覽器)首先要通過網絡與服務器建立連接,該連接是通過TCP來完成的,該協議與IP協議共同構建Internet,即著名的TCP/IP協議族,因此Internet又被稱作是TCP/IP網絡。HTTP是比TCP更高層次的應用層協議,根據規則,只有低層協議建立之後才能,才能進行更層協議的連接,因此,首先要建立TCP連接,一般TCP連接的端口號是80。這裏是8080端口。
    4)客戶機發送請求命令
    建立連接後,客戶機發送一個請求給服務器,請求方式的格式爲:統一資源標識符(URL)、協議版本號,後邊是MIME信息包括請求修飾符、客戶機信息和可內容。
    5)服務器響應
    服務器接到請求後,給予相應的響應信息,其格式爲一個狀態行,包括信息的協議版本號、一個成功或錯誤的代碼,後邊是MIME信息包括服務器信息、實體信息和可能的內容。 實體消息是服務器向瀏覽器發送頭信息後,它會發送一個空白行來表示頭信息的發送到此爲結束,接着,它就以Content-Type應答頭信息所描述的格式發送用戶所請求的實際數據
    6)服務器關閉TCP連接
    一般情況下,一旦Web服務器向瀏覽器發送了請求數據,它就要關閉TCP連接,然後如果瀏覽器或者服務器在其頭信息加入了這行代碼 Connection:keep-alive TCP連接在發送後將仍然保持打開狀態,於是,瀏覽器可以繼續通過相同的連接發送請求。保持連接節省了爲每個請求建立新連接所需的時間,還節約了網絡帶寬。

11. HTTP 報文結構

HTTP通信過程包括客戶端往服務器端發送請求以及服務器端給客戶端返回響應兩個過程。在這兩個過程中就會產生請求報文和響應報文。

  • 什麼是HTTP報文?
    HTTP報文是用於HTTP協議交互的信息,HTTP報文本身是由多行數據構成的字符串文本。客戶端的HTTP報文叫做請求報文服務器端的HTTP報文叫做響應報文

  • HTTP報文由哪幾部分構成?各部分都有什麼作用?
    HTTP報文由報文首部報文主體構成,中間由一個空行分隔。 報文首部是客戶端或服務器端需處理的請求或響應的內容及屬性, 可以傳遞額外的重要信息。報文首部包括請求行和請求頭部,報文主體主要包含應被髮送的數據。通常,不一定有報文主體。

HTTP報文首部的結構:由首部字段名和字段值構成的,中間用冒號“:”分割。首部字段格式: 首部字段名:字段值。

例如,在HTTP首部中以Content-Type這個字段來表示報文主體的對象類型:

  • Content-Type:text/html。
    上述的Content-Type是首部字段名,text/html是字段值,字段值可以是多個值,例如:Keep-Alive:timeout=15,max=10。

HTTP首部字段通常有4種類型:通用首部,請求首部,響應首部,實體首部。

  • 通用首部字段:請求報文和響應報文兩方都會使用的首部。

  • 請求首部字段:從客戶端向服務器端發送請求報文時使用的首部。補充了請求的附加內容、客戶端信息、響應內容相關優先級等信息。

  • 響應首部字段:從服務器端向客戶端返回響應報文時使用的首部。補充了響應的附加內容,也會要求客戶端附加額外的內容信息。

  • 實體首部字段:針對請求報文和響應報文的實體部分使用的首部。補充了資源內容更新時間等和實體有關的信息。

請求報文及響應報文的結構

  • (1)HTTP請求報文
    一個HTTP請求報文由請求行(request line)、請求頭部(request header)、空行和請求數據4個部分構成。
    在這裏插入圖片描述
    請求行數據格式由三個部分組成:請求方法、URI、HTTP協議版本,他們之間用空格分隔。
    該部分位於數據首行,基本格式爲:
GET /index.html HTTP/1.1

該部分的請求方法字段給出了請求類型,URI給出請求的資源位置(/index.html)。HTTP中的請求類型包括:GET、POST、HEAD、PUT、DELETE。一般常用的爲GET和POST方式。最後HTTP協議版本給出HTTP的版本號。

  • (2)HTTP響應報文
    HTTP響應報文由狀態行(HTTP版本、狀態碼(數字和原因短語))、響應頭部、空行和響應體4個部分構成。
    其中響應頭部和響應體同樣也是通過一個空行進行隔開,或者有一些瀏覽器響應頭部在Header中顯示,響應體在Reponse中顯示。
    響應頭部主要是返回一些服務器的基本信息,以及一些Cookie值等。
    響應體爲請求需要得到的具體數據,可以爲任何類型數據,一般網頁瀏覽返回的爲html文件內容。
    狀態行主要給出響應HTTP協議的版本號、響應返回狀態碼、響應描述,同樣是單行顯示。格式爲:HTTP/1.1 200 OK
    狀態碼告知從服務器端返回的請求的狀態,一般由一個三位數組成,分別以整數1~5開頭組成。
    在這裏插入圖片描述

12. GET 和 POST 請求的區別

  • (1)get是從服務器上獲取數據,post是向服務器傳送數據。

  • (2)生成方式不同
    Get:URL輸入;超連接;Form表單中method屬性爲get;Form表單中method爲空。
    Post只有一種:Form表單中method爲Post。

  • (3)數據傳送方式:Get傳遞的請求數據按照key-value的方式放在URL後面,在網址中可以直接看到,使用?分割URL和傳輸數據,傳輸的參數之間以&相連,如:login.action?name=user&password=123。所以安全性差。
    POST方法會把請求的參數放到請求頭部和空格下面的請求數據字段就是請求正文(請求體)中以&分隔各個字段,請求行不包含參數,URL中不會額外附帶參數。所以安全性高。

  • (4)發送數據大小的限制:通常GET請求可以用於獲取輕量級的數據,而POST請求的內容數據量比較龐大些。
    Get:1~2KB。get方法提交數據的大小直接影響到了URL的長度,但HTTP協議規範中其實是沒有對URL限制長度的,限制URL長度的是客戶端或服務器的支持的不同所影響。
    Post:沒有要求。post方式HTTP協議規範中也沒有限定,起限制作用的是服務器的處理程序的能力。

  • (5)提交數據的安全:POST比GET方式的安全性要高。Get安全性差,Post安全性高。
    通過GET提交數據,用戶名和密碼將明文出現在URL上,如果登錄頁面有瀏覽器緩存,或者其他人查看瀏覽器的歷史記錄,那麼就可以拿到用戶的賬號和密碼了。安全性將會很差。

13. HTTP 常見的狀態碼有哪些?301,302,404,500,502,504 等

  • 1XX 請求正在處理

  • 2XX 請求成功
    200 OK 正常處理
    204 no content 請求處理成功但沒有資源可返回
    206 Partial Content 對資源的某一部分請求

  • 3XX 重定向
    301 Moved Permanenly請求資源的URI已經更新(永久移動),客戶端會同步更新URI。
    302 Found 資源的URI已臨時定位到其他位置,客戶端不會更新URI。

    303 See Other 資源的URI已更新,明確表示客戶端要使用GET方法獲取資源。

    304 Not Modified 當客戶端附帶條件請求訪問資源時資源已找到但未符合條件請求。

    307 Temporary Redirect臨時重定向

  • 4XX 客戶端錯誤
    400 Bad Request 請求報文中存在語法錯誤,一般爲參數異常。
    401 Unauthorized 發送的請求需要HTTP認證。
    403 Forbiddden 不允許訪問,對請求資源的訪問被服務器拒絕 404 Not Found 無法找到請求的資源,請求資源不存在。
    405 請求的方式不支持。

  • 5XX 服務器錯誤
    500 Internal Server Error 服務器的內部資源出故障,服務器在執行請求時發生了錯誤。

    503 Service Unavailable 服務器暫時處於超負載狀態或正在進行停機維護,無法處理請求,服務器正忙

主要狀態碼

  • 200:正常
  • 301:永久重定向
  • 302/307:臨時重定向
  • 304:未修改,可以使用緩存,無需再次修改
  • 404:資源找不到
  • 500:服務器內部錯誤

14. HTTP 與 HTTPS 的區別是什麼?

HTTP:是互聯網上應用最爲廣泛的一種網絡協議,是一個客戶端和服務器端請求和應答的標準(TCP),用於從WWW服務器傳輸超文本到本地瀏覽器的傳輸協議,它可以使瀏覽器更加高效,使網絡傳輸減少。
HTTPS:是以安全爲目標的HTTP通道,簡單講是HTTP的安全版,即HTTP下加入SSL層,HTTPS的安全基礎是SSL,因此加密的詳細內容就需要SSL。
區別

  • 1、https協議需要到ca申請證書,一般免費證書較少,因而需要一定費用。
  • 2、http是超文本傳輸協議,信息是明文傳輸,https則是具有安全性的ssl加密傳輸協議。
  • 3、http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,後者是443。
  • 4、http的連接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。HTTPS並不是一個單獨的協議,是對工作在一加密連接(SSL/TLS)上常規HTTP協議。通過在TCP和HTTP之間加入TLS(Transport Layer Security)來加密。

15. 在瀏覽器中輸入 www.baidu.com 後執行的全部過程。

瀏覽器——即“客戶端”

  • 1、瀏覽器獲取輸入的域名www.baidu.com
  • 2、瀏覽器向域名系統DNS請求解析www.baidu.com的IP地址
  • 3、DNS解析出百度服務器的IP地址
  • 4、瀏覽器與服務器建立TCP連接(默認端口80)
  • 5、瀏覽器發出HTTP請求,請求百度首頁
  • 6、服務器通過HTTP請求把首頁文件發給瀏覽器
  • 7、TCP連接釋放
  • 8、瀏覽器解析首頁文件,展示web界面

16. 常用加密算法及原理

在安全領域,利用密鑰加密算法來對通信的過程進行加密是一種常見的安全手段。利用該手段能夠保障數據安全通信的三個目標:

  • 1、數據的保密性,防止用戶的數據被竊取或泄露;
  • 2、保證數據的完整性,防止用戶傳輸的數據被篡改;
  • 3、通信雙方的身份確認,確保數據來源與合法的用戶;

而常見的密鑰加密算法類型大體可以分爲三類:對稱加密、非對稱加密、單向加密。

對稱加密算法

  • 對稱加密算法採用單密鑰加密,在通信過程中,數據發送方將原始數據分割成固定大小的塊,經過密鑰和加密算法逐個加密後,發送給接收方;接收方收到加密後的報文後,結合密鑰和解密算法解密組合後得出原始數據。由於加解密算法是公開的,因此在這過程中,密鑰的安全傳遞就成爲了至關重要的事了。而密鑰通常來說是通過雙方協商,以物理的方式傳遞給對方,或者利用第三方平臺傳遞給對方,一旦這過程出現了密鑰泄露,不懷好意的人就能結合相應的算法攔截解密出其加密傳輸的內容。
  • 對稱加密算法擁有着算法公開、計算量小、加密速度和效率高得特定,但是也有着密鑰單一、密鑰管理困難等缺點在這裏插入圖片描述
    常見的對稱加密算法有:
    DES:分組式加密算法,以64位爲分組對數據加密,加解密使用同一個算法。
    3DES:三重數據加密算法,對每個數據塊應用三次DES加密算法。
    AES:高級加密標準算法,是美國聯邦政府採用的一種區塊加密標準,用於替代原先的DES,目前已被廣泛應用。
    Blowfish:Blowfish算法是一個64位分組及可變密鑰長度的對稱密鑰分組密碼算法,可用來加密64比特長度的字符串。

非對稱加密算法
非對稱加密算法採用公鑰和私鑰兩種不同的密碼來進行加解密。公鑰和私鑰是成對存在,公鑰是從私鑰中提取產生公開給所有人的,如果使用公鑰對數據進行加密,那麼只有對應的私鑰才能解密,反之亦然。
下圖爲簡單非對稱加密算法的常見流程:
在這裏插入圖片描述

發送方Bob從接收方Alice獲取其對應的公鑰,並結合相應的非對稱算法將明文加密後發送給Alice;Alice接收到加密的密文後,結合自己的私鑰和非對稱算法解密得到明文。這種簡單的非對稱加密算法的應用其安全性比對稱加密算法來說要高,但是其不足之處在於無法確認公鑰的來源合法性以及數據的完整性。

  • 非對稱加密算法具有安全性高、算法強度負複雜的優點,其缺點爲加解密耗時長、速度慢,只適合對少量數據進行加密,其常見算法包括:
    RSA:RSA算法基於一個十分簡單的數論事實:將兩個大素數相乘十分容易,但那時想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作爲加密密鑰,可用於加密,也能用於簽名。
    DSA:數字簽名算法,僅能用於簽名,不能用於加解密。
    DSS:數字簽名標準,技能用於簽名,也可以用於加解密。
    ELGamal:利用離散對數的原理對數據進行加解密或數據簽名,其速度是最慢的。

單向加密
單向加密算法常用於提取數據指紋,驗證數據的完整性。發送者將明文通過單向加密算法加密生成定長的密文串,然後傳遞給接收方。接收方在收到加密的報文後進行解密,將解密獲取到的明文使用相同的單向加密算法進行加密,得出加密後的密文串。隨後將之與發送者發送過來的密文串進行對比,若發送前和發送後的密文串相一致,則說明傳輸過程中數據沒有損壞;若不一致,說明傳輸過程中數據丟失了。單向加密算法只能用於對數據的加密,無法被解密,其特點爲定長輸出、雪崩效應。常見的算法包括:MD5、sha1、sha224等等,其常見用途包括:數字摘要、數字簽名等等
在這裏插入圖片描述

Python 語法相關知識點整理

1. 說說 Linux 常用命令的命令有哪些(不少於 20 個高階命令)。

linux的目錄結構
/ 下級目錄結構

  • bin (binaries)存放二進制可執行文件
  • sbin (super user binaries)存放二進制可執行文件,只有root才能訪問
  • etc (etcetera)存放系統配置文件
  • usr (unix shared resources)用於存放共享的系統資源
  • home 存放用戶文件的根目錄
  • root 超級用戶目錄
  • dev (devices)用於存放設備文件
  • lib (library)存放跟文件系統中的程序運行所需要的共享庫及內核模塊
  • mnt (mount)系統管理員安裝臨時文件系統的安裝點
  • boot 存放用於系統引導時使用的各種文件
  • tmp (temporary)用於存放各種臨時文件
  • var (variable)用於存放運行時需要改變數據的文件

操作文件及目錄在這裏插入圖片描述
在這裏插入圖片描述

系統常用命令
在這裏插入圖片描述

在這裏插入圖片描述
壓縮解壓縮
在這裏插入圖片描述
文件權限操作
linux文件權限的描述格式解讀

r 可讀權限,w可寫權限,x可執行權限(也可以用二進制表示 111 110 100 --> 764)
第1位:文件類型(d 目錄,- 普通文件,l 鏈接文件)
第2-4位:所屬用戶權限,用u(user)表示
第5-7位:所屬組權限,用g(group)表示
第8-10位:其他用戶權限,用o(other)表示
第2-10位:表示所有的權限,用a(all)表示在這裏插入圖片描述

2. 簡述解釋型和編譯型編程語言?

  • 解釋型語言編寫的程序不需要編譯,在執行的時候,專門有一個解釋器能夠將VB語言翻譯成機器語言,每個語句都是執行的時候才翻譯。這樣解釋型語言每執行一次就要翻譯一次,效率比較低。

  • 用編譯型語言寫的程序執行之前,需要一個專門的編譯過程,通過編譯系統,把源高級程序編譯成爲機器語言文件,翻譯只做了一次,運行時不需要翻譯,所以編譯型語言的程序執行效率高,但也不能一概而論,部分解釋型語言的解釋器通過在運行時動態優化代碼,甚至能夠使解釋型語言的性能超過編譯型語言

3. python 中.pyc 文件是什麼?

當我們在命令行中輸入python hello.py時,其實是激活了Python的“解釋器”,告訴“解釋器”:你要開始工作了。可是在“解釋”之前,其實執行的第一項工作和Java一樣,是編譯。當我們執行python hello.py時,他也一樣執行了這麼一個過程,所以我們應該這樣來描述Python,Python是一門先編譯後解釋的語言。

  • 簡述Python的運行過程:
    在說這個問題先來說兩個概念,0PyCodeObject和pyc文件。
    我們在硬盤上看到的pyc自然不必多說,而其實PyCodeObject則是Python編譯器真正編譯成的結果。
    當python程序運行時,編譯的結果則是保存在位於內存中的PyCodeObject中,當Python程序運行結束時,Python解釋器將PyCodeObject寫回到pyc文件中。
    當python程序第二次運行時,首先程序會在硬盤中尋找pyc文件,如果找到,則直接載入,否則就重複上面的過程。
    所以我們應該這樣來定位PyCodeObject和pyc文件,我們說pyc文件其實是 PyCodeObject的一種持久化保存方式。

4. python 中的可變對象和不可變對象之間的區別

可變對象:對象存放在地址中的值會原地被改變(所謂的改變是創建了一塊新的地址並把新的對象的值放在新地址中原來的對象並沒有發生變化)
不可變對象:對象存放在地址中的值不會改變
int str float tuple 都屬於不可變對象 其中tuple有些特殊 dict set list 屬於可變對象
總結:
可變對象是指,一個對象在不改變其所指向的地址的前提下,可以修改其所指向的地址中的值;
不可變對象是指,一個對象所指向的地址上值是不能修改的,如果你修改了這個對象的值,那麼它指向的地址就改變了,相當於你把這個對象指向的值複製出來一份,然後做了修改後存到另一個地址上了,但是可變對象就不會做這樣的動作,而是直接在對象所指的地址上把值給改變了,而這個對象依然指向這個地址。

變量的不可變舉例:


i=73
print(id(i))
i+=2
print(id(i))

i是一個變量,它指向對象的內容是73,當執行i+=2時,首先創建出一個新的內存裏面存放改變後的值(75),然後讓i指向新的地址,這是不可變對象在“改變”時的執行步驟,原來的對象內容和內存並沒有發生變化。對於不變對象來說,調用對象自身的任意方法,也不會改變該對象自身的內容。相反,這些方法會創建新的對象並返回,這樣,就保證了不可變對象本身永遠是不可變的。

在這裏插入圖片描述
可變對象實例:
可變對象對於自身的任意方法,是不需要重新開闢內存空間的,而是原地改變。

m=[5,9]

m.append(6)

print(m)

m的值變爲了[5,9,6]當執行append時由於m是list類型屬於可變對象所以它不會開闢新的內存空間而是像下圖一樣原地改變其值:
在這裏插入圖片描述

5. 字符串拼接直接用+會產生什麼問題怎麼去優化?

+的效率最慢,優化有兩種方式如下:

s = []

for n in range(0,1000):

    s.append(str(n))

''.join(s)
s = ''.join(map(str,range(0,1000)))  #此方法最好

6. 列表和元組有什麼不同?列表和集合有什麼區別?

一、列表
1.任意對象的有序集合
列表是一組任意類型的值,按照一定順序組合而成的
2.通過偏移讀取
組成列表的值叫做元素(Elements)。每一個元素被標識一個索引,第一個索引是0,序列的功能都能實現
3.可變長度,異構以及任意嵌套
列表中的元素可以是任意類型,甚至是列表類型,也就是說列表可以嵌套
4.可變的序列
支持索引、切片、合併、刪除等等操作,它們都是在原處進行修改列表
5.對象引用數組
列表可以當成普通的數組,每當用到引用時,Python總是會將這個引用指向一個對象,所以程序只需處理對象的操作。當把一個對象賦給一個數據結構元素或變量名時,Python總是會存儲對象的引用,而不是對象的一個拷貝
二、元組
1.任意對象的有序集合
與列表相同
2.通過偏移存取
與列表相同
3.屬於不可變序列類型
類似於字符串,但元組是不可變的,不支持在列表中任何原處修改操作,不支持任何方法調用
4.固定長度、異構、任意嵌套
固定長度即元組不可變,在不被拷貝的情況下長度固定,其他同列表
5.對象引用的數組
與列表相似,元祖是對象引用的數組
和list相比
1.比列表操作速度快
2.對數據“寫保護“
3.可用於字符串格式化中
4.可作爲字典的key
三、字典
1.通過鍵而不是偏移量來讀取
字典就是一個關聯數組,是一個通過關鍵字索引的對象的集合,使用鍵-值(key-value)進行存儲,查找速度快
2.任意對象的無序集合
字典中的項沒有特定順序,以“鍵”爲象徵
3.可變長、異構、任意嵌套
同列表,嵌套可以包含列表和其他的字典等
4.屬於可變映射類型
因爲是無序,故不能進行序列操作,但可以在遠處修改,通過鍵映射到值。字典是唯一內置的映射類型(鍵映射到值的對象)
5.對象引用表
字典存儲的是對象引用,不是拷貝,和列表一樣。字典的key是不能變的,list不能作爲key,字符串、元祖、整數等都可以
和list比較,dict有以下幾個特點:
1.查找和插入的速度極快,不會隨着key的增加而增加
2.需要佔用大量的內存,內存浪費多
而list相反:
1.查找和插入的時間隨着元素的增加而增加
2.佔用空間小,浪費內存很少
所以,dict是用空間來換取時間的一種方法
四、集合
1.是一組key的集合,但不存儲value,並且key不能重複
創建一個set,需要提供一個list作爲輸入集合,s = set([1,2,3]),注意,傳入的參數 [1, 2, 3] 是一個list,而顯示的 set([1, 2, 3]) 只是告訴你這個set內部有1,2,3這3個元素,顯示的[ ]不表示這是一個list
2.重複元素在set中自動被過濾
set可以看成數學意義上的無序和無重複元素的集合,因此,兩個set可以做數學意義上的交集、並集等操作
還有一種集合是forzenset( ),是凍結的集合,它是不可變的,存在哈希值,好處是它可以作爲字典的key,也可以作爲其它集合的元素。缺點是一旦創建便不能更改,沒有add,remove方法
和dict對比
1.set和dict的唯一區別僅在於沒有存儲對應的value
2.set的原理和dict一樣,同樣不可以放入可變對象,因爲無法判斷兩個可變對象是否相等,也就無法保證set內部“不會有重複元素”

7. python 中字典的底層是怎麼實現的?

從以下三個方面來回答:
1.python字典及其特性

字典是Python的一種可變、無序容器數據結構,它的元素以鍵值對的形式存在,鍵值唯一,它的特點搜索速度很快:數據量增加10000倍,搜索時間增加不到2倍;當數據量很大的時候,字典的搜索速度要比列表快成百上千倍1。

2.哈希表

Python字典的底層實現是哈希表。什麼是哈希表,簡單來說就是一張帶索引和存儲空間的表,對於任意可哈希對象,通過哈希索引的計算公式:hash(hashable)%k(對可哈希對象進行哈希計算,然後對結果進行取餘運算),可將該對象映射爲0到k-1之間的某個表索引,然後在該索引所對應的空間進行變量的存儲/讀取等操作。

在這裏插入圖片描述

3.Python字典如何運用哈希表
我們通過描述插入,查詢,刪除,擴容,哈希碰撞這幾個過程來解釋這一切。

插入:

對鍵進行哈希和取餘運算,得到一個哈希表的索引,如果該索引所對應的表地址空間爲空,將鍵值對存入該地址空間;

更新:

對鍵進行哈希和取餘運算,得到一個哈希表的索引,如果該索引所對應的地址空間中健與要更新的健一致,那麼就更新該健所對應的值;

查詢:

對要查找的健進行哈希和取餘運算,得到一個哈希表的索引,如果該索引所對應的地址空間中健與要查詢的健一致,那麼就將該鍵值對取出來;

擴容:

字典初始化的時候,會對應初始化一個有k個空間的表,等空間不夠用的時候,系統就會自動擴容,這時候會對已經存在的鍵值對重新進行哈希取餘運算(重新進行插入操作)保存到其它位置;

碰撞:
有時候對於不同的鍵,經過哈希取餘運算之後,得到的索引值一樣,這時候怎麼辦?這時採用公開尋址的方式,運用固定的模式將鍵值對插入到其它的地址空間,比如線性尋址:如果第i個位置已經被使用,我們就看看第i+1個,第i+2個,第i+3個有沒有被使用…直到找到一個空間或者對空間進行擴容。
比如:我們想存儲 {’小小‘:18}這個鍵值對,經過哈希和取餘運算之後,我們發現,其對應的索引值是0,但是0所指向的空間已經被’小王‘佔用了,這就是碰撞。怎麼辦呢?我們看看0+1對應的索引有沒有被佔用,如果沒有,我們就把’小小‘放在索引1所對應的地址空間中。取的時候,也按照同樣的規則,進行探查。

在這裏插入圖片描述
字典比列查找高效原因
列表查找是按順序一個一個遍歷,當列表越大,查找所用的時間就越久
字典是通過鍵值直接計算得到對應的地址空間,查找一步到位
注意:
1、Cython 中 哈希表的計算公式爲:hash(‘hashable’)&k,其中k 爲2的n次方減1,其實與hash(‘hashable’)%(k+1)的結果一致。
2、解決碰撞的方法,Python用的不是線性尋址,而是一種更爲複雜的尋址模式。

8. is 和==的區別

Python中對象包含的三個基本要素,分別是:id(身份標識)、type(數據類型)和value(值)。
is和== 都是對對象進行比較判斷作用的,但對對象比較判斷的內容並不相同。

‘ == ’ 是python標準操作符中的比較操作符,用來比較判斷兩個對象的 value(值) 是否相等,例如下面兩個字符串間的比較:

>>> a = 'cheesezh'
>>> b = 'cheesezh'
>>> a == b
True

is也被叫做同一性運算符,這個運算符比較判斷的是對象間的唯一身份標識,也就是id是否相同。

>>> x = y = [4,5,6]   # X與Y指向同一個地址
>>> z = [4,5,6]  # Z 指向另一個地址
>>> x == y
True
>>> x == z
True
>>> x is y
True
>>> x is z
False
>>>
>>> print id(x)
3075326572
>>> print id(y)
3075326572
>>> print id(z)
3075328140

注意:只有數值型和字符串型的情況下,a is b才爲True,當a和b是tuple,list,dict或set型時,a is b爲False。

>>> a = 1 #a和b爲數值類型
>>> b = 1
>>> a is b
True
>>> id(a)
14318944
>>> id(b)
14318944
>>> a = 'cheesezh' #a和b爲字符串類型
>>> b = 'cheesezh'
>>> a is b
True
>>> id(a)
42111872
>>> id(b)
42111872
>>> a = (1,2,3) #a和b爲元組類型
>>> b = (1,2,3)
>>> a is b
False
>>> id(a)
15001280
>>> id(b)
14790408
>>> a = [1,2,3] #a和b爲list類型
>>> b = [1,2,3]
>>> a is b
False
>>> id(a)
42091624
>>> id(b)
42082016
>>> a = {'cheese':1,'zh':2} #a和b爲dict類型
>>> b = {'cheese':1,'zh':2}
>>> a is b
False
>>> id(a)
42101616
>>> id(b)
42098736
>>> a = set([1,2,3])#a和b爲set類型
>>> b = set([1,2,3])
>>> a is b
False
>>> id(a)
14819976
>>> id(b)
14822256

9. 深拷貝和淺拷貝的區別是什麼?如何實現?

深拷貝和淺拷貝需要注意的地方就是可變元素的拷貝
在淺拷貝時,拷貝出來的新對象的地址和原對象是不一樣的,但是新對象裏面的可變元素(如列表)的地址和原對象裏的可變元素的地址是相同的,也就是說淺拷貝它拷貝的是淺層次的數據結構(不可變元素),對象裏的可變元素作爲深層次的數據結構並沒有被拷貝到新地址裏面去,而是和原對象裏的可變元素指向同一個地址,所以在新對象或原對象裏對這個可變元素做修改時,兩個對象是同時改變的,但是深拷貝不會這樣,這個是淺拷貝相對於深拷貝最根本的區別。
也可以這樣理解:
深拷貝就是完全跟以前就沒有任何關係了,原來的對象怎麼改都不會影響當前對象
淺拷貝,原對象的list元素改變的話會改變當前對象,如果當前對象中list元素改變了,也同樣會影響原對象。
淺拷貝就是藕斷絲連
深拷貝就是離婚了

通常複製的時候要用深拷貝,因爲淺拷貝後,兩個對象中不可變對象指向不同地址,相互不會改變,但是兩個對象中的可變元素是指向相同的地址,一個變了,另一個會同時改變,會有影響(list是可變對象)。

如果要讓原list和copy list沒有影響怎麼辦?
深拷貝,拷貝後完全開闢新的內存地址來保存之前的對象,雖然可能地址執行的內容可能相同(同一個地址,例如’s’),但是不會相互影響。
比如:
List1=[‘a’,’b’,’c’]
List2=[‘a’,’b’,’c’]
兩個列表中的’a’的地址是相同的
Id(list1[0])=id(list2[0]),但是兩個列表的地址是不同的

舉例:

import copy
a=[1,2,3,4,5,['a','b']]
#原始對象
b=a#賦值,傳對象的引用
c=copy.copy(a)#對象拷貝,淺拷貝
d=copy.deepcopy(a)#對象拷貝,深拷貝
print "a=",a,"    id(a)=",id(a),"id(a[5])=",id(a[5])
print "b=",b,"    id(b)=",id(b),"id(b[5])=",id(b[5])
print "c=",c,"    id(c)=",id(c),"id(c[5])=",id(c[5])
print "d=",d,"    id(d)=",id(d),"id(d[5])=",id(d[5])
print "*"*70

a.append(6)#修改對象a
a[5].append('c')#修改對象a中的['a','b']數組對象
print "a=",a,"    id(a)=",id(a),"id(a[5])=",id(a[5])
print "b=",b,"    id(b)=",id(b),"id(b[5])=",id(b[5])
print "c=",c,"       id(c)=",id(c),"id(c[5])=",id(c[5])
print "d=",d,"            id(d)=",id(d),"id(d[5])=",id(d[5])

結果:
在這裏插入圖片描述

10. Python 中的 pass 語句有什麼作用?

在編寫代碼時只寫框架思路,具體實現還未編寫就可以用 pass 進行佔位,使程序不報錯,不會進行任何操作。

11. 能否解釋一下 *args 和 **kwargs?

函數形參:*args 和 **kwargs

*args:將實參中按照位置傳值,多出來的值都給args,以元組方式實現。

def multiply(*args): #累乘
    z = 1
    for num in args:
        z *= num
    print(z)

multiply(4, 5)
multiply(10, 9)
multiply(2, 3, 4)
multiply(3, 5, 10, 6)


執行結果:
20
90
24
900
def func(x, *args):
    print(x)
    print(args)

func(1, 2, 3, 4, 5)  # 1->x  2,3,4,5->args

執行結果:
1
(2,3,4,5)

**是形參中按照關鍵字傳值把多餘的傳值以字典的方式實現。

def print_values(**kwargs):
    for key, value in kwargs.items():
        print("The value of {} is {}".format(key, value))
print_values(my_name="Sammy", your_name="Casey")

執行結果:
The value of my_name is Sammy
The value of your_name is Casey

12. 迭代器、可迭代對象、生成器分別是什麼?生成器的作用和使用場景?

  • 可迭代對象
    像list,tuple,set,dict,str等可以直接作用於for循環的對象,稱爲可迭代對象。可迭代對象實現了__iter__方法,用於返回迭代器。
    iter()
    將可迭代對象轉化爲迭代器。
demo = [1,2,3,4]
print(isinstance(demo, Iterable)) //True
iter_object = iter(demo)  #可迭代對象轉換爲迭代器
print(iter_object) //<list_iterator object at 0x00000258DC5EF748>
  • 迭代器
    迭代器對象就是實現了iter() 和 next() 方法的對象.迭代器是通過next()來實現的,每調用一次他就會返回下一個元素,當沒有下一個元素的時候返回一個StopIteration異常,所以實際上定義了這個方法的都算是迭代器。可以用通過下面例子來體驗一下迭代器:
    迭代器類似於一個遊標,卡到哪裏就是哪裏,可以通過這個來訪問某個可迭代對象的元素
In [38]: s = 'ab'

In [39]: it = iter(s)

In [40]: it
Out[40]: <iterator at 0x1068e6d50>

In [42]: it.next()
Out[42]: 'a'

In [43]: it.next()
Out[43]: 'b
  • 生成器(Generators)
    生成器是構造迭代器的最簡單有力的工具,與普通函數不同的只有在返回一個值的時候使用yield來替代return,然後yield會自動構建好next()和iter()。是不是很省事。例如:
定義生成器方式一:
gen = (x*x for x in range(5))
 print(gen) 
//Out:<generator object <genexpr> at 0x00000258DC5CD8E0>

我們可以利用next()訪問生成器下一個元素
print(next(gen)) //0
print(next(gen)) //1
...
print(next(gen)) //16
print(next(gen)) //StopIteration

用for循環遍歷
for n in gen:  
print(n) //0 1 4  9 16


定義生成器方式二:
def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

>>> for char in reverse('golf'):
...     print char
...
f
l
o
g

生成器最佳應用場景:你不想同一時間將所有計算出來的大量結果集分配到內存當中,特別是結果集裏還包含循環。比方說,循環打印1000000個數,我們一般會使用xrange()而不是range(),因爲前者返回的是生成器,後者返回的是列表(列表消耗大量空間)。

13. yield 和 return 的工作原理

在這裏插入圖片描述
在這裏插入圖片描述

1、.print並不會阻斷程序的執行,就不用多說了。
2、func2()方法中的循環執行第一次就被return結束掉了(後面的2、3、4就不會有返回的機會了)3、yield你可以通俗的叫它"輪轉容器",可用現實的一種實物來理解:水車,先yield來裝入數據、產出generator object、使用next()來釋放;好比水(數據)裝入水車(yield)中,隨着輪子轉動(調用next()),被轉到下面的水槽就能將水送入水道中流入田裏。

在這裏插入圖片描述

14. 請解釋 Python 中的閉包?

在一個外函數中定義了一個內函數,內函數裏運用了外函數的臨時變量,並且外函數的返回值是內函數的引用。這樣就構成了一個閉包。
一般情況下,在我們認知當中,如果一個函數結束,函數的內部所有東西都會釋放掉,還給內存,局部變量都會消失。但是閉包是一種特殊情況,如果外函數在結束的時候發現有自己的臨時變量將來會在內部函數中用到,就把這個臨時變量綁定給了內部函數,然後自己再結束。

#閉包函數實現的基本模板

def outter(xxx):
     def inner()
           xxx
      return inner

#爲函數體傳參的兩個方案
#1.直接傳入參數
#2.在函數體內部定義變量

15. python 中的裝飾器是什麼?如何實現?使用場景?

  • python的裝飾器說白了就是閉包函數的一種應用場景,在運用的時候我們遵循:
    開放封閉原則:對修改封閉,對拓展開放。
    裝飾器概念:裝飾他人的器具,本身可以是任意可調用的對象,被裝飾者也可以是任意可調用對象
    裝飾器的原則:1.不可修改被裝飾對象的源代碼,2不修改被裝飾對象的調用方式
    裝飾器的目標:在遵循1和2的前提下,爲被裝飾對象添加上新功能
  • 如何實現:裝飾器是通過閉包的方式實現的,外函數接收一個函數作爲外函數的臨時變量,然後在內函數中執行這個函數。內函數將需要的參數接收進來並傳給執行的函數,然後將執行結果返回。在內函數中,可以添加額外的功能的代碼,這些額外的功能就是裝飾器添加的功能。最後外函數將內函數返回。使用裝飾器來裝飾函數時,在被裝飾的函數的前一行,使用@裝飾器函數名的形式來裝飾,則函數本身的功能正常實現,裝飾器中添加的功能也實現了。如上面代碼中打印被裝飾函數的函數名。
    在這裏插入圖片描述
    在這裏插入圖片描述

在這裏插入圖片描述

16. python 你平時主要用過哪些包?

time、random、numpy、Matplotlib、scikit-learn: Machine Learning in Python、Pandas、jieba

17. python 中的 map 是怎麼實現的?

map()函數的簡介以及語法:
map是python內置函數,會根據提供的函數對指定的序列做映射。
map()函數的格式是:
map(function,iterable,…)
第一個參數接受一個函數名,後面的參數接受一個或多個可迭代的序列,返回的是一個集合。
把函數依次作用在list中的每一個元素上,得到一個新的list並返回。注意,map不改變原list,而是返回一個新list。

items = [1,2,3,4,5]
lamb'd x:x**2爲一個表達式,接受一個參數x並返回x的平方
squared=map(lamdba x:x**2, items)


執行結果:[1,4,9,16,25]

18. Python 是如何進行內存管理的?

Python引入了一個機制:引用計數。
python內部使用引用計數,來保持追蹤內存中的對象,Python內部記錄了對象有多少個引用,即引用計數,當對象被創建時就創建了一個引用計數,當對象不再需要時,這個對象的引用計數爲0時,它被垃圾回收。
引用計數增加情況:
1.
對象被創建:x = 4
2.
另外的別人被創建:y = x
3.
被作爲參數傳遞給函數:foo(x)
4.
作爲容器對象的一個元素:a = [1, x, ‘33’]
引用計數減少情況:
1.
一個本地引用離開了它的作用域。比如上面的foo(x)
函數結束時,x指向的對象引用減1。
2.
對象的別名被顯式的銷燬:del x ;或者del
y
3.
對象的一個別名被賦值給其他對象:x = 789
4.
對象從一個窗口對象中移除:myList.remove(x)
5.
窗口對象本身被銷燬:del myList,或者窗口對象本身離開了作用域。

垃圾回收
1、當內存中有不再使用的部分時,垃圾收集器就會把他們清理掉。它會去檢查那些引用計數爲0的對象,然後清除其在內存的空間。當然除了引用計數爲0的會被清除,還有一種情況也會被垃圾收集器清掉:當兩個對象相互引用時,他們本身其他的引用已經爲0了。
2、垃圾回收機制還有一個循環垃圾回收器, 確保釋放循環引用對象(a引用b, b引用a, 導致其引用計數永遠不爲0)。

內存池機制
在Python中,許多時候申請的內存都是小塊的內存,這些小塊內存在申請後,很快又會被釋放,由於這些內存的申請並不是爲了創建對象,所以並沒有對象一級的內存池機制。這就意味着Python在運行期間會大量地執行malloc和free的操作,頻繁地在用戶態和核心態之間進行切換,這將嚴重影響Python的執行效率。爲了加速Python的執行效率,Python引入了一個內存池機制,用於管理對小塊內存的申請和釋放。

Python提供了對內存的垃圾收集機制,但是它將不用的內存放到內存池而不是返回給操作系統。
Python中所有小於256個字節的對象都使用pymalloc實現的分配器,而大的對象則使用系統的
malloc。另外Python對象,如整數,浮點數和List,都有其獨立的私有內存池,對象間不共享他們的內存池。也就是說如果你分配又釋放了大量的整數,用於緩存這些整數的內存就不能再分配給浮點數。

19. 簡述什麼是面向過程編程和麪向對象編程?

面向過程的程序設計(Procedure-Oriented Programming)是一種以過程爲中心的設計方式。在該方式中,將目標功能的實現分爲多個步驟。程序依據步驟的過程一步步執行,最終實現程序功能。
面向對象的程序設計(Object-Oriented Programming,簡記爲OOP),是當下最流行的程序設計方式之一。在面向對象的設計思想中,將程序視爲多個對象共同協作的結果。程序被劃分爲多個子模塊,再由多個對象完成各自模塊最終實現程序的功能。

  • 舉例:級要舉辦元旦晚會演出,有三名同學報名了歌舞表演環節,分別是唱歌的小明、唱歌的小李和跳舞的小紅。歌舞環節由三個對象(小明、小李和小紅),每個對象實現各自的模塊(小明唱歌、小李唱歌、小紅跳舞),由此實現了程序的功能。

面向對象的解決步驟:

掃地:

1.1 拿出掃地機器人

1.2 掃地機器人!開始幹活!
洗衣:

2.1 找到洗衣機,放入衣服和洗衣液

2.2 洗衣機!開始幹活!
吹風:

3.1 拿出電風扇

3.2 電風扇!開始幹活!
這裏的掃地機器人、洗衣機、電風扇扮演着對象(Object)。
面向過程的解決步驟:

掃地:

1.1 拿掃把和掃帚

1.2 將垃圾彙集到某處

1.3 將垃圾掃進掃帚

1.4 將掃帚的垃圾倒進垃圾桶
洗衣:

2.1 拿盆接好水,倒入洗衣液

2.2 放入衣服並浸泡

2.3 揉搓衣服

2.4 換清水漂洗
比較總結:

  • 面向過程編程中,開發者注重於程序功能實現的過程,編程過程中扮演類似執行者的角色
  • 面向對象編程中,開發者注重於對象的創建和調用,編程過程中扮演類似指揮者的角色
  • 面向過程編程中,開發者可以精準把控程序執行的每一步和每一個細節(比如:手洗衣服過程中,衣服的哪個部位需要多搓一會,扇扇子的時候多扇頭還是扇腳)
  • 面向對象編程中,開發者無需知道對象的每一個細節,對象如何工作交給對象的設計者完成(當然開發者常常扮演設計者的角色,同時已經有很多東西已經被設計好了)
  • 面向過程設計方式在中小型項目中更有優勢。開發者只需要想好步驟,再依據步驟寫下來即可。
  • 面向對象設計方式在大中型項目中更有優勢。開發者設計好對象後,只需調用對象完成任務使得代碼更簡潔易懂易於維護。
  • 面向對象設計方式在宏觀上是面向對象的,在微觀上依舊是面向過程的。 在每個對象的內部有着它們的行爲屬性(掃地、洗衣服、吹風),設計者在設計如何讓它們工作的過程中依舊是按照面向過程的思想讓程序按照步驟執行。由此可見:面向過程是程序設計的基本方式

20. 介紹一下繼承和多態

繼承:程序向上總結
將子類共同的行爲和屬性集中寫到父類中,通過繼承,所有子類都能自動獲得這些屬性和行爲,大大減少了重複代碼。
繼承成爲多態實現的基礎。
多態:程序向下擴展(當子類和父類都存在相同的run()方法時,我們說,子類的run()覆蓋了父類的run(),在代碼運行的時候,總是會調用子類的run()。這樣,我們就獲得了繼承的另一個好處:多態。)
父類某些行爲,子類進行繼承重寫,從而實現:同種行爲,不同的實現。

21. 介紹python 的魔術方法,new,init,call 的區別是什麼

  • 在Python 中 存在於類裏面的構造方法__init__()負責將類的實例化,而在__init__()啓動之前,new()決定是否要使用該__init__()方法,因爲__new__()可以調用其他類的構造方法或者直接返回別的對象來作爲本類的實例。
  • 如果將類比喻爲工廠,那麼__init__()方法則是該工廠的生產工人,init()方法接受的初始化參數則是生產所需原料,init()方法會按照方法中的語句負責將原料加工成實例以供工廠出貨。而 new()則是生產部經理,new()方法可以決定是否將原料提供給該生產部工人,同時它還決定着出貨產品是否爲該生產部的產品,因爲這名經理可以借該工廠的名義向客戶出售完全不是該工廠的產品。
class A(object):
    def __new__(cls, x):
        print 'this is in A.__new__, and x is ', x
        return super(A, cls).__new__(cls)

    def __init__(self, y):
        print 'this is in A.__init__, and y is ', y

class B(A):
    def __new__(cls, z):
        print 'this is in B.__new__, and z is ', z
        return A.__new__(cls, z)

    def __init__(self, m):
        print 'this is in B.__init__, and m is ', m

if __name__ == '__main__':
    a = A(100)
    print '=' * 20
    b = B(200)
    print type(b)

執行結果:
this is in A.__new__, and x is  100
this is in A.__init__, and y is  100
====================
this is in B.__new__, and z is  200
this is in A.__new__, and x is  200
this is in B.__init__, and m is  200
<class '__main__.B'>

1.定義A類作爲下面類的父類,A類繼承object類,因爲需要重寫A類的__new__()函數,所以需要繼承object基類,成爲新式類,經典類沒有__new__()函數;
2.子類在重寫__new__()函數時,寫return時必須返回有繼承關係的類的__new__()函數調用,即上面代碼中的B類繼承自A類,則重寫B類的__new__()函數,寫return時,只能返回A.new(cls)或者object.new(cls);
3.B類的__new__()函數會在B類實例化時被調用,自動執行其中的代碼語句,但是重寫__new__()函數不會影響類的實例化結果,也就是說不管寫return時返回的是A的還是object的,B類的實例化對象就是B類的,而不會成爲A類的實例化對象;只是在實例化時,如果返回的是A.new(cls),則會執行A類中定義的__new__()函數;
4.new()函數確定了類的參數的個數,object類默認定義的__new__()函數的參數爲(cls, *more),但如果在子類中重寫了__new__(cls, x), 則實例化類時,需要傳入一個x參數,而__init__()函數接受到的有兩個參數,一個是實例化生成的實例對象self代替,一個是傳入的實參x的值;

  • init()是python的一個構造方法,在定義類時,用於初始化的一些操作;它能實現的功能及原理相對來講也是比較簡單一點,就是在實例化該類時,自動執行__init__()方法定義的內容;
>>> class A(object):
    def __init__(self, x):
        self.x = x
        print '__init__ called.'
    def foo(self):
        print self.x

     
>>> a = A('123')
__init__ called.
>>> 
>>> a.foo()
123

call_()方法能夠讓類的實例對象,像函數一樣被調用;

class A(object):
    def __call__(self, x):
        print('__call__ called, print x: ', x)

>>> a = A()
>>> a('123')
__call__called, print x: 123

22. Python 如何實現單例模式?

詳細完整版實現單例模式的各種方法
該模式的主要目的是確保某一個類只有一個實例存在。當你希望在整個系統中,某個類只能出現一個實例時,單例對象就能派上用場。
方式一:Python 的模塊
Python 的模塊就是天然的單例模式,因爲模塊在第一次導入時,會生成 .pyc 文件,當第二次導入時,就會直接加載 .pyc 文件,而不會再次執行模塊代碼。因此,我們只需把相關的函數和數據定義在一個模塊中,就可以獲得一個單例對象了。如果我們真的想要一個單例類,可以考慮這樣做:

class Singleton(object):
    def foo(self):
        pass
singleton = Singleton()

將上面的代碼保存在文件 mysingleton.py 中,要使用時,直接在其他文件中導入此文件中的對象,這個對象即是單例模式的對象

from a import singleton

方式二:基於__new__方法實現
當我們實例化一個對象時,是先執行了類的__new__方法(我們沒寫時,默認調用object.new),實例化對象;然後再執行類的__init__方法,對這個對象進行初始化,所有我們可以基於這個,實現單例模式

23. 數據庫中索引的作用,主鍵索引工作的大體流程。

作用:提高數據的查詢速度
第一,通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性。
第二,可以大大加快 數據的檢索速度,這也是創建索引的最主要的原因。
第三,可以加速表和表之間的連接,特別是在實現數據的參考完整性方面特別有意義。
第四,在使用分組和排序 子句進行數據檢索時,同樣可以顯著減少查詢中分組和排序的時間。
第五,通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的性能。

主鍵索引工作的大體流程:
主鍵:唯一標識記錄(常見的什麼什麼ID),不允許重複,不允許爲空。
主鍵默認建立唯一索引,所以創建表時,不能在同一個字段上建立兩個索引
主鍵一定是唯一性索引,唯一性索引並不一定就是主鍵。
一個表中可以有多個唯一性索引,但只能有一個主鍵(圖中多個主鍵指複合主鍵)
主鍵列不允許空值,而唯一性索引列允許空值。
索引可以提高查詢的速度。

主鍵和索引都是鍵,不過主鍵是邏輯鍵,索引是物理鍵,意思就是主鍵不實際存在,而索引實際存在在數據庫中

在這裏插入圖片描述

24. 給定一張表,寫出 SQL 語句,字段id,name,subject,grade。求出總分大於 300 分的學生的名單,求出沒有不及格成績的學生名單。

create table students(
    -> id int primary key auto_increment ,
    -> studentname varchar(20) not null unique,
    -> subject varchar(20) not null,
    -> grade float   )


select * from students where grade > = 60 ;

25. 數據庫聯接操作,左連接,右鏈接,全鏈接的操作以及區別(手寫 SQL 語句)

內連表:table_A inner join table_B,表 table_A 和 table_B 相匹配的行出現在結果集中
左連表:table_A left join table_B,表 table_A 和 table_B 相匹配的行出現在結果集中,外加表 table_A 中獨有的數據,爲對應的數據用 null 填充
右連表:table_A right join table_B,表 table_A 和 table_B 相匹配的行出現在結果集中,外加表 table_B 中獨有的數據,爲對應的數據用 null 填充

26. 數據庫的三大範式的理解。

  • 第一範式(1NF):列不可拆分 , 即無重複的域。指數據庫表的每一列都是不可分割的基本數據項。 符合第一範式的特點就有:有主關鍵字、主鍵不能爲空、主鍵不能重複,字段不可以再分。
    理解: 第零範式中重複的字段抽取出來,作爲表的數據,從而形成一個穩定的、冗餘數據少得表結構。
    第二範式(2NF):滿足屬性對主鍵是完全函數依賴的,因此,滿足第二範式的表當然也是滿足第一範式的,第二範式的目的就是消除表中的部分依賴。
    完全函數依賴
    設有屬性集K和P,若K中的所有屬性共同能夠推出P中的任意屬性,且對於K的任何真子集,都不能推出P中的任意屬性,則成K完全函數依賴P。
    部分函數依賴
    與上相似,只是,K中存在真子集使得,該子集能推出p中任意屬性。
    舉例
    有一張學生成績表,包含如下屬性(學生Id,課程Id,課程分數,學生名字),其中,主鍵爲(學生id,課程id),表中的數據如下:
    在這裏插入圖片描述
    此時這張表的設計就不滿足第二範式, 因爲 主鍵(學生id,課程id) 能夠唯一確定學生的姓名(K中存在真子集使得,該子集能推出p中任意屬性。),因此,不滿足屬性完全函數依賴主鍵,因此不是第二範式。
    從上面的表數據易知,不滿足第二範式的表至少有以下幾個缺點:
    1:數據重複,浪費空間,因爲每存一條記錄,都要存學生的名字,這樣就是得存在大量重複的數據。
    2: 插入異常,若一個表三個屬性:姓名、成績、老師名稱,如果該老師沒有教任何學生,就無法錄入老師信息,產生插入異常。
    3: 更新異常,刪除異常等
    解決方法
    student_name字段放入學生表中,即消除表中的部分依賴。
    第三範式(3NF): 第三範式是指在滿足第二範式的情況下,消除表中的傳遞依賴。
    傳遞依賴,就是指x–>y,y–>z,那麼可以得到x–>z.
    傳遞依賴常發生在主鍵、外鍵、外鍵相關的屬性上,例如,假設有:
    學生表(學生id,學生姓名,院系id,院系名) ,此處主鍵爲(學生id),外鍵爲(院系id)
    院系表(院系id,院長名稱),主鍵爲 (院系id)
    很明顯,此處存在傳遞依賴,因爲 學生id 可以唯一確定 院系id,而 院系id 可以唯一確定 院系名。
    不滿足第三範式的表至少有以下幾個缺點:
    1 : 數據重複,浪費空間,因爲學生表每存一條記錄,都會記錄住院系的名字,存在大量的重複數據。
    2: 插入異常,若新建一個院系,而該院系沒有學生的話,該院系就沒有名字。
    3: 更新異常,刪除異常等

27 . 什麼是 SQL 注入?

SQL注入:是現在普通使用的一種攻擊手段,通過把非法的SQL命令插入到Web表單中或頁面請求查詢字符串中,最終達到欺騙服務器執行惡意的SQL語句的目的。SQL注入一旦成功,輕則直接繞開服務器驗證,直接登錄成功,重則將服務器端數據庫中的內容一覽無餘,更有甚者,直接篡改數據庫內容等。
SQL注入的產生條件:1). 有參數傳遞;2). 參數值帶入數據庫查詢並且執行
爲防止SQL注入,需要對用戶的輸入進行過濾,因爲在Web攻防中,我們永遠不要相信用戶的輸入。
防止SQL注入包括:使用預編譯語句,綁定變量。
使用安全的存儲過程對抗SQL注入。
檢查數據類型。
使用安全函數。

28. Python 的底層實現,用到的排序函數(手寫冒泡算法)

參考博客

29. 解釋一下 python 中的 GIL 是什麼

參考

30. GIL 是單線程的,那麼 python 中多線程的實現有什麼用。

進程之間不能共享內存,但線程之間共享內存非常容易。

操作系統在創建進程時,需要爲該進程重新分配系統資源,但創建線程的代價則小得多。因此,使用多線程來實現多任務併發執行比使用多進程的效率高。

Python 語言內置了多線程功能支持,而不是單純地作爲底層操作系統的調度方式,從而簡化了 Python 的多線程編程。
比如一個瀏覽器必須能同時下載多張圖片;一個 Web 服務器必須能同時響應多個用戶請求;圖形用戶界面(GUI)應用也需要啓動單獨的線程,從主機環境中收集用戶界面事件……總之,多線程在實際編程中的應用是非常廣泛的。

31. 對於多線程,我是怎麼去使用的,如果要我去設計一個線程池,我該怎麼去設計。

參考

32. python 協程實現一個生產者消費者模型。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章