阿里P8必備Java 知識點:算法、設計模式、語法,看看你缺了哪些!

排序算法 9

P1:排序算法的分類

排序算法能夠分爲內部排序和外部排序,在內存中進行的排序稱爲內部排序,當要排序的數據量很大時沒法所有拷貝到內存,須要使用外存進行排序,這種排序稱爲外部排序。java

內部排序包括比較排序和非比較排序,比較排序包括插入排序、選擇排序、交換排序和歸併排序,非比較排序包括計數排序、基數排序和桶排序。其中插入排序又包括直接插入排序和希爾排序,選擇排序包括直接選擇排序和堆排序,交換排序包括冒泡排序和快速排序。程序員


P2:直接插入排序

直接插入排序屬於插入排序,是一種穩定的排序,平均時間複雜度和最差時間複雜度均爲 O(n²),當元素基本有序時的最好時間複雜度爲O(n),空間複雜度爲 O(1)。算法

基本原理是每一趟將一個待排序的記錄,按其關鍵字的大小插入到已經排好序的一組記錄的適當位置上,直到全部待排序記錄所有插入爲止。適用於待排序記錄較少或基本有序的狀況。shell

`public` `void` `insertionSort(``int``[] nums) {`

`for` `(``int` `i =` `1``; i < nums.length; i++) {`

`int` `insertNum = nums[i];`

`int` `insertIndex;`

`for` `(insertIndex = i -` `1``; insertIndex >=` `0` `&& nums[insertIndex] > insertNum; insertIndex--) {`

`nums[insertIndex +` `1``] = nums[insertIndex];`

`}`

`nums[insertIndex +` `1``] = insertNum;`

`}`

`}`

複製代碼

**優化:**直接插入並無利用到要插入的序列已有序的特色,插入第 i 個元素時能夠經過二分查找找到要插入的位置,再把第 i 個元素前 1位與插入位置之間的全部元素後移,把第 i 個元素放在目標位置上。編程

`public` `void` `binaryInsertionSort(``int``[] nums) {`

`for` `(``int` `index =` `1``; index < nums.length; index++) {`

`int` `insertNum = nums[index];`

`int` `insertIndex = -``1``;`

`int` `start =` `0``;`

`int` `end = index -` `1``;`

`while` `(start <= end) {`

`int` `mid = start + (end - start) /` `2``;`

`if` `(insertNum > nums[mid])`

`start = mid +` `1``;`

`else` `if` `(insertNum < nums[mid])`

`end = mid -` `1``;`

`else` `{`

`insertIndex = mid +` `1``;`

`break``;`

`}`

`}`

`if` `(insertIndex == -``1``)`

`insertIndex = start;`

`if` `(index - insertIndex >=` `0``)`

`System.arraycopy(nums, insertIndex, nums, insertIndex +` `1``, index - insertIndex);`

`nums[insertIndex] = insertNum;`

`}`

`}`

複製代碼

P3:希爾排序

希爾排序屬於插入排序,又稱縮小增量排序,是對直接插入排序的一種改進,而且是一種不穩定的排序,平均時間複雜度爲O(n^1.3^),最差時間複雜度爲 O(n²),最好時間複雜度爲 O(n),空間複雜度爲 O(1)。json

基本原理是把記錄按下標的必定增量分組,對每組進行直接插入排序,每次排序後減少增量,當增量減至 1 時,排序完畢。適用於中等規模的數據量,對規模很是大的數據量不是最佳選擇。設計模式

`public` `void` `shellSort(``int``[] nums) {`

`for` `(``int` `d = nums.length /` `2``; d >` `0` `; d /=` `2``) {`

`for` `(``int` `i = d; i < nums.length; i++) {`

`int` `insertNum = nums[i];`

`int` `insertIndex;`

`for` `(insertIndex = i - d; insertIndex >=` `0` `&& nums[insertIndex] > insertNum; insertIndex -= d) {`

`nums[insertIndex + d] = nums[insertIndex];`

`}`

`nums[insertIndex + d] = insertNum;`

`}`

`}`

`}`

複製代碼

P4:直接選擇排序

直接選擇排序屬於選擇排序,是一種不穩定的排序,任何狀況下時間複雜度都是 O(n²),空間複雜度爲 O(1)。基本原理是每次在未排序序列中找到最小元素,和未排序序列的第一個元素交換位置,再在剩餘的未排序序列重複該操做直到全部元素排序完畢。適用於數據量較小的狀況,比直接插入排序稍快。數組

`public` `void` `selectSort(``int``[] nums) {`

`int` `minIndex;`

`for` `(``int` `index =` `0``; index < nums.length -` `1``; index++){`

`minIndex = index;`

`for` `(``int` `i = index +` `1``;i < nums.length; i++){`

`if``(nums[i] < nums[minIndex])`

`minIndex = i;`

`}`

`if` `(index != minIndex){`

`swap(nums, index, minIndex);`

`}`

`}`

`}`

複製代碼

P5:堆排序

堆排序屬於選擇排序,是對直接選擇排序的改進,而且是一種不穩定的排序,任何狀況時間複雜度都爲 O(nlogn),空間複雜度爲 O(1)。安全

基本原理是將待排序記錄看做徹底二叉樹,能夠創建大根堆或小根堆,大根堆中每一個節點的值都不小於它的子節點值,小根堆中每一個節點的值都不大於它的子節點值。適用於數據量較大的狀況。bash

以大根堆爲例,在建堆時首先將最後一個節點做爲當前節點,若是當前結點存在父節點且值大於父節點,就將當前節點和父節點交換。在移除時首先暫存根節點的值,而後用最後一個節點代替根節點並做爲當前節點,若是當前節點存在子節點且值小於子節點,就將其與值較大的子節點進行交換,調整完堆後返回暫存的值。

`public` `void` `add(``int``[] nums,` `int` `i,` `int` `num){`

`nums[i] = num;`

`int` `curIndex = i;`

`while` `(curIndex >` `0``) {`

`int` `parentIndex = (curIndex -` `1``) /` `2``;`

`if` `(nums[parentIndex] < nums[curIndex])`

`swap(nums, parentIndex, curIndex);`

`else` `break``;`

`curIndex =parentIndex;`

`}`

`}`

`public` `int` `remove(``int``[] nums,` `int` `size){`

`int` `result = nums[``0``];`

`nums[``0``] = nums[size -` `1``];`

`int` `curIndex =` `0``;`

`while` `(``true``) {`

`int` `leftIndex = curIndex *` `2` `+` `1``;`

`int` `rightIndex = curIndex *` `2` `+` `2``;`

`if` `(leftIndex >= size)` `break``;`

`int` `maxIndex = leftIndex;`

`if` `(rightIndex < size && nums[maxIndex] < nums[rightIndex])`

`maxIndex = rightIndex;`

`if` `(nums[curIndex] < nums[maxIndex])`

`swap(nums, curIndex, maxIndex);`

`else` `break``;`

`curIndex = maxIndex;`

`}`

`return` `result;`

`}`

