TCP與UDP區別javascript
TCP提供的是面向鏈接的、可靠的數據流傳輸;php
UDP提供的是非面向鏈接的、不可靠的數據流傳輸。java
TCP提供可靠的服務,經過TCP鏈接傳送的數據,無差錯、不丟失,不重複,按序到達;UDP盡最大努力交付,即不保證可靠交付。git
TCP面向字節流;程序員
UDP面向報文。github
TCP鏈接只能是點到點的;算法
UDP支持一對1、一對多、多對一和多對多的交互通訊。編程
TCP首部開銷20字節;數組
UDP的首部開銷小,只有8個字節。瀏覽器
TCP的邏輯通訊信道是全雙工的可靠信道;
UDP的邏輯通訊信道是不可靠信道。
TCP定義
TCP(Transmission Control Protocol 傳輸控制協議)是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議,由IETF的RFC 793定義。
UDP定義
UDP (User Datagram Protocol 用戶數據報協議)是OSI(Open System Interconnection開放式系統互聯) 參考模型中一種無鏈接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務。
--------------------------------------------------------------
假設 A 爲客戶端,B 爲服務器端。
首先 B 處於 LISTEN(監聽)狀態,等待客戶的鏈接請求。
A 向 B 發送鏈接請求報文段,SYN=1,ACK=0,選擇一個初始的序號 x。
B 收到鏈接請求報文段,若是贊成創建鏈接,則向 A 發送鏈接確認報文段,SYN=1,ACK=1,確認號爲 x+1,同時也選擇一個初始的序號 y。
A 收到 B 的鏈接確認報文段後,還要向 B 發出確認,確認號爲 y+1,序號爲 x+1。
B 收到 A 的確認後,鏈接創建。
三次握手的緣由
第三次握手是爲了防止失效的鏈接請求到達服務器,讓服務器錯誤打開鏈接。
失效的鏈接請求是指,客戶端發送的鏈接請求在網絡中滯留,客戶端由於沒及時收到服務器端發送的鏈接確認,所以就從新發送了鏈接請求。滯留的鏈接請求並非丟失,以後仍是會到達服務器。若是不進行第三次握手,那麼服務器會誤認爲客戶端從新請求鏈接,而後打開了鏈接。可是並非客戶端真正打開這個鏈接,所以客戶端不會給服務器發送數據,這個鏈接就白白浪費了。
如下描述不討論序號和確認號,由於序號和確認號的規則比較簡單。而且不討論 ACK,由於 ACK 在鏈接創建以後都爲 1。
A 發送鏈接釋放報文段,FIN=1。
B 收到以後發出確認,此時 TCP 屬於半關閉狀態,B 能向 A 發送數據可是 A 不能向 B 發送數據。
當 B 要再也不須要鏈接時,發送鏈接釋放請求報文段,FIN=1。
A 收到後發出確認,進入 TIME-WAIT 狀態,等待 2MSL 時間後釋放鏈接。
B 收到 A 的確認後釋放鏈接。
四次揮手的緣由
客戶端發送了 FIN 鏈接釋放報文以後,服務器收到了這個報文,就進入了 CLOSE-WAIT 狀態。這個狀態是爲了讓服務器端發送還未傳送完畢的數據,傳送完畢以後,服務器會發送 FIN 鏈接釋放報文。
TIME_WAIT
客戶端接收到服務器端的 FIN 報文後進入此狀態,此時並非直接進入 CLOSED 狀態,還須要等待一個時間計時器設置的時間 2MSL。這麼作有兩個理由:
確保最後一個確認報文段可以到達。若是 B 沒收到 A 發送來的確認報文段,那麼就會從新發送鏈接釋放請求報文段,A 等待一段時間就是爲了處理這種狀況的發生。
等待一段時間是爲了讓本鏈接持續時間內所產生的全部報文段都從網絡中消失,使得下一個新的鏈接不會出現舊的鏈接請求報文段。
---------------------------------------------------------------------------
窗口是緩存的一部分,用來暫時存放字節流。發送方和接收方各有一個窗口,接收方經過 TCP 報文段中的窗口字段告訴發送方本身的窗口大小,發送方根據這個值和其它信息設置本身的窗口大小。
發送窗口內的字節都容許被髮送,接收窗口內的字節都容許被接收。若是發送窗口左部的字節已經發送而且收到了確認,那麼就將發送窗口向右滑動必定距離,直到左部第一個字節不是已發送而且已確認的狀態;接收窗口的滑動相似,接收窗口左部字節已經發送確認並交付主機,就向右滑動接收窗口。
接收窗口只會對窗口內最後一個按序到達的字節進行確認,例如接收窗口已經收到的字節爲 {31, 32, 34, 35},其中 {31, 32} 按序到達,而 {34, 35} 就不是,所以只對字節 32 進行確認。發送方獲得一個字節的確認以後,就知道這個字節以前的全部字節都已經被接收。
TCP 使用超時重傳來實現可靠傳輸:若是一個已經發送的報文段在超時時間內沒有收到確認,那麼就重傳這個報文段。
一個報文段從發送再到接收到確認所通過的時間稱爲往返時間 RTT,加權平均往返時間 RTTs 計算以下:
超時時間 RTO 應該略大於 RTTs,TCP 使用的超時時間計算以下:
其中 RTTd 爲誤差。
流量控制是爲了控制發送方發送速率,保證接收方來得及接收。
接收方發送的確認報文中的窗口字段能夠用來控制發送方窗口大小,從而影響發送方的發送速率。將窗口字段設置爲 0,則發送方不能發送數據。
若是網絡出現擁塞,分組將會丟失,此時發送方會繼續重傳,從而致使網絡擁塞程度更高。所以當出現擁塞時,應當控制發送方的速率。這一點和流量控制很像,可是出發點不一樣。流量控制是爲了讓接收方能來得及接受,而擁塞控制是爲了下降整個網絡的擁塞程度。
TCP 主要經過四種算法來進行擁塞控制:慢開始、擁塞避免、快重傳、快恢復。發送方須要維護一個叫作擁塞窗口(cwnd)的狀態變量。注意擁塞窗口與發送方窗口的區別,擁塞窗口只是一個狀態變量,實際決定發送方能發送多少數據的是發送方窗口。
爲了便於討論,作以下假設:
發送的最初執行慢開始,令 cwnd=1,發送方只能發送 1 個報文段;當收到確認後,將 cwnd 加倍,所以以後發送方可以發送的報文段數量爲:二、四、8 ...
注意到慢開始每一個輪次都將 cwnd 加倍,這樣會讓 cwnd 增加速度很是快,從而使得發送方發送的速度增加速度過快,網絡擁塞的可能也就更高。設置一個慢開始門限 ssthresh,當 cwnd >= ssthresh 時,進入擁塞避免,每一個輪次只將 cwnd 加 1。
若是出現了超時,則令 ssthresh = cwnd/2,而後從新執行慢開始。
在接收方,要求每次接收到報文段都應該發送對已收到有序報文段的確認,例如已經接收到 M1 和 M2,此時收到 M4,應當發送對 M2 的確認。
在發送方,若是收到三個重複確認,那麼能夠確認下一個報文段丟失,例如收到三個 M2 ,則 M3 丟失。此時執行快重傳,當即重傳下一個報文段。
在這種狀況下,只是丟失個別報文段,而不是網絡擁塞,所以執行快恢復,令 ssthresh = cwnd/2 ,cwnd = ssthresh,注意到此時直接進入擁塞避免。
------------------------------------------------------------------------------------------------------
cookie 和session 的區別:
一、cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。
二、cookie不是很安全,別人能夠分析存放在本地的COOKIE並進行COOKIE欺騙
考慮到安全應當使用session。
三、session會在必定時間內保存在服務器上。當訪問增多,會比較佔用你服務器的性能
考慮到減輕服務器性能方面,應當使用COOKIE。
四、單個cookie保存的數據不能超過4K,不少瀏覽器都限制一個站點最多保存20個cookie。
cookie 和session 的聯繫:
session是經過cookie來工做的
session和cookie之間是經過$_COOKIE['PHPSESSID']來聯繫的,經過$_COOKIE['PHPSESSID']能夠知道session的id,從而獲取到其餘的信息。
-----------------------------------------------------------------------------------------------------------------------
hashMap源碼獲取元素的位置:
static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); }
解釋:
h:爲插入元素的hashcode
length:爲map的容量大小
&:與操做 好比 1101 & 1011=1001
若是length爲2的次冪 則length-1 轉化爲二進制一定是11111……的形式,在於h的二進制與操做效率會很是的快,
並且空間不浪費;若是length不是2的次冪,好比length爲15,則length-1爲14,對應的二進制爲1110,在於h與操做,
最後一位都爲0,而0001,0011,0101,1001,1011,0111,1101這幾個位置永遠都不能存放元素了,
空間浪費至關大,更糟的是這種狀況中,數組可使用的位置比數組長度小了不少,
這意味着進一步增長了碰撞的概率,減慢了查詢的效率!這樣就會形成空間的浪費
---------------------------------------------------------------------
徹底函數依賴
在一張表中,若 X → Y,且對於 X 的任何一個真子集(假如屬性組 X 包含超過一個屬性的話),X ' → Y 不成立,那麼咱們稱 Y 對於 X 徹底函數依賴,記做 X F→ Y。(那個F應該寫在箭頭的正上方,沒辦法打出來……,正確的寫法如圖1)
-------------------------------------------------------------------------
內鏈接:指鏈接結果僅包含符合鏈接條件的行,參與鏈接的兩個表都應該符合鏈接條件。
外鏈接:鏈接結果不只包含符合鏈接條件的行同時也包含自身不符合條件的行。包括左外鏈接、右外鏈接和全外鏈接。
----------------------------------------------------------------------------
==和equals()的區別
1)對於==,若是做用於基本數據類型的變量,則直接比較其存儲的 「值」是否相等;
若是做用於引用類型的變量,則比較的是所指向的對象的地址
2)對於equals方法,注意:equals方法不能做用於基本數據類型的變量
若是沒有對equals方法進行重寫,則比較的是引用類型的變量所指向的對象的地址;
諸如String、Date等類對equals方法進行了重寫的話,比較的是所指向的對象的內容。
-----------------------------------------------------------
(1)區別
(2)String 字符串常量;
/** Strings are constant; their values cannot be changed after they * are created. String buffers support mutable strings. * Because String objects are immutable they can be shared. * 字符串是不變的,他們的值在創造後不能改變。 * 字符串緩衝區支持可變字符串,由於字符串對象是不可變的,因此它們能夠共享。 * * @see StringBuffer * @see StringBuilder * @see Charset * @since 1.0 */ public final class String implements Serializable, Comparable<String>, CharSequence { private static final long serialVersionUID = -6849794470754667710L; private static final char REPLACEMENT_CHAR = (char) 0xfffd;
這句話總結概括了String的兩個最重要的特色:
(3)StringBuffer字符串變量(線程安全)是一個容器,最終會經過toString方法變成字符串;
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, Appendable, CharSequence { /** * Constructs a string buffer with no characters in it and an * initial capacity of 16 characters. */ public StringBuffer() { super(16); } public synchronized StringBuffer append(int i) { super.append(i); return this; } public synchronized StringBuffer delete(int start, int end) { super.delete(start, end); return this; } }
(4)StringBuilder 字符串變量(非線程安全)。
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, Appendable, CharSequence { public StringBuilder() { super(16); } public StringBuilder append(String str) { super.append(str); return this; } public StringBuilder delete(int start, int end) { super.delete(start, end); return this; } }
(1) String在修改時不會改變對象自身
在每次對 String 類型進行改變的時候其實都等同於生成了一個新的 String 對象,而後將指針指向新的 String 對象,因此常常改變內容的字符串最好不要用 String 。
String str = "abc";//地址str1 str = "def";//地址str2
(2) StringBuffer在修改時會改變對象自身
每次結果都會對 StringBuffer 對象自己進行操做,而不是生成新的對象,再改變對象引用。因此在通常狀況下咱們推薦使用 StringBuffer ,特別是字符串對象常常改變的狀況下。StringBuffer 上的主要操做是 append 和 insert 方法。
StringBuffer strBuffer = new StringBuffer("abc");//地址strBuffer,值是abc strBuffer.append("def");//地址strBuffer,值是abcdef
public class MyTest { public static void main(String[] args) { String str = "abc"; StringBuffer strBuffer = new StringBuffer(); strBuffer.append("def"); System.out.println(a.getClass() + "@" + str.hashCode()); System.out.println(b.getClass() + "@" + strBuffer.hashCode()); str = "aaa"; strBuffer.append("bbb"); System.out.println(a.getClass() + "@" + str.hashCode()); System.out.println(b.getClass() + "@" + strBuffer.hashCode()); } }
小結:String的地址已改變,對象已經改變爲另外一個;StringBuffer地址不變,仍是一樣的對象。
(1)String
StringBuffer s = null; StringBuffer s = 「abc」;
(2)StringBuffer
StringBuffer s = null; //結果警告:Null pointer access: The variable result can only be null at this location
StringBuffer s = new StringBuffer();//StringBuffer對象是一個空的對象 StringBuffer s = new StringBuffer(「abc」);//建立帶有內容的StringBuffer對象,對象的內容就是字符串」abc」
StringBuffer和String屬於不一樣的類型,也不能直接進行強制類型轉換。StringBuffer對象和String對象之間的互轉的代碼以下:
String s = 「abc」; StringBuffer sb1 = new StringBuffer(「123」); StringBuffer sb2 = new StringBuffer(s); //String轉換爲StringBuffer String s1 = sb1.toString(); //StringBuffer轉換爲String
StringBuffer類中的方法主要偏重於對於字符串的變化,例如追加、插入和刪除等,經常使用方法有:append方法、insert方法、deleteCharAt方法、reverse方法等。
(1)若是要操做少許的數據用 String;
(2)(多線程下)常常須要對一個字符串進行修改,例如追加、插入和刪除等操做,使用StringBuffer要更加適合一些。
此類提供一個與StringBuffer兼容的 API,但不保證同步。該類被設計用做StringBuffer 的一個簡易替換,用在字符串緩衝區被單個線程使用的時候(這種狀況很廣泛)。
(1) StringBuffer:線程安全的;
(2) StringBuilder:線程非安全的。
因爲String對象不可變,重複新建對象;StringBuffer對象可變。
當咱們在字符串緩衝去被多個線程使用是,JVM不能保證StringBuilder的操做是安全的,雖然他的速度最快,可是能夠保證StringBuffer是能夠正確操做的。固然大多數狀況下就是咱們是在單線程下進行的操做,因此大多數狀況下是建議用StringBuilder而不用StringBuffer的。
//String效率是遠要比StringBuffer快的: String S1 = 「This is only a」 + 「 simple」 + 「 test」; StringBuffer Sb = new StringBuilder(「This is only a」).append(「simple」).append(「 test」);
//String速度是很是慢的: String S2 = 「This is only a」; String S3 = 「 simple」; String S4 = 「 test」; String S1 = S2 +S3 + S4;
(1)若是要操做少許的數據用 String;
(2)多線程操做字符串緩衝區下操做大量數據 StringBuffer;
(3)單線程操做字符串緩衝區下操做大量數據 StringBuilder。
-----------------------------------
最近看了Object類的源碼,對hashCode() 和equals()方法有了更深的認識。重寫equals()方法就必須重寫hashCode()方法的緣由,從源頭Object類講起就更好理解了。
先來看Object關於hashCode()和equals()的源碼:
光從代碼中咱們能夠知道,hashCode()方法是一個本地native方法,返回的是對象引用中存儲的對象的內存地址,而equals方法是利用==來比較的也是對象的內存地址。從上邊咱們能夠看出,hashCode方法和equals方法是一致的。還有最關鍵的一點,咱們來看Object類中關於hashCode()方法的註釋:
簡單的翻譯一下就是,hashCode方法通常的規定是:
再簡單的翻譯一下第二三點就是:hashCode()和equals()保持一致,若是equals方法返回true,那麼兩個對象的hasCode()返回值必須同樣。若是equals方法返回false,hashcode能夠不同,可是這樣不利於哈希表的性能,通常咱們也不要這樣作。重寫equals()方法就必須重寫hashCode()方法的緣由也就顯而易見了。
假設兩個對象,重寫了其equals方法,其相等條件是屬性相等,就返回true。若是不重寫hashcode方法,其返回的依然是兩個對象的內存地址值,必然不相等。這就出現了equals方法相等,可是hashcode不相等的狀況。這不符合hashcode的規則。下邊,會介紹在集合框架中,這種狀況會致使的嚴重問題。
保護方法,實現對象的淺複製,只有實現了Cloneable接口才能夠調用該方法,不然拋出CloneNotSupportedException異常。
final方法,得到運行時類型。
該方法用得比較多,通常子類都有覆蓋。
該方法用於釋放資源。由於沒法肯定該方法何時被調用,不多使用。
該方法是很是重要的一個方法。通常equals和==是不同的,可是在Object中二者是同樣的。子類通常都要重寫這個方法。
該方法用於哈希查找,重寫了equals方法通常都要重寫hashCode方法。這個方法在一些具備哈希功能的Collection中用到。
通常必須知足obj1.equals(obj2)==true。能夠推出obj1.hash-Code()==obj2.hashCode(),可是hashCode相等不必定就知足equals。不過爲了提升效率,應該儘可能使上面兩個條件接近等價。
wait方法就是使當前線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具備該對象的鎖。wait()方法一直等待,直到得到鎖或者被中斷。wait(longtimeout)設定一個超時間隔,若是在規定時間內沒有得到鎖就返回。
調用該方法後當前線程進入睡眠狀態,直到如下事件發生。
(1)其餘線程調用了該對象的notify方法。
(2)其餘線程調用了該對象的notifyAll方法。
(3)其餘線程調用了interrupt中斷該線程。
(4)時間間隔到了。
此時該線程就能夠被調度了,若是是被中斷的話就拋出一個InterruptedException異常。
該方法喚醒在該對象上等待的某個線程。
該方法喚醒在該對象上等待的全部線程。
---------------------------------------------------------------
Java中建立線程主要有三種方式:
1、繼承Thread類建立線程類
(1)定義Thread類的子類,並重寫該類的run方法,該run方法的方法體就表明了線程要完成的任務。所以把run()方法稱爲執行體。
(2)建立Thread子類的實例,即建立了線程對象。
(3)調用線程對象的start()方法來啓動該線程。
上述代碼中Thread.currentThread()方法返回當前正在執行的線程對象。GetName()方法返回調用該方法的線程的名字。
2、經過Runnable接口建立線程類
(1)定義runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體一樣是該線程的線程執行體。
(2)建立 Runnable實現類的實例,並依此實例做爲Thread的target來建立Thread對象,該Thread對象纔是真正的線程對象。
(3)調用線程對象的start()方法來啓動該線程。
示例代碼爲:
3、經過Callable和Future建立線程
(1)建立Callable接口的實現類,並實現call()方法,該call()方法將做爲線程執行體,而且有返回值。
(2)建立Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
(3)使用FutureTask對象做爲Thread對象的target建立並啓動新線程。
(4)調用FutureTask對象的get()方法來得到子線程執行結束後的返回值
實例代碼:
2、建立線程的三種方式的對比
採用實現Runnable、Callable接口的方式創見多線程時,優點是:
線程類只是實現了Runnable接口或Callable接口,還能夠繼承其餘類。
在這種方式下,多個線程能夠共享同一個target對象,因此很是適合多個相同線程來處理同一份資源的狀況,從而能夠將CPU、代碼和數據分開,造成清晰的模型,較好地體現了面向對象的思想。
劣勢是:
編程稍微複雜,若是要訪問當前線程,則必須使用Thread.currentThread()方法。
使用繼承Thread類的方式建立多線程時優點是:
編寫簡單,若是須要訪問當前線程,則無需使用Thread.currentThread()方法,直接使用this便可得到當前線程。
劣勢是:
線程類已經繼承了Thread類,因此不能再繼承其餘父類。