【備戰秋招】高質量 Java知識點整理1:算法、設計模式、Java 基礎

不喜歡 CSDN 的複製黏貼,不喜歡公衆號的關注領取。

無大廠情懷,總結偏個人認爲的高頻考察基礎知識,本章主要包括排序算法、設計模式和 Java 基礎。

待總結內容:併發、JVM、SPring、MySQL、Redis、計算機網絡、操作系統。


排序算法 9

P1:排序算法的分類

排序算法可以分爲內部排序和外部排序,在內存中進行的排序稱爲內部排序,當要排序的數據量很大時無法全部拷貝到內存,需要使用外存進行排序,這種排序稱爲外部排序。

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


P2:直接插入排序

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

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

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(n1.3),最差時間複雜度爲 O(n²),最好時間複雜度爲 O(n),空間複雜度爲 O(1)。

基本原理是把記錄按下標的一定增量分組,對每組進行直接插入排序,每次排序後減小增量,當增量減至 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)。

基本原理是將待排序記錄看作完全二叉樹,可以建立大根堆或小根堆,大根堆中每個節點的值都不小於它的子節點值,小根堆中每個節點的值都不大於它的子節點值。適用於數據量較大的情況。

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

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 字節 -27 ~ 27-1
short 2 字節 -215 ~ 215-1
int 4 字節 -231 ~ 231-1
long 8 字節 -263 ~ 263-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()。

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


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