複製代碼

P6:冒泡排序

冒泡排序屬於交換排序,是一種穩定的排序,平均時間複雜度和最壞時間複雜度均爲 O(n²),當元素基本有序時的最好時間複雜度爲O(n),空間複雜度爲 O(1)。

基本原理是比較相鄰的元素,若是第一個比第二個大就進行交換,對每一對相鄰元素作一樣的工做,從開始第一對到結尾的最後一對,每一輪排序後末尾元素都是有序的,針對 n 個元素重複以上步驟 n -1 次排序完畢。

`public` `void` `bubbleSort(``int``[] nums) {`

`for` `(``int` `i =` `0``; i < nums.length -` `1``; i++) {`

`for` `(``int` `index =` `0``; index < nums.length -` `1` `- i; index++) {`

`if` `(nums[index] > nums[index +` `1``])`

`swap(nums, index, index +` `1``)`

`}`

`}`

`}`

複製代碼

**優化:**當序列已經有序時仍會進行沒必要要的比較,能夠設置一個標誌位記錄是否有元素交換,若是沒有直接結束比較。

`public` `void` `betterBubbleSort(``int``[] nums) {`

`boolean` `swap;`

`for` `(``int` `i =` `0``; i < nums.length -` `1``; i++) {`

`swap =` `true``;`

`for` `(``int` `index =` `0``; index < nums.length -` `1` `- i; index++) {`

`if` `(nums[index] > nums[index +` `1``]) {`

`swap(nums, index ,index +` `1``);`

`swap =` `false``;`

`}`

`}`

`if` `(swap)` `break``;`

`}`

`}`

複製代碼

P7:快速排序

快速排序屬於交換排序,是對冒泡排序的一種改進,而且是一種不穩定的排序,平均時間複雜度和最好時間複雜度均爲 O(nlogn),當元素基本有序時的最壞時間複雜度爲O(n²),空間複雜度爲 O(logn)。

基本原理是首先選擇一個基準元素,而後經過一趟排序將要排序的數據分割成獨立的兩部分,一部分所有小於等於基準元素,一部分所有大於等於基準元素,而後再按此方法遞歸對這兩部分數據分別進行快速排序。適用於數據量較大且元素基本無序的狀況。

快速排序的一次劃分從兩頭交替搜索,直到 low 和 high 指針重合,所以時間複雜度是 O(n),而整個算法的時間複雜度與劃分趟數有關。最好狀況是每次劃分選擇的中間數剛好將當前序列幾乎等分,通過 logn 趟劃分即可獲得長度爲 1 的子表,這樣算法的時間複雜度爲O(nlogn)。最壞的狀況是每次所選中間數是當前序列中的最大或最小元素,這使每次劃分所得的子表其中一個爲空表,另外一個子表的長度爲原表的長度 - 1。這樣長度爲 n 的數據表的須要通過 n 趟劃分,整個排序算法的時間複雜度爲O(n²)。

從空間上看盡管快速排序只須要一個元素的輔助空間,但快速排序須要一個棧空間來實現遞歸。最好的狀況下,即快速排序的每一趟排序都將元素序列均勻地分割成長度相近的兩個子表,所需棧的最大深度爲 log(n+1),最壞狀況下棧的最大深度爲 n。

`public` `void` `quickSort(``int``[] nums,` `int` `start,` `int` `end) {`

`if` `(start < end) {`

`int` `pivotIndex = getPivotIndex(nums, start, end);`

`quickSort(nums, start, pivotIndex -` `1``);`

`quickSort(nums, pivotIndex +` `1``, end);`

`}`

`}`

`public` `int` `getPivotIndex(``int``[] nums,` `int` `start,` `int` `end) {`

`int` `pivot = nums[start];`

`int` `low = start;`

`int` `high = end;`

`while` `(low < high) {`

`while` `(low <= high && nums[low] <= pivot)`

`low++;`

`while` `(low <= high && nums[high] > pivot)`

`high--;`

`if` `(low < high)`

`swap(nums, low, high);`

`}`

`swap(nums, start, high);`

`return` `high;`

`}`

複製代碼

**優化:**當規模足夠小時,例如 end - start < 10 時,採用直接插入排序。


P8:歸併排序

歸併排序是基於歸併操做的排序算法,是一種穩定的排序算法,任何狀況時間複雜度都爲 O(nlogn),空間複雜度爲 O(n)。

基本原理是應用分治法將待排序序列分紅兩部分,而後對兩部分分別遞歸排序,最後進行合併,使用一個輔助空間並設定兩個指針分別指向兩個有序序列的起始元素,將指針對應的較小元素添加到輔助空間,重複該步驟到某一序列到達末尾,而後將另外一序列剩餘元素合併到輔助空間末尾。適用於數據量大且對穩定性有要求的狀況。

`int``[] help;`

`public` `void` `mergeSort(``int``[] arr) {`

`int``[] help =` `new` `int``[arr.length];`

`sort(arr,` `0``, arr.length -` `1``);`

`}`

`public` `void` `sort(``int``[] arr,` `int` `start,` `int` `end) {`

`if` `(start == end)` `return``;`

`int` `mid = start + (end - start) /` `2``;`

`sort(arr, start, mid);`

`sort(arr, mid +` `1``, end);`

`merge(arr, start, mid, end);`

`}`

`public` `void` `merge(``int``[] arr,` `int` `start,` `int` `mid,` `int` `end) {`

`if` `(end +` `1` `- start >=` `0``) System.arraycopy(arr, start, help, start, end +` `1` `- start);`

`int` `p = start;`

`int` `q = mid +` `1``;`

`int` `index = start;`

`while` `(p <= mid && q <= end) {`

`if` `(help[p] < help[q])`

`arr[index++] = help[p++];`

`else`

`arr[index++] = help[q++];`

`}`

`while` `(p <= mid) arr[index++] = help[p++];`

`while` `(q <= end) arr[index++] = help[q++];`

`}`

複製代碼

P9:排序算法的選擇原則

當數據量規模較小時,能夠考慮直接插入排序或直接選擇排序,當元素分佈有序時直接插入排序將大大減小比較次數和移動記錄的次數,若是不要求穩定性,可使用直接選擇排序,效率略高於直接插入排序。

當數據量規模中等時,能夠選擇希爾排序。

當數據量規模較大時,能夠考慮堆排序、快速排序和歸併排序。若是對穩定性有要求能夠採用歸併排序,若是元素分佈隨機能夠採用快速排序,若是元素分佈接近正序或逆序能夠採用堆排序。

通常不使用冒泡排序。


設計模式 7

P1:設計模式的原則

**開閉原則:**面向對象設計中最基礎的設計原則,指一個軟件實體(類、模塊、方法等)應該對擴展開放,對修改關閉。它強調用抽象構建框架,用實現擴展細節,提升代碼的可複用性和可維護性。例如在版本更新時儘可能不修改源代碼,但能夠增長新功能。

**單一職責原則:**一個類、接口或方法只負責一個職責,能夠提升代碼可讀性和可維護性,下降代碼複雜度以及變動引發的風險。

