---恢復內容開始---java
1. 什麼是隊列、棧、鏈表?node
隊列:隊列即按照數據到達的順序進行排隊,每次新插入一個節點,將其插到隊尾;每次只有對頭才能出隊列。是一種「先進先出」(FIFO)的數據結構。算法
棧:棧是一種只能在一端進行插入和刪除操做的特殊線性表。它按照先進後出(FILO)的原則存儲數據,先進入的數據被壓入棧底,最後的數據在棧頂,須要讀數據的時候從棧頂開始彈出數據(最後一個數據被第一個讀出來),能夠理解爲一個儲物的地方,且只有一個出口,先放進去的東西最後才能拿出來。shell
鏈表:鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。鏈表由一系列結點(鏈表中每個元素稱爲結點)組成,結點能夠在運行時動態生成。每一個結點包括兩個部分:一個是存儲數據元素的數據域,另外一個是存儲下一個結點地址的指針域。 相比於線性表順序結構,操做複雜。因爲沒必要須按順序存儲,鏈表在插入的時候能夠達到O(1)的複雜度,比另外一種線性表順序錶快得多,可是查找一個節點或者訪問特定編號的節點則須要O(n)的時間,而線性表和順序表相應的時間複雜度分別是O(logn)和O(1)數組
2. 什麼是樹(平衡樹,排序樹,B樹,B+樹,R樹,紅黑樹)、堆(大根堆、小根堆)、圖(有向圖、無向圖、拓撲)瀏覽器
樹:平衡二叉搜索樹:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹。安全
紅黑樹(Red Black Tree):是一種自平衡二叉查找樹 ,紅黑樹和AVL樹相似,都是在進行插入和刪除操做時經過特定操做保持二叉查找樹的平衡,從而得到較高的查找性能。服務器
(1) 每一個節點或者是黑色,或者是紅色。
(2) 根節點是黑色。
(3) 每一個葉子節點是黑色。 [注意:這裏葉子節點,是指爲空的葉子節點!]
(4) 若是一個節點是紅色的,則它的子節點必須是黑色的。
(5) 從一個節點到該節點的子孫節點的全部路徑上包含相同數目的黑節點。數據結構
B樹(多叉排序樹):併發
一、根結點至少有兩個子女;
二、每一個非根節點所包含的關鍵字個數 j 知足:m/2 - 1 <= j <= m - 1;
三、除根結點之外的全部結點(不包括葉子結點)的度數正好是關鍵字總數加1,故內部子樹個數 k 知足:m/2<= k <= m ;
四、全部的葉子結點都位於同一層。
R樹:
小根堆:最小堆,是一種通過排序的徹底二叉樹,其中任一非終端節點的數據值均不大於其左子節點和右子節點的值。
圖:有向圖:一個有向圖D是指一個有序三元組(V(D),A(D),ψD),其中ψD)爲關聯函數,它使A(D)中的每個元素(稱爲有向邊或弧)對應於V(D)中的一個有序元素(稱爲頂點或點)對
無向圖:邊沒有方向的圖稱爲無向圖。
無向圖G=<V,E>,其中:
1.V是非空集合,稱爲頂點集。
2.E是V中元素構成的無序二元組的集合,稱爲邊集
3. 棧和隊列的相同和不一樣之處 ?
共同點:都是隻容許在端點處插入和刪除元素的數據結構;
不一樣點:棧是僅在棧頂進行訪問,遵循後進先出的原則(LIFO);隊列是在隊尾插入數據,在隊頭刪除數據(FIFO)
4. 棧一般採用的兩種存儲結構 ?
連接存儲:鏈棧 帶有頭指針或頭結點的單循環鏈表。
順序存儲:數組實現
5. 排序
都有哪幾種方法?
(1) 冒泡排序:
public static void bubbleSort(Comparable[] a) { int j, flag;Comparable temp; for (int i = 0; i < a.length; i++) { flag = 0; for (j = 1; j < a.length - i; j++) { if (a[j].compareTo(a[j - 1]) < 0) { temp = a[j]; a[j] = a[j - 1]; a[j - 1] = temp; flag = 1; } } // 若是沒有交換,表明已經排序完畢,直接返回 if (flag == 0) { return; } } }
(2) 插入排序
public static void insertionSort(Comparable[] a) { int length = a.length; Comparable temp; for (int i = 1; i < length; i++) { for (int j = i; j > 0 && a[j].compareTo(a[j - 1]) < 0; j--) { temp = a[j]; a[j] = a[j - 1]; a[j - 1] = temp; } } }// 對實現Comparable的類型進行排序,先將大的元素都向右移動,減小一半交換次數 public static void insertionSort(Comparable[] a) { int length = a.length; Comparable temp; int j; for (int i = 1; i < length; i++) { temp = a[i]; for (j = i; j > 0 && temp.compareTo(a[j - 1]) < 0; j--) { a[j] = a[j - 1]; } a[j] = temp; } }// 二分插入排序,使用二分查找找到插入點,而後進行移位 public static void insertionSort(Comparable[] a) { int length = a.length; Comparable temp; int j; for (int i = 1; i < length; i++) { if (a[i].compareTo(a[i - 1]) < 0) { temp = a[i]; int index = binarySearch(a, a[i], 0, i - 1); for (j = i - 1; j >= index; j--) { a[j + 1] = a[j]; } a[index] = temp; } } } private static int binarySearch(Comparable[] a, Comparable target, int start, int end) { int mid; while (start <= end) { mid = (start + end) >> 1; if (target.compareTo(a[mid]) < 0) { end = mid - 1; } else { start = mid + 1; } } return start; }
(3) 選擇排序
public static void selectionSort1(Comparable[] a) { int length = a.length; int min; Comparable temp; for (int i = 0; i < length; i++) { min = i; for (int j = i + 1; j < length; j++) { if (a[j].compareTo(a[min]) < 0) { min = j; } } temp = a[min]; a[min] = a[i]; a[i] = temp; } }
(4) 希爾排序
public static void shellSort(Comparable[] a) { int length = a.length; int h = 1; Comparable temp; while (h < length / 3) { h = 3 * h + 1; } while (h >= 1) { for (int i = h; i < length; i++) { for (int j = i; j >= h && a[j].compareTo(a[j - h]) < 0; j -= h) { temp = a[j]; a[j] = a[j - h]; a[j - h] = temp; } } h /= 3; } }
(5) 堆排序
public static void heapSort(Comparable[] a) { int length = a.length; Comparable temp; for (int k = length / 2; k >= 1; k--) { sink(a, k, length); } while (length > 0) { temp = a[0]; a[0] = a[length - 1]; a[length - 1] = temp; length--; sink(a, 1, length); } } private static void sink(Comparable[] a, int k, int n) { Comparable temp; while (2 * k <= n) { int j = 2 * k; if (j < n && a[j - 1].compareTo(a[j]) < 0) { j++; } if (a[k - 1].compareTo(a[j - 1]) >= 0) { break; } temp = a[k - 1]; a[k - 1] = a[j - 1]; a[j - 1] = temp; k = j; } }
(6) 歸併排序
private static Comparable[] aux;// 自頂向下 public static void mergeSort(Comparable[] a) { aux = new Comparable[a.length]; mergeSort(a, 0, a.length - 1); } public static void mergeSort(Comparable[] a, int lo, int hi) { if (hi <= lo) { return; } int mid = (lo + hi) >>> 1; mergeSort(a, lo, mid); mergeSort(a, mid + 1, hi); merge(a, lo, mid, hi); } public static void merge(Comparable[] a, int lo, int mid, int hi) { int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j++]; } else if (j > hi) { a[k] = aux[i++]; } else if (aux[j].compareTo(aux[i]) < 0) { a[k] = aux[j++]; } else { a[k] = aux[i++]; } } }
自底向上的歸併排序
private static Comparable[] aux;// 自底向上 public static void mergeSort(Comparable[] a) { int length = a.length; aux = new Comparable[length]; for (int sz = 1; sz < length; sz = sz + sz) { for (int lo = 0; lo < length - sz; lo += sz + sz) { merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, length - 1)); } } } public static void merge(Comparable[] a, int lo, int mid, int hi) { int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j++]; } else if (j > hi) { a[k] = aux[i++]; } else if (aux[j].compareTo(aux[i]) < 0) { a[k] = aux[j++]; } else { a[k] = aux[i++]; } } }
(7) 快速排序
public static void quickSort(Comparable[] a) { quickSort(a, 0, a.length - 1); } public static void quickSort(Comparable[] a, int lo, int hi) { if (hi <= lo) { return; } int j = partition(a, lo, hi); quickSort(a, lo, j - 1); quickSort(a, j + 1, hi); } public static int partition(Comparable[] a, int lo, int hi) { int i = lo, j = hi + 1; Comparable temp; Comparable v = a[lo]; while (true) { while (a[++i].compareTo(v) < 0) { if (i == hi) { break; } } while (v.compareTo(a[--j]) < 0) { if (j == lo) { break; } } if (i >= j) { break; } temp = a[i]; a[i] = a[j]; a[j] = temp; } temp = a[lo]; a[lo] = a[j]; a[j] = temp; return j; }
6. 常見Hash算法,哈希的原理和代價?
散列表,它是基於快速存取的角度設計的,也是一種典型的「空間換時間」的作法。顧名思義,該數據結構能夠理解爲一個線性表,可是其中的元素不是緊密排列的,而是可能存在空隙。
散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它經過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫作散列函數,存放記錄的數組叫作散列表。
好比咱們存儲70個元素,但咱們可能爲這70個元素申請了100個元素的空間。70/100=0.7,這個數字稱爲負載因子。咱們之因此這樣作,也是爲了「快速存取」的目的。咱們基於一種結果儘量隨機平均分佈的固定函數H爲每一個元素安排存儲位置,這樣就能夠避免遍歷性質的線性搜索,以達到快速存取。可是因爲此隨機性,也必然致使一個問題就是衝突。所謂衝突,即兩個元素經過散列函數H獲得的地址相同,那麼這兩個元素稱爲「同義詞」。這相似於70我的去一個有100個椅子的飯店吃飯。散列函數的計算結果是一個存儲單位地址,每一個存儲單位稱爲「桶」。設一個散列表有m個桶,則散列函數的值域應爲[0,m-1]。
解決衝突是一個複雜問題。衝突主要取決於:
(1)散列函數,一個好的散列函數的值應儘量平均分佈。
(2)處理衝突方法。
(3)負載因子的大小。太大不必定就好,並且浪費空間嚴重,負載因子和散列函數是聯動的。
解決衝突的辦法:
(1)線性探查法:衝突後,線性向前試探,找到最近的一個空位置。缺點是會出現堆積現象。存取時,可能不是同義詞的詞也位於探查序列,影響效率。
(2)雙散列函數法:在位置d衝突後,再次使用另外一個散列函數產生一個與散列表桶容量m互質的數c,依次試探(d+n*c)%m,使探查序列跳躍式分佈。
經常使用的構造散列函數的方法:
散列函數能使對一個數據序列的訪問過程更加迅速有效,經過散列函數,數據元素將被更快地定位:
1. 直接尋址法:取關鍵字或關鍵字的某個線性函數值爲散列地址。即H(key)=key或H(key) = a•key + b,其中a和b爲常數(這種散列函數叫作自身函數)
2. 數字分析法:分析一組數據,好比一組員工的出生年月日,這時咱們發現出生年月日的前幾位數字大致相同,這樣的話,出現衝突的概率就會很大,可是咱們發現年月日的後幾位表示月份和具體日期的數字差異很大,若是用後面的數字來構成散列地址,則衝突的概率會明顯下降。所以數字分析法就是找出數字的規律,儘量利用這些數據來構造衝突概率較低的散列地址。
3. 平方取中法:取關鍵字平方後的中間幾位做爲散列地址。
4. 摺疊法:將關鍵字分割成位數相同的幾部分,最後一部分位數能夠不一樣,而後取這幾部分的疊加和(去除進位)做爲散列地址。
5. 隨機數法:選擇一隨機函數,取關鍵字的隨機值做爲散列地址,一般用於關鍵字長度不一樣的場合。
6. 除留餘數法:取關鍵字被某個不大於散列表表長m的數p除後所得的餘數爲散列地址。即 H(key) = key MOD p, p<=m。不只能夠對關鍵字直接取模,也可在摺疊、平方取中等運算以後取模。對p的選擇很重要,通常取素數或m,若p選的很差,容易產生同義詞。
7. 一致性Hash算法?
一致性哈希算法(Consistent Hashing Algorithm)是一種分佈式算法,經常使用於負載均衡。Memcached client也選擇這種算法,解決將key-value均勻分配到衆多Memcached server上的問題。它能夠取代傳統的取模操做,解決了取模操做沒法應對增刪Memcached Server的問題(增刪server會致使同一個key,在get操做時分配不到數據真正存儲的server,命中率會急劇降低)。
簡單來講,一致性哈希將整個哈希值空間組織成一個虛擬的圓環,如假設某哈希函數H的值空間爲0 - (2^32)-1(即哈希值是一個32位無符號整形),整個哈希空間環以下:
(1) 首先求出memcached服務器(節點)的哈希值,並將其配置到0~232的圓(continuum)上。
(2) 而後採用一樣的方法求出存儲數據的鍵的哈希值,並映射到相同的圓上。
(3) 而後從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。若是超過232仍然找不到服務器,就會保存到第一臺memcached服務器上。
容錯性與可擴展性分析:在一致性哈希算法中,若是一臺服務器不可用,則受影響的數據僅僅是此服務器到其環空間中前一臺服務器(即順着逆時針方向行走遇到的第一臺服務器)之間數據,其它不會受到影響,在一致性哈希算法中,若是增長一臺服務器,則受影響的數據僅僅是新服務器到其環空間中前一臺服務器(即順着逆時針方向行走遇到的第一臺服務器)之間數據,其它不會受到影響。 一致性哈希算法在服務節點太少時,容易由於節點分部不均勻而形成數據傾斜問題。
代碼實現:
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collection; import java.util.SortedMap; import java.util.TreeMap; /** * 一致性Hash算法 * * @param <T> 節點類型 */ public class ConsistentHash<T> { /** * Hash計算對象,用於自定義hash算法 */ HashFunc hashFunc; /** * 複製的節點個數 */ private final int numberOfReplicas; /** * 一致性Hash環 */ private final SortedMap<Long, T> circle = new TreeMap<>(); /** * 構造,使用Java默認的Hash算法 * @param numberOfReplicas 複製的節點個數,增長每一個節點的複製節點有利於負載均衡 * @param nodes 節點對象 */ public ConsistentHash(int numberOfReplicas, Collection<T> nodes) { this.numberOfReplicas = numberOfReplicas; this.hashFunc = new HashFunc() { @Override public Long hash(Object key) { // return fnv1HashingAlg(key.toString()); return md5HashingAlg(key.toString()); } }; //初始化節點 for (T node : nodes) { add(node); } } /** * 構造 * @param hashFunc hash算法對象 * @param numberOfReplicas 複製的節點個數,增長每一個節點的複製節點有利於負載均衡 * @param nodes 節點對象 */ public ConsistentHash(HashFunc hashFunc, int numberOfReplicas, Collection<T> nodes) { this.numberOfReplicas = numberOfReplicas; this.hashFunc = hashFunc; //初始化節點 for (T node : nodes) { add(node); } } /** * 增長節點<br> * 每增長一個節點,就會在閉環上增長給定複製節點數<br> * 例如複製節點數是2,則每調用此方法一次,增長兩個虛擬節點,這兩個節點指向同一Node * 因爲hash算法會調用node的toString方法,故按照toString去重 * * @param node 節點對象 */ public void add(T node) { for (int i = 0; i < numberOfReplicas; i++) { circle.put(hashFunc.hash(node.toString() + i), node); } } /** * 移除節點的同時移除相應的虛擬節點 * * @param node 節點對象 */ public void remove(T node) { for (int i = 0; i < numberOfReplicas; i++) { circle.remove(hashFunc.hash(node.toString() + i)); } } /** * 得到一個最近的順時針節點 * * @param key 爲給定鍵取Hash,取得順時針方向上最近的一個虛擬節點對應的實際節點 * @return 節點對象 */ public T get(Object key) { if (circle.isEmpty()) { return null; } long hash = hashFunc.hash(key); if (!circle.containsKey(hash)) { SortedMap<Long, T> tailMap = circle.tailMap(hash); //返回此映射的部分視圖,其鍵大於等於 hash hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); } //正好命中 return circle.get(hash); } /** * 使用MD5算法 * @param key * @return */ private static long md5HashingAlg(String key) { MessageDigest md5 = null; try { md5 = MessageDigest.getInstance("MD5"); md5.reset(); md5.update(key.getBytes()); byte[] bKey = md5.digest(); long res = ((long) (bKey[3] & 0xFF) << 24) | ((long) (bKey[2] & 0xFF) << 16) | ((long) (bKey[1] & 0xFF) << 8)| (long) (bKey[0] & 0xFF); return res; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return 0l; } /** * 使用FNV1hash算法 * @param key * @return */ private static long fnv1HashingAlg(String key) { final int p = 16777619; int hash = (int) 2166136261L; for (int i = 0; i < key.length(); i++) hash = (hash ^ key.charAt(i)) * p; hash += hash << 13; hash ^= hash >> 7; hash += hash << 3; hash ^= hash >> 17; hash += hash << 5; return hash; } /** * Hash算法對象,用於自定義hash算法 */ public interface HashFunc { public Long hash(Object key); } }
8. 死鎖的四個必要條件,避免方法?
4個必要條件:
(1)互斥條件:進程對所分配到的資源不容許其餘進程進行訪問,若其餘進程訪問該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源
(2)請求和保持條件:進程得到必定的資源以後,又對其餘資源發出請求,可是該資源可能被其餘進程佔有,此事請求阻塞,但又對本身得到的資源保持不放
(3)不可剝奪條件:是指進程已得到的資源,在未完成使用以前,不可被剝奪,只能在使用完後本身釋放
(4)環路等待條件:是指進程發生死鎖後,必然存在一個進程--資源之間的環形鏈
處理方法:
(1).預防死鎖:經過設置一些限制條件,去破壞產生死鎖的必要條件
(2).避免死鎖:在資源分配過程當中,使用某種方法避免系統進入不安全的狀態,從而避免發生死鎖
(3).檢測死鎖:容許死鎖的發生,可是經過系統的檢測以後,採起一些措施,將死鎖清除掉
(4).解除死鎖:該方法與檢測死鎖配合使用
9. HTTP常見響應碼:200、30一、30二、40四、500?
200 (成功) 服務器已成功處理了請求。 一般,這表示服務器提供了請求的網頁。
301 (永久移動) 請求的網頁已永久移動到新位置。 服務器返回此響應(對 GET 或 HEAD 請求的響應)時,會自動將請求者轉到新位置。
302 (臨時移動) 服務器目前從不一樣位置的網頁響應請求,但請求者應繼續使用原有位置來進行之後的請求。
404 (未找到) 服務器找不到請求的網頁。
500 (服務器內部錯誤) 服務器遇到錯誤,沒法完成請求。
10. get和post的區別?
get參數經過url傳遞,post放在request body中。
get請求在url中傳遞的參數是有長度限制的,而post沒有。
get比post更不安全,由於參數直接暴露在url中,因此不能用來傳遞敏感信息。
get請求只能進行url編碼,而post支持多種編碼方式
get請求會瀏覽器主動cache,而post支持多種編碼方式。
get請求參數會被完整保留在瀏覽歷史記錄裏,而post中的參數不會被保留。
GET和POST本質上就是TCP連接,並沒有差異。可是因爲HTTP的規定和瀏覽器/服務器的限制,致使他們在應用過程當中體現出一些不一樣。
GET產生一個TCP數據包;POST產生兩個TCP數據包。
長的說:
對於GET方式的請求,瀏覽器會把http header和data一併發送出去,服務器響應200(返回數據);
而對於POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 ok(返回數據)。
11. 內鏈接、左鏈接、右鏈接的做用及區別?
左鏈接:左邊有的,右邊沒有的爲null
右鏈接:左邊沒有的,右邊有的爲null
內鏈接:顯示左邊右邊共有的
12. 面向對象的三大基本特徵,五大基本原則 ?
三大特性是:封裝,繼承,多態
五大基本原則 :單一職責原則SRP、開放封閉原則OCP、替換原則LSP、依賴原則DIP、接口分離原則ISP
13. Collection
和 Collections
的區別?
一、java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操做的通用接口方法。Collection接口在Java 類庫中有不少具體的實現。Collection接口的意義是爲各類具體的集合提供了最大化的統一操做方式,其直接繼承接口有List與Set Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set 二、Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜索以及線程安全等各類操做。