**依賴倒置原則:**程序應該依賴於抽象類或接口,而不是具體的實現類。能夠下降代碼的耦合度,提升系統的穩定性。

**接口隔離原則:**將不一樣功能定義在不一樣接口中實現接口隔離,避免了類依賴它不須要的接口,減小了接口之間依賴的冗餘性和複雜性。

**里氏替換原則:**對開閉原則的補充,規定了任何父類能夠出現的地方子類都必定能夠出現,能夠約束繼承氾濫,加鍵程序健壯性。

**迪米特原則:**也叫最少知道原則,每一個模塊對其餘模塊都要儘量少的瞭解和依賴,能夠下降代碼耦合度。

**合成/聚合原則:**儘可能使用組合(has a)或聚合(contains a)而不是繼承關係達到軟件複用的目的,可使系統更加靈活,下降耦合度。


P2:設計模式的分類

**建立型模式:**提供了一種在建立對象的同時隱藏建立邏輯的方式,而不是使用 new 運算符直接實例化對象,這使得程序在判斷針對某個給定實例須要建立哪些對象時更加靈活。包括:工廠模式、抽象工廠模式、單例模式、建造者模式、原型模式。

**結構型模式:**經過類和接口之間的繼承和引用實現建立複雜結構對象的功能。包括:適配器模式、橋接模式、過濾器模式、組合模式、裝飾器模式、外觀模式、享元模式、代理模式。

**行爲型模式:**經過類之間不一樣的通訊方式實現不一樣的行爲方式。包括:責任鏈模式、命名模式、解釋器模式、迭代器模式、中介者模式、備忘錄模式、觀察者模式、狀態模式、策略模式、模板模式、訪問者模式。


P3:工廠模式

工廠模式屬於建立型模式,分爲簡單工廠模式,工廠方法模式和抽象工廠模式。

簡單工廠模式指由一個工廠對象來建立實例,客戶端不須要關注建立的邏輯,只須要提供傳入工廠對象的參數。

工廠方法模式指定義一個建立對象的接口,讓接口的實現類來決定建立哪種對象,工廠方法模式讓類的實例化推遲到子類中進行。工廠方法模式中客戶端只需關心對應的工廠而無需關心建立細節,主要解決了產品擴展的問題,在簡單工廠模式中若是產品種類變多,工廠的職責會愈來愈多,不便於維護。

抽象工廠模式指提供一個建立一系列相關或相互依賴對象的接口,無需指定它們的具體類。客戶端不依賴於產品類實例如何被建立和實現的細節,主要用於系統的產品有多於一個的產品族,而系統只消費其中某一個產品族產品的狀況。

總結:

**簡單工廠:**一個工廠,一種抽象產品。例如一個麥當勞店,能夠生產多種漢堡。

`public` `class` `MacDonaldFactory {`

`public` `Hamburger eatHamburger(String name) {`

`if` `(``"beef"``.equals(name))`

`return` `new` `BeefHamburger();`

`else` `if` `(``"pig"``.equals(name))`

`return` `new` `PigHamburger();`

`return` `null``;`

`}`

`}`

`interface` `Hamburger {`

`void` `eat();`

`}`

`class` `BeefHamburger` `implements` `Hamburger {`

`@Override`

`public` `void` `eat() {`

`System.out.println(``"吃牛肉漢堡"``);`

`}`

`}`

`class` `PigHamburger` `implements` `Hamburger {`

`@Override`

`public` `void` `eat() {`

`System.out.println(``"吃豬肉漢堡"``);`

`}`

`}`

複製代碼

**工廠方法:**多個工廠,一種抽象產品。例如一個麥當勞店,能夠生產多種漢堡,一個肯德基店,也能夠生產多種漢堡。

`public` `interface` `HamburgerFactory {`

`Hamburger build();`

`}`

`class` `MCFactory` `implements` `HamburgerFactory {`

`@Override`

`public` `Hamburger build() {`

`return` `new` `MCHamburger();`

`}`

`}`

`class` `KFCFactory` `implements` `HamburgerFactory {`

`@Override`

`public` `Hamburger build() {`

`return` `new` `KFCHamburger();`

`}`

`}`

`interface` `Hamburger {`

`void` `eat();`

`}`

`class` `MCHamburger` `implements` `Hamburger {`

`@Override`

`public` `void` `eat() {`

`System.out.println(``"吃麥當勞漢堡"``);`

`}`

`}`

`class` `KFCHamburger` `implements` `Hamburger {`

`@Override`

`public` `void` `eat() {`

`System.out.println(``"吃肯德基漢堡"``);`

`}`

`}`

複製代碼

**抽象工廠:**多個工廠,多種抽象產品。例如一個麥當勞店和一個肯德基店均可以生產多種漢堡和可樂。

`public` `interface` `FoodFactory {`

`Hamburger buildHamburger();`

`Drink buildDrink();`

`}`

`class` `MCFactory` `implements` `FoodFactory {`

`@Override`

`public` `Hamburger buildHamburger() {`

`return` `new` `MCHamburger();`

`}`

`@Override`

`public` `Drink buildDrink() {`

`return` `new` `MCDrink();`

`}`

`}`

`class` `KFCFactory` `implements` `FoodFactory {`

`@Override`

`public` `Hamburger buildHamburger() {`

`return` `new` `KFCHamburger();`

`}`

`@Override`

`public` `Drink buildDrink() {`

`return` `new` `KFCDrink();`

`}`

`}`

`interface` `Hamburger {`

`void` `eat();`

`}`

`class` `MCHamburger` `implements` `Hamburger {`

`@Override`

`public` `void` `eat() {`

`System.out.println(``"吃麥當勞漢堡"``);`

`}`

`}`

`class` `KFCHamburger` `implements` `Hamburger {`

`@Override`

`public` `void` `eat() {`

`System.out.println(``"吃肯德基漢堡"``);`

`}`

`}`

`interface` `Drink {`

`void` `drink();`

`}`

`class` `MCDrink` `implements` `Drink {`

`@Override`

`public` `void` `drink() {`

`System.out.println(``"喝麥當勞飲料"``);`

`}`

`}`

`class` `KFCDrink` `implements` `Drink {`

`@Override`

`public` `void` `drink() {`

`System.out.println(``"喝肯德基飲料"``);`

`}`

`}`

複製代碼

P4:單例模式

單例模式屬於建立型模式,是指一個單例類在任何狀況下都只存在一個實例,構造器必須是私有的並由本身建立一個靜態實例對象,並對外提供一個靜態公有的獲取實例方法。優勢是在內存裏只有一個實例,減小了內存的開銷,尤爲是頻繁的建立和銷燬實例的狀況下,而且能夠避免對資源的多重佔用。缺點是沒有抽象層,難以擴展,與單一職責原則衝突。

**餓漢式:**在類加載時就初始化建立單例對象,是線程安全的,但無論是否使用都會建立對象,可能會浪費內存。

`public` `class` `HungrySingleton {`

`private` `HungrySingleton(){}`

`private` `static` `HungrySingleton instance =` `new` `HungrySingleton();`

`public` `static` `HungrySingleton getInstance() {`

`return` `instance;`

`}`

`}`
複製代碼

**懶漢式:**在外部調用時纔會加載,是線程不安全的,能夠加鎖保證線程安全但效率低。

`public` `class` `LazySingleton {`

`private` `LazySingleton(){}`

`private` `static` `LazySingleton instance;`

`public` `static` `LazySingleton getInstance() {`

`if``(instance ==` `null``) {`

`instance =` `new` `LazySingleton();`

`}`

`return` `instance;`

`}`

`}`

複製代碼

**雙重檢查鎖:**使用 volatile 以及兩次檢查來減少 synchronized 鎖範圍,提高效率。

`public` `class` `DoubleCheckSingleton {`

`private` `DoubleCheckSingleton(){}`

`private` `volatile` `static` `DoubleCheckSingleton instance;`

`public` `static` `DoubleCheckSingleton getInstance() {`

`if``(instance ==` `null``) {`

`synchronized` `(DoubleCheckSingleton.``class``) {`

`if` `(instance ==` `null``) {`

`instance =` `new` `DoubleCheckSingleton();`

`}`

`}`

`}`

`return` `instance;`

`}`

`}`

複製代碼

**靜態內部類:**能夠同時解決餓漢式的內存浪費問題和懶漢式的線程安全問題。

`public` `class` `StaticSingleton {`

`private` `StaticSingleton(){}`

`public` `static` `StaticSingleton getInstance() {`

`return` `StaticClass.instance;`

`}`

`private` `static` `class` `StaticClass {`

`private` `static` `final` `StaticSingleton instance =` `new` `StaticSingleton();`

`}`

`}`

複製代碼

**枚舉:**這種方式是 Effective Java 做者提倡的方式,它不只能避免多線程同步問題,還能防止反序列化從新建立新的對象,絕對防止屢次實例化,也能防止反射破解單例的問題。

`public` `enum` `EnumSingleton {`

`INSTANCE;`

`}`

複製代碼

P5:代理模式

代理模式屬於結構型模式,爲其餘對象提供一種代理以控制對這個對象的訪問,能夠加強目標對象的功能。優勢是能夠加強目標對象的功能,必定程度下降代碼耦合度,擴展性好。缺點是在客戶端和目標對象之間增長代理對象會致使請求處理速度變慢,同時也會增長系統複雜度。

**靜態代理:**代理對象持有真實對象的引用,調用代理對象方法時也會調用真實對象的方法,可是會在真實對象方法的先後增長一些其餘邏輯。須要手動完成代理操做,在程序運行前就已經存在代理類的字節碼文件,代理類和被代理類的關係在運行前就已經肯定了。 缺點是一個代理類只能爲一個目標類服務,若是要服務多種類型就會增長很大的工做量。

`public` `interface` `Company {`

`void` `findWorker();`

`}`

`public` `class` `Hr` `implements` `Company {`

`@Override`

`public` `void` `findWorker() {`

`System.out.println(``"我須要找招聘一個員工"``);`

`}`

`}`

`public` `class` `Proxy` `implements` `Company {`

`private` `Hr hr;`

`public` `Proxy(){`

`this``.hr =` `new` `Hr();`

`}`

`@Override`

`public` `void` `findWorker() {`

`hr.findWorker();`

`System.out.println(``"找到了員工"``);`

`}`

`}`

複製代碼

**動態代理:**動態代理在程序運行時才建立具體的代理類,代理類和被代理類的關係在運行前是不肯定的。動態代理的適用性更強,主要分爲 JDK 動態代理和 CGLib 動態代理。

  • **JDK 動態代理:**經過 Proxy類的 newInstance 方法獲取一個動態代理對象,須要傳入三個參數,被代理對象的類加載器、被代理對象實現的接口,以及一個 InvocationHandler 調用處理器實例來指明具體的邏輯,相比靜態代理最大的優點是接口中聲明的全部方法都被轉移到 InvocationHandler 中的 invoke 方法集中處理。
`public` `static` `void` `main(String[] args) {`

  `Hr hr =` `new` `Hr();`

  `Hr proxyHr = (Hr) Proxy.newProxyInstance(hr.getClass().getClassLoader(), hr.getClass().getInterfaces(), (proxy, method, args1) -> {`

  `System.out.println(``"接收代理請求"``);`

  `Object obj = method.invoke(hr, args1);`

  `System.out.println(``"找到了員工,完成請求"``);`

  `return` `obj;`

  `});`

  `proxyHr.findWorker();`

  `}`

複製代碼
  • **CGLib 動態代理:**與 JDK 動態代理不一樣的是,JDK 動態代理要求實現被代理對象的接口,而 CGLib 要求繼承被代理對象,若是一個類是 final 類則不能使用 CGLib 動態代理。兩種代理都是在運行期生成字節碼,JDK 動態代理直接寫字節碼,而 CGLib 動態代理使用 ASM 框架寫字節碼,ASM 做用於已編譯好的 Class 文件,其目的是生成、轉換和分析以字節數組表示的已編譯 Java 類。 JDK 動態代理調用代理方法是經過反射機制實現的,而 GCLib 動態代理是經過 FastClass 機制直接調用方法的,爲代理類和被代理類各生成一個類,該類爲代理類和被代理類的方法會分配一個 int 類型的參數,調用方法時能夠直接定位而省去反射,所以調用方法的效率更高。

P6:裝飾器模式

指在不改變原有對象的基礎上,將功能附加到對象上,相比繼承能夠更加靈活地擴展原有對象的功能,屬於結構型模式。這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下提供了額外的功能。裝飾器模式適合的場景:在不想增長不少子類的前提下擴展一個類的功能或給一個類添加附加職責、動態地給一個類添加功能,這些功能能夠再動態地撤銷。

**和動態代理的區別:**裝飾器模式的關注點在於給對象動態添加方法,而動態代理更注重對象的訪問控制。動態代理一般會在代理類中建立被代理對象的實例,而裝飾器模式會將裝飾者做爲構造器的參數。


P7:適配器模式

適配器模式屬於結構型模式,它做爲兩個不兼容的接口之間的橋樑,結合了兩個獨立接口的功能,將一個類的接口轉換成另一個接口,這種模式涉及到一個單一的類,該類負責加入獨立的或不兼容的接口功能。優勢是使得本來因爲接口不兼容而不能一塊兒工做的類能夠一塊兒工做。 缺點是過多使用適配器會讓系統很是零亂,不易總體進行把握。

**和裝飾器模式的區別:**適配器模式的是要將一個接口轉變成另外一個接口,目的是經過改變接口來解決接口不兼容的問題。而裝飾器模式不是要改變被裝飾對象的接口,而是要加強原有對象的功能。例如 java.io 包中,適配器模式是將 InputStream 字節輸入流經過適配器 InputStreamReader 轉換爲 Reader 字符輸入流,而裝飾器模式是將 InputStream 經過裝飾器 BufferedInputStream 加強爲緩衝字節輸入流。


Java 基礎 17

P1:Java 語言的基本概念

優勢:

  • 具備平臺無關性,擺脫了硬件平臺的束縛,實現了「一次編寫,處處運行」的理想。

  • 提供了一種相對安全的內存管理和訪問機制,避免了絕大部份內存泄漏和指針越界問題。

  • 實現了熱點代碼檢測和運行時編譯及優化,使得 Java 程序隨運行時間增加能夠得到更高的性能。

  • 有一套完善的應用程序接口,還支持不少第三方類庫。

Java 平臺無關性原理:

主要是經過 JVM 和 Java 語言規範實現。

  • 編譯器生成一個體繫結構中立的目標文件格式,這是一種編譯後的代碼,只要有 Java 運行時系統,這些編譯後的代碼能夠在不少處理器上運行。Java 編譯器經過生成與特定計算機體系結構無關的字節碼指令來實現這一特性,字節碼文件不只能夠很容易地在任何機器上解釋執行,還能夠動態地轉換成本地機器代碼,轉換是由 JVM 實現的,JVM 是平臺相關的,屏蔽了不一樣操做系統的差別。
  • Java 中基本數據類型的大小以及有關運算的行爲都有明確的說明,例如 Java 中的 int 類型永遠爲 32 位的整數,而在 C/C++ 中 int 多是 16 位整數、32 位整數,也多是編譯器開發商指定的其餘任何大小。在 Java 中數值類型有固定的字節數,二進制數據以固定的格式進行存儲和傳輸,字符串則採用標準的 Unicode 格式存儲。

專業術語:

  • JDK:Java Development Kit,Java 開發工具包。它提供了編譯、運行 Java 程序所需的各類工具和資源,包括 Java 編譯器、JRE 以及經常使用的 Java 基礎類庫等,是 JAVA 的核心。JDK 是編寫 Java 程序的程序員使用的軟件。
  • JRE:Java Runtime Environment,Java 運行時環境,是運行基於 Java 語言編寫的程序所不可缺乏的運行環境。JRE 是運行 Java 程序的用戶使用的軟件。
  • SE:Standard Edition,標準版,用於桌面或簡單服務器應用的 Java 平臺。
  • EE:Enterprise Edition,企業版,用於複雜服務器應用的 Java 平臺。
  • ME:Micro Edition,微型版,用於小型設備的 Java 平臺。

P2:Java 基本數據類型

數據類型 佔用內存大小 取值範圍
byte 1 字節 -2^7^ ~ 2^7^-1
short 2 字節 -2^15^ ~ 2^15^-1
int 4 字節 -2^31^ ~ 2^31^-1
long 8 字節 -2^63^ ~ 2^63^-1
float 4 字節 ±3.4E+38F(有效位數 6~7 位)
double 8 字節 ±1.7E+308(有效位數 15 位)
char 英文在 UTF-8 和 GBK 中均佔 1 字節,中文在 UTF-8 佔 3 字節,GBK 佔 2 字節。 /
boolean 單個變量用 int 代替,佔 4 字節,而數組會編碼成 byte 數組,佔 1 字節。 true、false

每一個基本數據類型都對應一個本身的包裝類,除了 int 和 char 對應 Integer 和 Character 以外,其他基本數據類型的包裝類都是首字母大寫便可。自動裝箱指的是將基本數據類型包裝爲一個包裝類對象,例如向一個泛型爲 Integer 類型的集合添加 int 類型的元素。自動拆箱指的是將一個包裝類對象轉換爲一個基本數據類型,例如將一個包裝類對象賦值給一個基本數據類型的變量。要比較兩個包裝類的數值須要使用 equals 方法,而不能使用 == 比較運算符。


P3:String

**不可變性:**String 是不可變類,而且存儲數據的 value 字符數組也是 final 修飾的不可變數組,所以當修改一個 String 變量的值時,並無真正修改它引用的 String 對象的字符數組中的值,而是從新建立了一個 String 對象賦值給了 String 變量進行引用。

**字符串拼接:**直接使用 + 進行字符串拼接,若是是字面量會自動拼接爲一個新的常量。要提高拼接效率可使用 StringBuilder 或 StringBuffer 可變字符串,區別是 StringBuffer 使用了 synchronized 保證線程安全性,但通常字符串拼接都是單線程操做,因此使用 StringBuilder 較多。常量和常量的拼接,結果也在常量池中,且不存在兩個相同的常量。只要參與拼接的字符串裏有變量,結果就在堆中。

建立: 若是是經過字符串常量賦值的形式,例如 String s = "s",字符串常量內容存於常量池,變量存於棧中並直接引用常量池中的字符串。若是是經過new 的形式,例如 String s = new String("s"),會先在堆中建立實例對象,而後再去常量池尋找須要的字符串常量,若是找到了則直接使用,沒找到則開闢新的空間並存儲內容,最後棧中變量引用堆中對象,對象再引用常量池中的字符串。


P4:值調用和引用調用

按值調用指的是方法接收的是調用者提供的值,而按引用調用指的是方法接收的是調用者提供的變量地址。Java 老是採用按值調用,也就是說方法獲得的是全部參數值的一個副本,當傳遞對象時實際上方法接收的是這個對象引用的副本。方法不能修改基本數據類型的參數,能夠改變對象參數的狀態,但不能讓對象參數引用一個新的對象。

舉例來講,若是傳遞了一個 int 類型的值 ,改變該值不會影響實參,由於改變的是該值的一個副本。若是傳遞了一個 int[] 類型的數組,改變數組的內容會影響實參,而若是改變這個參數的引用,並不會讓實參引用新的數組對象。


P5:面向對象

**概念:**面向對象是一種程序設計思想,相對於面向過程而言更適合解決規模較大的問題。採用面向對象的開發方式能夠對現實的事物進行抽象,把現實的事物映射爲開發對象,接近人的思惟。而且能夠經過繼承或組合的方式實現代碼的重用,所以開發效率高。而且面向對象的開發方式提升了代碼的可讀性,使代碼結構更加清晰,方便代碼的維護。

特性:

  • **封裝:**也稱數據隱藏,從形式上看就是將數據和行爲組合在一個包中,並對對象的使用者隱藏具體的實現方式。
  • **繼承:**能夠經過繼承來擴展一個類,擴展的子類能夠繼承父類的屬性和方法,並能夠添加本身獨有的屬性和方法。Java 中類只能夠單繼承,接口之間是能夠多繼承的。繼承是一種"is-a"的關係,能夠提升代碼的複用性。
  • **多態:**父類的變量能夠引用一個子類的對象,在運行時經過動態綁定來決定調用的方法。
    • **重載:**是指同一個類中具備多個方法名相同而方法參數列表不一樣的方法,重載方法的返回值類型不作要求,但方法的參數列表必須不一樣。重載屬於一種編譯時多態。
    • **重寫:**是指子類具備和父類方法名和方法參數列表都相同的方法,要求返回值不大於父類方法的返回值,拋出的異常類型不大於父類方法拋出的異常類型,訪問修飾符可見性不小於父類方法的訪問修飾符可見性。重寫屬於一種運行時多態。

P6:方法修飾符

訪問修飾符 本類可見性 本包可見性 子類可見性 不一樣包可見性
public
protected ×
默認 × ×
private × × ×

P7:接口和抽象類

**成員變量:**接口中的成員變量默認是 public static final 修飾的常量,抽象類中的成員變量無特殊要求。

**構造器:**接口和抽象類都不能直接實例化,但接口沒有構造器,抽象類是有構造器的。

**方法:**接口中的方法默認是 public 修飾的,Java 8 開始支持默認方法和靜態方法,Java 9 開始支持私有方法。抽象類中的方法不作要求,抽象類能夠不含抽象方法,但含有抽象方法的類必定是抽象類。

**繼承:**接口能夠多繼承和多實現,而抽象類只能單繼承。

**選擇原則:**若是知道某個類應該成爲基類,那麼第一選擇應該是讓它成爲一個接口,只有在必需要有方法定義和成員變量的時候,才應該選擇抽象類。在接口和抽象類的選擇上,必須遵照這樣一個原則:行爲模型應該老是經過接口而不是抽象類定義。經過抽象類創建行爲模型會出現的問題:若是有一個產品類 A,有兩個子類 B 和 C 分別有本身的功能,若是出現一個既有 B 產品功能又有 C 產品功能的新產品需求,因爲 Java 不容許多繼承就出現了問題,而若是是接口的話只須要同時實現兩個接口便可。


P8:Object 類

Object 的類是全部類的父類,Object 類的方法:

  • **equals:**用於檢測一個對象是否等於另外一個對象,默認使用 == 比較兩個對象的引用,能夠重寫 equals 方法自定義比較規則。equals 方法須要知足如下規範:自反性、對稱性、傳遞性、一致性並對於任何非空引用 x,x.equals(null) 返回 false。
  • **hashCode:**散列碼是由對象導出的一個整型值,是沒有規律的,每一個對象都有一個默認的散列碼,值由對象的存儲地址得出。字符串可能有相同的散列碼,由於字符串的散列碼是由內容導出的。爲了在集合中正確使用對象,通常須要同時重寫 equals 和 hashCode 方法,要求是 equals 相同是 hashCode 必須相同,但 hashCode 相同時 equals 未必相同,所以 hashCode 是兩個對象相同的必要不充分條件。
  • toString:打印對象時默認會調用它的 toString 方法,若是沒有重寫該方法默認打印的是表示對象值的一個字符串,通常須要重寫該方法。打印數組時可使用 Arrays.toString() 方法。
  • **clone:**clone 方法聲明爲 protected,類只能經過該方法克隆它本身的對象,若是但願其餘類也能調用該方法必須定義該方法爲 public。若是一個對象的類沒有實現 Cloneable 接口,該對象調用 clone 方法會拋出一個 CloneNotSupport 異常。默認的 clone 方法是淺拷貝,通常重寫 clone 方法須要實現 Cloneable 接口並指定訪問修飾符爲 public。
    • **淺拷貝:**若是對象包含子對象的引用,拷貝字段就會獲得相同子對象的另外一個引用,若是共享的子對象是不可變的則是安全的,一般子對象都是可變的,所以淺拷貝是不安全的,拷貝對象的更改會影響原對象。
    • **深拷貝:**會徹底拷貝基本數據類型和引用數據類型,深拷貝是安全的。
  • **finalize:**在垃圾收集器清理對象以前調用,因爲沒法肯定該對象執行的具體時機所以已經被廢棄。
  • **getClass:**返回包含對象信息的類對象。
  • **wait / notify / notifyAll:**阻塞或喚醒持有該對象鎖的線程。

P9:內部類

使用內部類主要有兩個緣由:內部類能夠對同一個包中的其餘類隱藏。內部類方法能夠訪問定義這個內部類的做用域中的數據,包括本來私有的數據。內部類是一個編譯器現象,與虛擬機無關。編譯器會把內部類轉換成常規的類文件,用美圓符號 $ 分隔外部類名與內部類名,而虛擬機對此一無所知。

**靜態內部類:**由static修飾,屬於外部類自己,只加載一次。類能夠定義的成分靜態內部類均可以定義,能夠訪問外部類的靜態變量和方法,經過 new 外部類.內部類構造器 來建立對象。只要內部類不須要訪問外部類對象,就應該使用靜態內部類。

**成員內部類:**屬於外部類的每一個對象,隨對象一塊兒加載。不能夠定義靜態成員和方法,能夠訪問外部類的全部內容,經過 new 外部類構造器.new 內部類構造器 來建立對象。

**局部內部類:**定義在方法、構造器、代碼塊、循環中。不能聲明訪問修飾符,只能定義實例成員變量和實例方法,做用範圍僅在聲明這個局部類的代碼塊中。

**匿名內部類:**沒有名字的局部內部類,能夠簡化代碼,匿名內部類會當即建立一個匿名內部類的對象返回,對象類型至關於當前 new 的類的子類類型。匿名內部類通常用於實現事件監聽器和其餘回調。

`class` `OuterClass{`

`static` `class` `StaticInnerClass {}`

`class` `NormalInnerClass {}`

`public` `void` `test() {`

`class` `LocalClass {}`

`// 靜態內部類建立對象`

`new` `OuterClass.StaticInnerClass();`

`// 成員內部類建立對象`

`new` `OuterClass().``new` `NormalInnerClass();`

`// 局部內部類建立對象`

`new` `LocalClass();`

`// 匿名內部類建立對象`

`Runnable runnable = () -> {};`

`}`

`}`

複製代碼

P10:static

static 關鍵字主要有兩個做用:(1)爲某特定數據類型或對象分配單一的存儲空間,而與建立對象的個數無關。(2)讓某個屬性或方法與類而不是對象關聯在一塊兒,能夠在不建立對象的狀況下經過類名來訪問。

做用範圍:

static 修飾的變量稱爲靜態變量,也叫作類變量,能夠直接經過類名來訪問,靜態變量存儲在 JVM 的方法區中。

static 修飾的方法稱爲靜態方法,也叫作類方法,能夠直接經過類名來訪問,靜態方法只能訪問靜態變量或靜態方法。

static 修飾的代碼塊稱爲靜態代碼塊,只能定義在類下,會在類加載時執行,只會執行一次。

static 修飾的類稱爲靜態內部類,能夠訪問外部類的靜態變量和方法。

static 也能夠用來導入包下的靜態變量。

類初始化的順序:

(1)父類靜態代碼塊和靜態變量 (2)子類靜態代碼塊和靜態變量 (3)父類普通代碼塊和普通變量 (4)父類構造器 (5)子類普通代碼塊和普通變量 (6)子類構造器

其中代碼塊和變量的初始化順序按照類中聲明的順序執行。


P11:序列化和反序列化

Java 對象在 JVM 運行時被建立,當 JVM 退出時存活對象都會銷燬,若是須要將對象及其狀態持久化,就須要經過序列化來實現,將對象及其狀態信息保存在字節數組中,在須要時再將這些字節數組反序列化爲對象。對象序列化保存的是對象的狀態,所以類中的靜態變量不會被序列化,由於靜態變量是類屬性。

要實現序列化功能須要實現 java.io.Serializabale 標記接口,序列化和反序列化必須保持序列化 ID 的一致,通常使用 private static final long serialVersionUID 定義序列化 ID,若是須要序列化父類的狀態,父類也須要實現該接口。

有許多序列化框架,例如 fastjson、thrift等,也可使用 JDK 自帶的 ObjectOutputStream 類的 writeObject 方法實現序列化,將對象以流的方式寫入磁盤中,ObjectInputStream 類的 readObject 方法實現反序列化,以流的方式從磁盤讀取。

除了靜態變量外,transient 修飾的變量也不會被序列化。transient 的做用就是把這字段的生命週期僅限於內存中而不會寫到磁盤裏持久化,被 transient 修飾的變量會被設爲對應數據類型的默認初始值。

除了實現 Serializabale 接口外,另外一種方法是實現 Exteranlizable 接口。 須要重寫 writeExternal 和 readExternal 方法,它的效率比Serializable 高一些,而且能夠決定哪些屬性須要序列化(即便是 transient 修飾的變量),可是對大量對象或者重複對象則效率低。


P12:反射

在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法,對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲Java的反射機制。優勢是運行時動態獲取類的所有信息,缺點是破壞了類的封裝性,泛型的約束性。反射是框架的核心靈魂,動態代理設計模式採用了反射機制,還有 Spring、Hibernate 等框架也大量使用到了反射機制。

在程序運行期間,Java 運行時系統始終爲全部對象維護一個運行時類型標識,這個信息會跟蹤每一個對象所屬的類,虛擬機利用運行時類型信息選擇要執行的正確方法,保存這些信息的類名爲 Class。

獲取 Class 實例的方法有三種:(1)直接經過 類名.class 。②經過對象的 getClass()方法。③經過 Class.forName(類的全限定名)。Class 類中的 getFields、getMethods 和 getConstructors 方法分別返回這個類支持的公共字段、方法和構造器的數組,其中包括父類的公共成員。Class 類中的 getDeclaredFields、getDeclaredMethods 和 getDeclaredConstructors 方法分別返回這個類聲明的所有字段、方法和構造器的數組,其中包括私有成員、包成員和受保護成員,但不包括父類的成員。

Field、Method、Constructor 分別用於描述類的字段、方法和構造器。這三個類都有一個 getName 方法返回字段、方法或構造器的名稱。Field 類有一個 getType 方法用來返回描述字段類型的一個對象,這個對象的類型也是 Class。Method 和 Constructor 類有報告參數類型的方法,Method 類還有一個報告返回類型的方法。這三個類都有一個 getModifiers 方法,它返回一個整數,用不一樣的 0/1 位描述所使用的修飾符。


P13:註解

註解是一種標記,可使類或接口附加額外的信息,是幫助編譯器和 JVM 完成一些特定功能的,例如經常使用註解 @Override 標識一個方法是重寫方法。

元註解就是自定義註解的註解,包括:

  • @Target:用來約束註解做用的位置,值是 ElementType 枚舉類實例,包括 METHOD 方法、VARIABLE 變量、TYPE 類/接口、PARAMETER 方法參數、CONSTRUCTORS 構造器和 LOACL_VARIABLE 局部變量等。

  • @Rentention:用來約束註解的生命週期,值是 RetentionPolicy 枚舉類實例,包括:SOURCE 源碼、CLASS 字節碼和 RUNTIME 運行時。

  • @Documented:代表這個註解應該被 javadoc 工具記錄。

  • @Inherited:表面某個被標註的類型是被繼承的。


P14:異常

全部的異常都派生於 Throwable 類的一個類實例,在下一層分爲 Error 和 Exception。

Error 類描述了 Java 運行時系統的內部錯誤和資源耗盡錯誤,若是出現了這種錯誤,通常無能爲力。

Exception 類又分爲 RuntimeException 和其餘異常,通常規則是由編程錯誤致使的異常屬於 RuntimeException,若是程序自己沒有問題,但因爲像 IO 錯誤這類問題致使的異常屬於其餘異常。派生於 Error 和 RuntimeException 的異常屬於非檢查型異常,其他異常都屬於檢查型異常。

常見的 RuntimeException 異常:

  • ClassCastException,錯誤的強制類型轉換。
  • ArrayIndexOutOfBoundsException,數組訪問越界。
  • NullPointerException,空指針異常。

常見的檢查型異常:

  • FileNotFoundException,試圖打開不存在的文件。
  • ClassNotFoundException,試圖根據指定字符串查找 Class 對象,而這個類並不存在。
  • IOException,試圖超越文件末尾繼續讀取數據。

異常處理:

**拋出異常:**遇到異常不進行具體處理,而是將異常拋出給調用者,由調用者根據狀況處理。拋出異常有2種形式,一種是 throws 關鍵字聲明拋出的異常,做用在方法上,一種是使用throw 語句直接拋出異常,做用在方法內。

**捕獲異常:**使用 try/catch 進行異常的捕獲,try 中發生的異常會被 catch 代碼塊捕獲,根據狀況進行處理,若是有 finally 代碼塊不管是否發生異常都會執行,通常用於釋放資源,Java 7 開始能夠將資源定義在 try 代碼塊中自動釋放資源。


P15:泛型

泛型的本質是參數化類型,泛型提供了編譯時類型的安全檢測機制,該機制容許程序在編譯時檢測非法的類型。

類型擦除:

虛擬機沒有泛型類型對象,全部對象都屬於普通類。不管什麼時候定義一個泛型類型,都會自動提供一個相應的原始類型,原始類型的名字就是去掉類型參數後的泛型類型名。類型變量會被擦除,若是沒有限定類型就會替換爲 Object,若是有限定類型就會替換爲第一個限定類型,例如 <T extends A & B> 會使用 A 類型替換 T。

泛型主要用於編譯階段,在編譯後生成的 Java 字節代碼文件中不包含泛型中的類型信息。

泛型規範:

泛型標記 說明
E(Element) 在集合中使用,表示在集合中存放的元素。
T(Type) 表示類,包括基本的類以及自定義類。
K(Key) 表示鍵,例如 Map 集合中的 Key。
V(Value) 表示值,例如 Map 集合中的 Value。
N(Number) 表示數值類型。
表示不肯定的類型。

泛型限定:

對泛型上限的限定使用<? extends T>,它表示該通配符所表明的類型是 T 類的子類型或 T 接口的子接口。

對泛型下限的限定使用<? super T>,它表示該通配符所表明的類型是 T 類的父類型或 T 接口的父接口。


P16:Java 8 新特性

**lambda 表達式:**lambda 表達式容許把函數做爲一個方法的參數傳遞到方法中,主要用來簡化匿名內部類的代碼。

**函數式接口:**使用 @FunctionalInterface 註解標識,有且僅有一個抽象方法,能夠被隱式轉換爲 lambda 表達式。

**方法引用:**能夠直接引用已有類或對象的方法或構造器,進一步簡化 lambda 表達式。方法引用有四種形式:引用構造方法、引用類的靜態方法、引用特定類的任意對象方法、引用某個對象的方法。

**接口中的方法:**接口中能夠定義 default 修飾的默認方法,下降了接口升級的複雜性,還能夠定義靜態方法。

**註解:**Java 8 引入了重複註解機制,相同的註解在同一個地方能夠聲明屢次。註解的做用範圍也進行了擴展,能夠做用於局部變量、泛型、方法異常等。

**類型推測:**增強了類型推測機制,可使代碼更加簡潔,例如在定義泛型集合時能夠省略對象中的泛型參數。

**Optional 類:**用來處理空指針異常,提升代碼可讀性。

**Stream 類:**把函數式編程風格引入 Java 語言,提供了不少功能,可使代碼更加簡潔。方法包括forEach() 遍歷、count() 統計個數、filter() 按條件過濾、limit() 取前 n 個元素、skip() 跳過前 n 個元素、map() 映射加工、concat() 合併stream流等。

**日期:**加強了日期和時間的 API,新的 java.time 主要包含了處理日期、時間、日期/時間、時區、時刻和時鐘等操做。

**JavaScript:**Java 8 提供了一個新的 Nashorn JavaScript 引擎,它容許咱們在 JVM上運行特定的 JavaScript 應用。


P17:Java IO

IO 模型 對應的 Java 版本
BIO(同步阻塞 IO) 1.4 以前
NIO(同步非阻塞 IO) 1.4
AIO(異步非阻塞 IO) 1.7

同步和異步是通訊機制,阻塞和非阻塞是調用狀態。

  • 同步 IO 是用戶線程發起 I/O 請求後須要等待或者輪詢內核 I/O 操做完成後才能繼續執行。

  • 異步 IO 是用戶線程發起 I/O 請求後仍能夠繼續執行,當內核 I/O 操做完成後會通知用戶線程,或者調用用戶線程註冊的回調函數。

  • 阻塞 IO 是指 I/O 操做須要完全完成後才能返回用戶空間 。

  • 非阻塞 IO 是指 I/O 操做被調用後當即返回一個狀態值,無需等 I/O 操做完全完成。

BIO:

同步阻塞式 IO,服務器實現模式爲一個鏈接請求對應一個線程,即客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理,若是這個鏈接不作任何事情會形成沒必要要的線程開銷。能夠經過線程池機制改善,這種 IO 稱爲僞異步 IO。

主要分爲字符流和字節流,字符流包括字符輸入流 Reader 和字符輸出流 Writer,字節流包括字節輸入流 InputStream 和 字節輸出流 OutputStream,字節流和字符流都有對應的緩衝流和過濾流,也能夠將字節流包裝爲字符流。

**適用場景:**鏈接數目少、服務器資源多、開發難度低。


NIO:

同步非阻塞 IO,服務器實現模式爲多個鏈接請求對應一個線程,客戶端發送的鏈接請求都會註冊到一個多路複用器 Selector 上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理,有數據纔會開啓線程處理,性能比較好。

同步是指線程仍是要不斷接收客戶端鏈接並處理數據,非阻塞是指若是一個管道沒有數據,不須要等待,能夠輪詢下一個管道。

有三個核心組件:

  • Selector

    選擇器或多路複用器,主要做用是輪詢檢查多個 Channel 的狀態,判斷 Channel 註冊的事件是否發生,即判斷 Channel 是否處於可讀或可寫狀態。在使用以前須要將 Channel 註冊到 Selector 上,註冊以後會獲得一個 SelectionKey,經過 SelectionKey 能夠獲取 Channel 和 Selector 的相關信息。

  • Channel

    雙向通道,替換了 IO 中的 Stream,不能直接訪問數據,要經過 Buffer 來讀寫數據,也能夠和其餘 Channel 交互。

    **分類:**FileChannel 處理文件、DatagramChannel 處理 UDP 數據、SocketChannel 處理 TCP 數據,用做客戶端、ServerSocketChannel 處理 TCP 數據,用做服務器端。

  • Buffer

    緩衝區,本質是一塊可讀寫數據的內存,這塊內存被包裝成 NIO 的 Buffer 對象,用來簡化數據的讀寫。Buffer 的三個重要屬性:position 表示下一次讀寫數據的位置,limit 表示本次讀寫的極限位置,capacity 表示最大容量。

    • flip() 將寫轉爲讀,底層實現原理是把 position 置 0,並把 limit 設爲當前的 position 值。
    • 經過 clear() 將讀轉爲寫模式(用於讀徹底部數據的狀況,把 position 置 0,limit 設爲 capacity)。
    • 經過 compact() 將讀轉爲寫模式(用於沒有讀徹底部數據,存在未讀數據的狀況,讓 position 指向未讀數據的下一個)。
    • 通道的方向和 Buffer 的方向是相反的,讀取數據至關於向 Buffer 寫入,寫出數據至關於從 Buffer 讀取。

    **使用步驟:**向 Buffer 寫入數據,調用 flip 方法將 Buffer 從寫模式切換爲讀模式,從 Buffer 中讀取數據,調用 clear 或 compact 方法來清空 Buffer。

**適應場景:**鏈接數目多、鏈接時間短、開發難度高。


AIO:

異步非阻塞 IO,服務器實現模式爲一個有效請求對應一個線程,客戶端的 I/O 請求都是由操做系統先完成 IO 操做後再通知服務器應用來啓動線程直接使用數據。

異步是指服務端線程接收到客戶端管道後就交給底層處理IO通訊,本身能夠作其餘事情,非阻塞是指客戶端有數據纔會處理,處理好再通知服務器。

AsynchronousServerSocketChannel 異步服務器端通道,經過靜態方法 open() 獲取實例,經過 accept 方法獲取客戶端鏈接通道。

AsynchronousSocketChannel 異步客戶端通道,經過靜態方法 open() 獲取實例,過 connect 方法鏈接服務器通道。

AsynchronousChannelGroup 異步通道分組管理器,它能夠實現資源共享。建立時須要傳入一個ExecutorService,也就是綁定一個線程池,該線程池負責兩個任務:處理 IO 事件和觸發 CompletionHandler 回調接口。

實現方式:

經過 Future 的 get 方法進行阻塞式調用。

經過實現 CompletionHandler 接口,重寫請求成功的回調方法 completed() 和 請求失敗回調方法 failed()。

**適用場景:**鏈接數目多、鏈接時間長、開發難度高。


##最後 以上資料所有已整理成相關PDF文檔,如需獲取,請於後臺私信PDF 領取便可

對於本篇內容有其餘疑問的,歡迎在底部評論區留言便可!

相關文章
相關標籤/搜索