java面試題文檔(QA)

基礎篇

1、 Java語言有哪些特點

1、簡單易學、有豐富的類庫

2、面向對象(Java最重要的特性,讓程序耦合度更低,內聚性更高)

3、與平臺無關性(JVM是Java跨平臺使用的根本)

4、可靠安全

5、支持多線程

2、面向對象和麪向過程的區別

面向過程:是分析解決問題的步驟,然後用函數把這些步驟一步一步地實現,然後在使用的時候一一調用則可。性能較高,所以單片機、嵌入式開發等一般採用面向過程開發

面向對象:是把構成問題的事務分解成各個對象,而建立對象的目的也不是爲了完成一個個步驟,而是爲了描述某個事物在解決整個問題的過程中所發生的行爲。面向對象有封裝、繼承、多態的特性,所以易維護、易複用、易擴展。可以設計出低耦合的系統。 但是性能上來說,比面向過程要低。

3 、八種基本數據類型的大小,以及他們的封裝類

基本類型 大小(字節) 默認值 封裝類
byte 1 (byte)0 Byte
short 2 (short)0 Short
int 4 0 Integer
long 8 0L Long
float 4 0.0f Float
double 8 0.0d Double
boolean - false Boolean
char 2 \u0000(null) Character

注:

1.int是基本數據類型,Integer是int的封裝類,是引用類型。int默認值是0,而Integer默認值是null,所以Integer能區分出0和null的情況。一旦java看到null,就知道這個引用還沒有指向某個對象,再任何引用使用前,必須爲其指定一個對象,否則會報錯。

2.基本數據類型在聲明時系統會自動給它分配空間,而引用類型聲明時只是分配了引用空間,必須通過實例化開闢數據空間之後纔可以賦值。數組對象也是一個引用對象,將一個數組賦值給另一個數組時只是複製了一個引用,所以通過某一個數組所做的修改在另一個數組中也看的見。

雖然定義了boolean這種數據類型,但是隻對它提供了非常有限的支持。在Java虛擬機中沒有任何供boolean值專用的字節碼指令,Java語言表達式所操作的boolean值,在編譯之後都使用Java虛擬機中的int數據類型來代替,而boolean數組將會被編碼成Java虛擬機的byte數組,每個元素boolean元素佔8位。這樣我們可以得出boolean類型佔了單獨使用是4個字節,在數組中又是1個字節。使用int的原因是,對於當下32位的處理器(CPU)來說,一次處理數據是32位(這裏不是指的是32/64位系統,而是指CPU硬件層面),具有高效存取的特點。

4、標識符的命名規則。

標識符的含義:
是指在程序中,我們自己定義的內容,譬如,類的名字,方法名稱以及變量名稱等等,都是標識符。

命名規則:(硬性要求)
標識符可以包含英文字母,0-9的數字,$以及_
標識符不能以數字開頭
標識符不是關鍵字

命名規範:(非硬性要求)
類名規範:首字符大寫,後面每個單詞首字母大寫(大駝峯式)。
變量名規範:首字母小寫,後面每個單詞首字母大寫(小駝峯式)。
方法名規範:同變量名。

5、instanceof 關鍵字的作用

instanceof 嚴格來說是Java中的一個雙目運算符,用來測試一個對象是否爲一個類的實例,用法爲:

boolean result = obj instanceof Class

其中 obj 爲一個對象,Class 表示一個類或者一個接口,當 obj 爲 Class 的對象,或者是其直接或間接子類,或者是其接口的實現類,結果result 都返回 true,否則返回false。

注意:編譯器會檢查 obj 是否能轉換成右邊的class類型,如果不能轉換則直接報錯,如果不能確定類型,則通過編譯,具體看運行時定。

int i = 0;
System.out.println(i instanceof Integer);//編譯不通過  i必須是引用類型,不能是基本類型
System.out.println(i instanceof Object);//編譯不通過
Integer integer = new Integer(1);
System.out.println(integer instanceof  Integer);//true
//false   ,在 JavaSE規範 中對 instanceof 運算符的規定就是:如果 obj 爲 null,那麼將返回 false。
System.out.println(null instanceof Object);

6、Java自動裝箱與拆箱

裝箱就是自動將基本數據類型轉換爲包裝器類型(int–>Integer);調用方法:Integer的valueOf(int) 方法

**拆箱就是自動將包裝器類型轉換爲基本數據類型(Integer–>int)。調用方法:Integer的intValue方法 **

在Java SE5之前,如果要生成一個數值爲10的Integer對象,必須這樣進行:

Integer i = new Integer(10);

而在從Java SE5開始就提供了自動裝箱的特性,如果要生成一個數值爲10的Integer對象,只需要這樣就可以了:

Integer i = 10;

面試題1: 以下代碼會輸出什麼?

public class Main {
    public static void main(String[] args) {
         
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

運行結果:

true
false

爲什麼會出現這樣的結果?輸出結果表明i1和i2指向的是同一個對象,而i3和i4指向的是不同的對象。此時只需一看源碼便知究竟,下面這段代碼是Integer的valueOf方法的具體實現:

public static Integer valueOf(int i) {
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }

其中IntegerCache類的實現爲:

private static class IntegerCache {
        static final int high;
        static final Integer cache[];

        static {
            final int low = -128;

            // high value may be configured by property
            int h = 127;
            if (integerCacheHighPropValue != null) {
                // Use Long.decode here to avoid invoking methods that
                // require Integer's autoboxing cache to be initialized
                int i = Long.decode(integerCacheHighPropValue).intValue();
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - -low);
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
        }

        private IntegerCache() {}
    }

從這2段代碼可以看出,在通過valueOf方法創建Integer對象的時候,如果數值在[-128,127]之間,便返回指向IntegerCache.cache中已經存在的對象的引用;否則創建一個新的Integer對象。

上面的代碼中i1和i2的數值爲100,因此會直接從cache中取已經存在的對象,所以i1和i2指向的是同一個對象,而i3和i4則是分別指向不同的對象。

面試題2:以下代碼輸出什麼?

public class Main {
    public static void main(String[] args) {
         
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

運行結果:

false
false

原因: 在某個範圍內的整型數值的個數是有限的,而浮點數卻不是。

7、 重載和重寫的區別

重寫(Override)

從字面上看,重寫就是 重新寫一遍的意思。其實就是在子類中把父類本身有的方法重新寫一遍。子類繼承了父類原有的方法,但有時子類並不想原封不動的繼承父類中的某個方法,所以在方法名,參數列表,返回類型(除過子類中方法的返回值是父類中方法返回值的子類時)都相同的情況下, 對方法體進行修改或重寫,這就是重寫。但要注意子類函數的訪問修飾權限不能少於父類的。

public class Father {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Son s = new Son();
        s.sayHello();
    }

    public void sayHello() {
        System.out.println("Hello");
    }
}

class Son extends Father{

    @Override
    public void sayHello() {
        // TODO Auto-generated method stub
        System.out.println("hello by ");
    }

}

重寫 總結:
1.發生在父類與子類之間
2.方法名,參數列表,返回類型(除過子類中方法的返回類型是父類中返回類型的子類)必須相同
3.訪問修飾符的限制一定要大於被重寫方法的訪問修飾符(public>protected>default>private)
4.重寫方法一定不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的檢查型異常

重載(Overload)

在一個類中,同名的方法如果有不同的參數列表(參數類型不同、參數個數不同甚至是參數順序不同)則視爲重載。同時,重載對返回類型沒有要求,可以相同也可以不同,但不能通過返回類型是否相同來判斷重載

public class Father {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Father s = new Father();
        s.sayHello();
        s.sayHello("wintershii");

    }

    public void sayHello() {
        System.out.println("Hello");
    }

    public void sayHello(String name) {
        System.out.println("Hello" + " " + name);
    }
}

重載 總結:
1.重載Overload是一個類中多態性的一種表現
2.重載要求同名方法的參數列表不同(參數類型,參數個數甚至是參數順序)
3.重載的時候,返回值類型可以相同也可以不相同。無法以返回型別作爲重載函數的區分標準

8、 equals與==的區別

== :

== 比較的是變量(棧)內存中存放的對象的(堆)內存地址,用來判斷兩個對象的地址是否相同,即是否是指相同一個對象。比較的是真正意義上的指針操作。

1、比較的是操作符兩端的操作數是否是同一個對象。
2、兩邊的操作數必須是同一類型的(可以是父子類之間)才能編譯通過。
3、比較的是地址,如果是具體的阿拉伯數字的比較,值相等則爲true,如:
int a=10 與 long b=10L 與 double c=10.0都是相同的(爲true),因爲他們都指向地址爲10的堆。

equals

equals用來比較的是兩個對象的內容是否相等,由於所有的類都是繼承自java.lang.Object類的,所以適用於所有對象,如果沒有對該方法進行覆蓋的話,調用的仍然是Object類中的方法,而Object中的equals方法返回的卻是==的判斷。

總結:

所有比較是否相等時,都是用equals 並且在對常量相比較時,把常量寫在前面,因爲使用object的equals object可能爲null 則空指針

在阿里的代碼規範中只使用equals ,阿里插件默認會識別,並可以快速修改,推薦安裝阿里插件來排查老代碼使用“==”,替換成equals

9、 Hashcode的作用

java的集合有兩類,一類是List,還有一類是Set。前者有序可重複,後者無序不重複。當我們在set中插入的時候怎麼判斷是否已經存在該元素呢,可以通過equals方法。但是如果元素太多,用這樣的方法就會比較滿。

於是有人發明了哈希算法來提高集合中查找元素的效率。 這種方式將集合分成若干個存儲區域,每個對象可以計算出一個哈希碼,可以將哈希碼分組,每組分別對應某個存儲區域,根據一個對象的哈希碼就可以確定該對象應該存儲的那個區域。

hashCode方法可以這樣理解:它返回的就是根據對象的內存地址換算出的一個值。這樣一來,當集合要添加新的元素時,先調用這個元素的hashCode方法,就一下子能定位到它應該放置的物理位置上。如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進行任何比較了;如果這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址。這樣一來實際調用equals方法的次數就大大降低了,幾乎只需要一兩次。

10、String、String StringBuffer 和 StringBuilder 的區別是什麼?

String是隻讀字符串,它並不是基本數據類型,而是一個對象。從底層源碼來看是一個final類型的字符數組,所引用的字符串不能被改變,一經定義,無法再增刪改。每次對String的操作都會生成新的String對象。

private final char value[];

每次+操作 : 隱式在堆上new了一個跟原字符串相同的StringBuilder對象,再調用append方法 拼接+後面的字符。

StringBuffer和StringBuilder他們兩都繼承了AbstractStringBuilder抽象類,從AbstractStringBuilder抽象類中我們可以看到

/**
     * The value is used for character storage.
     */
    char[] value;

他們的底層都是可變的字符數組,所以在進行頻繁的字符串操作時,建議使用StringBuffer和StringBuilder來進行操作。 另外StringBuffer 對方法加了同步鎖或者對調用的方法加了同步鎖,所以是線程安全的。StringBuilder 並沒有對方法進行加同步鎖,所以是非線程安全的。

11、ArrayList和linkedList的區別

Array(數組)是基於索引(index)的數據結構,它使用索引在數組中搜索和讀取數據是很快的。

Array獲取數據的時間複雜度是O(1),但是要刪除數據卻是開銷很大,因爲這需要重排數組中的所有數據, (因爲刪除數據以後, 需要把後面所有的數據前移)

缺點: 數組初始化必須指定初始化的長度, 否則報錯

例如:

int[] a = new int[4];//推介使用int[] 這種方式初始化

int c[] = {23,43,56,78};//長度:4,索引範圍:[0,3]

List—是一個有序的集合,可以包含重複的元素,提供了按索引訪問的方式,它繼承Collection。

List有兩個重要的實現類:ArrayList和LinkedList

ArrayList: 可以看作是能夠自動增長容量的數組

ArrayList的toArray方法返回一個數組

ArrayList的asList方法返回一個列表

ArrayList底層的實現是Array, 數組擴容實現

LinkList是一個雙鏈表,在添加和刪除元素時具有比ArrayList更好的性能.但在get與set方面弱於ArrayList.當然,這些對比都是指數據量很大或者操作很頻繁。

12、 HashMap和HashTable的區別

1、兩者父類不同

HashMap是繼承自AbstractMap類,而Hashtable是繼承自Dictionary類。不過它們都實現了同時實現了map、Cloneable(可複製)、Serializable(可序列化)這三個接口。

2、對外提供的接口不同

Hashtable比HashMap多提供了elments() 和contains() 兩個方法。
elments() 方法繼承自Hashtable的父類Dictionnary。elements() 方法用於返回此Hashtable中的value的枚舉。

contains()方法判斷該Hashtable是否包含傳入的value。它的作用與containsValue()一致。事實上,contansValue() 就只是調用了一下contains() 方法。

3、對null的支持不同

Hashtable:key和value都不能爲null。

HashMap:key可以爲null,但是這樣的key只能有一個,因爲必須保證key的唯一性;可以有多個key值對應的value爲null。

4、安全性不同

HashMap是線程不安全的,在多線程併發的環境下,可能會產生死鎖等問題,因此需要開發人員自己處理多線程的安全問題。

Hashtable是線程安全的,它的每個方法上都有synchronized 關鍵字,因此可直接用於多線程中。

雖然HashMap是線程不安全的,但是它的效率遠遠高於Hashtable,這樣設計是合理的,因爲大部分的使用場景都是單線程。當需要多線程操作的時候可以使用線程安全的ConcurrentHashMap。

ConcurrentHashMap雖然也是線程安全的,但是它的效率比Hashtable要高好多倍。因爲ConcurrentHashMap使用了分段鎖,並不對整個數據進行鎖定。

5、初始容量大小和每次擴充容量大小不同

6、計算hash值的方法不同

13、 Collection包結構,與Collections的區別

Collection是集合類的上級接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、Set;

Collections是集合類的一個幫助類, 它包含有各種有關集合操作的靜態多態方法,用於實現對各種集合的搜索、排序、線程安全化等操作。此類不能實例化,就像一個工具類,服務於Java的Collection框架。

14、 Java的四種引用,強弱軟虛

  • 強引用

    強引用是平常中使用最多的引用,強引用在程序內存不足(OOM)的時候也不會被回收,使用方式:

    String str = new String("str");
    
  • 軟引用

    軟引用在程序內存不足時,會被回收,使用方式:

    // 注意:wrf這個引用也是強引用,它是指向SoftReference這個對象的,
    // 這裏的軟引用指的是指向new String("str")的引用,也就是SoftReference類中T
    SoftReference<String> wrf = new SoftReference<String>(new String("str"));
    

    可用場景: 創建緩存的時候,創建的對象放進緩存中,當內存不足時,JVM就會回收早先創建的對象。

  • 弱引用

    弱引用就是隻要JVM垃圾回收器發現了它,就會將之回收,使用方式:

    WeakReference<String> wrf = new WeakReference<String>(str);
    

    可用場景: Java源碼中的java.util.WeakHashMap中的key就是使用弱引用,我的理解就是,一旦我不需要某個引用,JVM會自動幫我處理它,這樣我就不需要做其它操作。

  • 虛引用

    虛引用的回收機制跟弱引用差不多,但是它被回收之前,會被放入ReferenceQueue中。注意哦,其它引用是被JVM回收後才被傳入ReferenceQueue中的。由於這個機制,所以虛引用大多被用於引用銷燬前的處理工作。還有就是,虛引用創建的時候,必須帶有ReferenceQueue,使用例子:

    PhantomReference<String> prf = new PhantomReference<String>(new String("str"), new ReferenceQueue<>());
    

    可用場景: 對象銷燬前的一些操作,比如說資源釋放等。**Object.finalize()雖然也可以做這類動作,但是這個方式即不安全又低效

上訴所說的幾類引用,都是指對象本身的引用,而不是指Reference的四個子類的引用(SoftReference等)。

15、 泛型常用特點

泛型是Java SE 1.5之後的特性, 《Java 核心技術》中對泛型的定義是:

“泛型” 意味着編寫的代碼可以被不同類型的對象所重用。

“泛型”,顧名思義,“泛指的類型”。我們提供了泛指的概念,但具體執行的時候卻可以有具體的規則來約束,比如我們用的非常多的ArrayList就是個泛型類,ArrayList作爲集合可以存放各種元素,如Integer, String,自定義的各種類型等,但在我們使用的時候通過具體的規則來約束,如我們可以約束集合中只存放Integer類型的元素,如

List<Integer> iniData = new ArrayList<>()

使用泛型的好處?

以集合來舉例,使用泛型的好處是我們不必因爲添加元素類型的不同而定義不同類型的集合,如整型集合類,浮點型集合類,字符串集合類,我們可以定義一個集合來存放整型、浮點型,字符串型數據,而這並不是最重要的,因爲我們只要把底層存儲設置了Object即可,添加的數據全部都可向上轉型爲Object。 更重要的是我們可以通過規則按照自己的想法控制存儲的數據類型。

16、Java創建對象有幾種方式?

java中提供了以下四種創建對象的方式:

  • new創建新對象
  • 通過反射機制
  • 採用clone機制
  • 通過序列化機制

17、有沒有可能兩個不相等的對象有相同的hashcode

有可能.在產生hash衝突時,兩個不相等的對象就會有相同的 hashcode 值.當hash衝突產生時,一般有以下幾種方式來處理:

  • 拉鍊法:每個哈希表節點都有一個next指針,多個哈希表節點可以用next指針構成一個單向鏈表,被分配到同一個索引上的多個節點可以用這個單向鏈表進行存儲.
  • 開放定址法:一旦發生了衝突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存入
  • 再哈希:又叫雙哈希法,有多個不同的Hash函數.當發生衝突時,使用第二個,第三個….等哈希函數計算地址,直到無衝突.

18、深拷貝和淺拷貝的區別是什麼?

  • 淺拷貝:被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象.換言之,淺拷貝僅僅複製所考慮的對象,而不復制它所引用的對象.

  • 深拷貝:被複制對象的所有變量都含有與原來的對象相同的值.而那些引用其他對象的變量將指向被複制過的新對象.而不再是原有的那些被引用的對象.換言之.深拷貝把要複製的對象所引用的對象都複製了一遍.

19、final有哪些用法?

final也是很多面試喜歡問的地方,但我覺得這個問題很無聊,通常能回答下以下5點就不錯了:

  • 被final修飾的類不可以被繼承
  • 被final修飾的方法不可以被重寫
  • 被final修飾的變量不可以被改變.如果修飾引用,那麼表示引用不可變,引用指向的內容可變.
  • 被final修飾的方法,JVM會嘗試將其內聯,以提高運行效率
  • 被final修飾的常量,在編譯階段會存入常量池中.

除此之外,編譯器對final域要遵守的兩個重排序規則更好:

在構造函數內對一個final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序
初次讀一個包含final域的對象的引用,與隨後初次讀這個final域,這兩個操作之間不能重排序.

20、static都有哪些用法?

所有的人都知道static關鍵字這兩個基本的用法:靜態變量和靜態方法.也就是被static所修飾的變量/方法都屬於類的靜態資源,類實例所共享.

除了靜態變量和靜態方法之外,static也用於靜態塊,多用於初始化操作:

public calss PreCache{
    static{
        //執行相關操作
    }
}

此外static也多用於修飾內部類,此時稱之爲靜態內部類.

最後一種用法就是靜態導包,即import static.import static是在JDK 1.5之後引入的新特性,可以用來指定導入某個類中的靜態資源,並且不需要使用類名,可以直接使用資源名,比如:

import static java.lang.Math.*;
 
public class Test{
 
    public static void main(String[] args){
        //System.out.println(Math.sin(20));傳統做法
        System.out.println(sin(20));
    }
}

21、3*0.1==0.3返回值是什麼

false,因爲有些浮點數不能完全精確的表示出來.

22、a=a+b與a+=b有什麼區別嗎?

+=操作符會進行隱式自動類型轉換,此處a+=b隱式的將加操作的結果類型強制轉換爲持有結果的類型,而a=a+b則不會自動進行類型轉換.如:

byte a = 127;
byte b = 127;
b = a + b; // 報編譯錯誤:cannot convert from int to byte
b += a; 

以下代碼是否有錯,有的話怎麼改?

short s1= 1;
s1 = s1 + 1;

有錯誤.short類型在進行運算時會自動提升爲int類型,也就是說s1+1的運算結果是int類型,而s1是short類型,此時編譯器會報錯.

正確寫法:

short s1= 1; 
s1 += 1; 

+=操作符會對右邊的表達式結果強轉匹配左邊的數據類型,所以沒錯.

23、try catch finally,try裏有return,finally還執行麼?

執行,並且finally的執行早於try裏面的return

結論:

1、不管有木有出現異常,finally塊中代碼都會執行;

2、當try和catch中有return時,finally仍然會執行;

3、finally是在return後面的表達式運算後執行的(此時並沒有返回運算後的值,而是先把要返回的值保存起來,管finally中的代碼怎麼樣,返回的值都不會改變,任然是之前保存的值),所以函數返回值是在finally執行前確定的;

4、finally中最好不要包含return,否則程序會提前退出,返回值不是try或catch中保存的返回值。

24、 Excption與Error包結構

Java可拋出(Throwable)的結構分爲三種類型:被檢查的異常(CheckedException),運行時異常(RuntimeException),錯誤(Error)。

1、運行時異常

定義:RuntimeException及其子類都被稱爲運行時異常。

特點:Java編譯器不會檢查它。也就是說,當程序中可能出現這類異常時,倘若既"沒有通過throws聲明拋出它",也"沒有用try-catch語句捕獲它",還是會編譯通過。例如,除數爲零時產生的ArithmeticException異常,數組越界時產生的IndexOutOfBoundsException異常,fail-fast機制產生的ConcurrentModificationException異常(java.util包下面的所有的集合類都是快速失敗的,“快速失敗”也就是fail-fast,它是Java集合的一種錯誤檢測機制。當多個線程對集合進行結構上的改變的操作時,有可能會產生fail-fast機制。記住是有可能,而不是一定。例如:假設存在兩個線程(線程1、線程2),線程1通過Iterator在遍歷集合A中的元素,在某個時候線程2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那麼這個時候程序就會拋出 ConcurrentModificationException 異常,從而產生fail-fast機制,這個錯叫併發修改異常。Fail-safe,java.util.concurrent包下面的所有的類都是安全失敗的,在遍歷過程中,如果已經遍歷的數組上的內容變化了,迭代器不會拋出ConcurrentModificationException異常。如果未遍歷的數組上的內容發生了變化,則有可能反映到迭代過程中。這就是ConcurrentHashMap迭代器弱一致的表現。ConcurrentHashMap的弱一致性主要是爲了提升效率,是一致性與效率之間的一種權衡。要成爲強一致性,就得到處使用鎖,甚至是全局鎖,這就與Hashtable和同步的HashMap一樣了。)等,都屬於運行時異常。

常見的五種運行時異常:

ClassCastException(類轉換異常)

IndexOutOfBoundsException(數組越界)

NullPointerException(空指針異常)

ArrayStoreException(數據存儲異常,操作數組是類型不一致)

BufferOverflowException

2、被檢查異常

定義:Exception類本身,以及Exception的子類中除了"運行時異常"之外的其它子類都屬於被檢查異常。

特點 : Java編譯器會檢查它。 此類異常,要麼通過throws進行聲明拋出,要麼通過try-catch進行捕獲處理,否則不能通過編譯。例如,CloneNotSupportedException就屬於被檢查異常。當通過clone()接口去克隆一個對象,而該對象對應的類沒有實現Cloneable接口,就會拋出CloneNotSupportedException異常。被檢查異常通常都是可以恢復的。
如:

IOException

FileNotFoundException

SQLException

被檢查的異常適用於那些不是因程序引起的錯誤情況,比如:讀取文件時文件不存在引發的FileNotFoundException。然而,不被檢查的異常通常都是由於糟糕的編程引起的,比如:在對象引用時沒有確保對象非空而引起的NullPointerException

3、錯誤

定義 : Error類及其子類。

特點 : 和運行時異常一樣,編譯器也不會對錯誤進行檢查。

當資源不足、約束失敗、或是其它程序無法繼續運行的條件發生時,就產生錯誤。程序本身無法修復這些錯誤的。例如,VirtualMachineError就屬於錯誤。出現這種錯誤會導致程序終止運行。OutOfMemoryError、ThreadDeath。

Java虛擬機規範規定JVM的內存分爲了好幾塊,比如堆,棧,程序計數器,方法區等

25、OOM你遇到過哪些情況,SOF你遇到過哪些情況

OOM

1,OutOfMemoryError異常

除了程序計數器外,虛擬機內存的其他幾個運行時區域都有發生OutOfMemoryError(OOM)異常的可能。

Java Heap 溢出:

一般的異常信息:java.lang.OutOfMemoryError:Java heap spacess。

java堆用於存儲對象實例,我們只要不斷的創建對象,並且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,就會在對象數量達到最大堆容量限制後產生內存溢出異常。

出現這種異常,一般手段是先通過內存映像分析工具(如Eclipse Memory Analyzer)對dump出來的堆轉存快照進行分析,重點是確認內存中的對象是否是必要的,先分清是因爲內存泄漏(Memory Leak)還是內存溢出(Memory Overflow)。

如果是內存泄漏,可進一步通過工具查看泄漏對象到GCRoots的引用鏈。於是就能找到泄漏對象是通過怎樣的路徑與GC Roots相關聯並導致垃圾收集器無法自動回收。

如果不存在泄漏,那就應該檢查虛擬機的參數(-Xmx與-Xms)的設置是否適當。

2,虛擬機棧和本地方法棧溢出

如果線程請求的棧深度大於虛擬機所允許的最大深度,將拋出StackOverflowError異常。

如果虛擬機在擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError異常

這裏需要注意當棧的大小越大可分配的線程數就越少。

3,運行時常量池溢出

異常信息:java.lang.OutOfMemoryError:PermGenspace

如果要向運行時常量池中添加內容,最簡單的做法就是使用String.intern()這個Native方法。該方法的作用是:如果池中已經包含一個等於此String的字符串,則返回代表池中這個字符串的String對象;否則,將此String對象包含的字符串添加到常量池中,並且返回此String對象的引用。由於常量池分配在方法區內,我們可以通過-XX:PermSize和-XX:MaxPermSize限制方法區的大小,從而間接限制其中常量池的容量。

4,方法區溢出

方法區用於存放Class的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。也有可能是方法區中保存的class對象沒有被及時回收掉或者class信息佔用的內存超過了我們配置。

異常信息:java.lang.OutOfMemoryError:PermGenspace

方法區溢出也是一種常見的內存溢出異常,一個類如果要被垃圾收集器回收,判定條件是很苛刻的。在經常動態生成大量Class的應用中,要特別注意這點。

SOF(堆棧溢出StackOverflow):

StackOverflowError 的定義:當應用程序遞歸太深而發生堆棧溢出時,拋出該錯誤。

因爲棧一般默認爲1-2m,一旦出現死循環或者是大量的遞歸調用,在不斷的壓棧過程中,造成棧容量超過1m而導致溢出。

棧溢出的原因:遞歸調用,大量循環或死循環,全局變量是否過多,數組、List、map數據過大。

26、 簡述線程、程序、進程的基本概念。以及他們之間關係是什麼?

線程與進程相似,但線程是一個比進程更小的執行單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享同一塊內存空間和一組系統資源,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因爲如此,線程也被稱爲輕量級進程。

程序是含有指令和數據的文件,被存儲在磁盤或其他的數據存儲設備中,也就是說程序是靜態的代碼。

進程是程序的一次執行過程,是系統運行程序的基本單位,因此進程是動態的。系統運行一個程序即是一個進程從創建,運行到消亡的過程。簡單來說,一個進程就是一個執行中的程序,它在計算機中一個指令接着一個指令地執行着,同時,每個進程還佔有某些系統資源如 CPU 時間,內存空間,文件,輸入輸出設備的使用權等等。換句話說,當程序在執行時,將會被操作系統載入內存中。 線程是進程劃分成的更小的運行單位。線程和進程最大的不同在於基本上各進程是獨立的,而各線程則不一定,因爲同一進程中的線程極有可能會相互影響。從另一角度來說,進程屬於操作系統的範疇,主要是同一段時間內,可以同時執行一個以上的程序,而線程則是在同一程序內幾乎同時執行一個以上的程序段。

27、線程有哪些基本狀態?

Java 線程在運行的生命週期中的指定時刻只可能處於下面6種不同狀態的其中一個狀態(圖源《Java 併發編程藝術》4.1.4節)。
在這裏插入圖片描述
線程在生命週期中並不是固定處於某一個狀態而是隨着代碼的執行在不同狀態之間切換。Java 線程狀態變遷如下圖所示(圖源《Java 併發編程藝術》4.1.4節):

在這裏插入圖片描述

操作系統隱藏 Java虛擬機(JVM)中的 RUNNABLE 和 RUNNING 狀態,它只能看到 RUNNABLE 狀態(圖源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系統一般將這兩個狀態統稱爲 RUNNABLE(運行中) 狀態 。

操作系統隱藏 Java虛擬機(JVM)中的 RUNNABLE 和 RUNNING 狀態,它只能看到 RUNNABLE 狀態(圖源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系統一般將這兩個狀態統稱爲 RUNNABLE(運行中) 狀態 。

在這裏插入圖片描述

當線程執行 wait()方法之後,線程進入 **WAITING(等待)**狀態。進入等待狀態的線程需要依靠其他線程的通知才能夠返回到運行狀態,而 TIME_WAITING(超時等待) 狀態相當於在等待狀態的基礎上增加了超時限制,比如通過 sleep(long millis)方法或 wait(long millis)方法可以將 Java 線程置於 TIMED WAITING 狀態。當超時時間到達後 Java 線程將會返回到 RUNNABLE 狀態。當線程調用同步方法時,在沒有獲取到鎖的情況下,線程將會進入到 BLOCKED(阻塞) 狀態。線程在執行 Runnable 的run()方法之後將會進入到 TERMINATED(終止) 狀態。

28、Java 序列化中如果有些字段不想進行序列化,怎麼辦?

對於不想進行序列化的變量,使用 transient 關鍵字修飾。

transient 關鍵字的作用是:阻止實例中那些用此關鍵字修飾的的變量序列化;當對象被反序列化時,被 transient 修飾的變量值不會被持久化和恢復。transient 只能修飾變量,不能修飾類和方法。

29、Java 中 IO 流

Java 中 IO 流分爲幾種?

  • 按照流的流向分,可以分爲輸入流和輸出流;
  • 按照操作單元劃分,可以劃分爲字節流和字符流;
  • 按照流的角色劃分爲節點流和處理流。

Java Io 流共涉及 40 多個類,這些類看上去很雜亂,但實際上很有規則,而且彼此之間存在非常緊密的聯繫, Java I0 流的 40 多個類都是從如下 4 個抽象類基類中派生出來的。

  • InputStream/Reader: 所有的輸入流的基類,前者是字節輸入流,後者是字符輸入流。
  • OutputStream/Writer: 所有輸出流的基類,前者是字節輸出流,後者是字符輸出流。

按操作方式分類結構圖:

IO-操作方式分類

按操作對象分類結構圖:

IO-操作對象分類

30、 Java IO與 NIO的區別

推薦閱讀:https://mp.weixin.qq.com/s/N1ojvByYmary65B6JM1ZWA

31、java反射的作用於原理

1、定義:

反射機制是在運行時,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意個對象,都能夠調用它的任意一個方法。在java中,只要給定類的名字,就可以通過反射機制來獲得類的所有信息。

這種動態獲取的信息以及動態調用對象的方法的功能稱爲Java語言的反射機制。

2、哪裏會用到反射機制?

jdbc就是典型的反射

Class.forName('com.mysql.jdbc.Driver.class');//加載MySQL的驅動類

這就是反射。如hibernate,struts等框架使用反射實現的。

3、反射的實現方式:

第一步:獲取Class對象,有4中方法:
1)Class.forName(“類的路徑”);
2)類名.class
3)對象名.getClass()
4)基本類型的包裝類,可以調用包裝類的Type屬性來獲得該包裝類的Class對象

4、實現Java反射的類:

1)Class:表示正在運行的Java應用程序中的類和接口
注意: 所有獲取對象的信息都需要Class類來實現。
2)Field:提供有關類和接口的屬性信息,以及對它的動態訪問權限。
3)Constructor:提供關於類的單個構造方法的信息以及它的訪問權限
4)Method:提供類或接口中某個方法的信息

5、反射機制的優缺點:

優點:
1)能夠運行時動態獲取類的實例,提高靈活性;
2)與動態編譯結合
缺點:
1)使用反射性能較低,需要解析字節碼,將內存中的對象進行解析。
解決方案:
1、通過setAccessible(true)關閉JDK的安全檢查來提升反射速度;
2、多次創建一個類的實例時,有緩存會快很多
3、ReflectASM工具類,通過字節碼生成的方式加快反射速度
2)相對不安全,破壞了封裝性(因爲通過反射可以獲得私有方法和屬性)

32、說說List,Set,Map三者的區別?

  • List(對付順序的好幫手): List接口存儲一組不唯一(可以有多個元素引用相同的對象),有序的對象
  • Set(注重獨一無二的性質): 不允許重複的集合。不會有多個元素引用相同的對象。
  • Map(用Key來搜索的專家): 使用鍵值對存儲。Map會維護與Key有關聯的值。兩個Key可以引用相同的對象,但Key不能重複,典型的Key是String類型,但也可以是任何對象。

JVM篇

1、知識點彙總

JVM是Java運行基礎,面試時一定會遇到JVM的有關問題,內容相對集中,但對只是深度要求較高.

在這裏插入圖片描述

其中內存模型,類加載機制,GC是重點方面.性能調優部分更偏向應用,重點突出實踐能力.編譯器優化和執行模式部分偏向於理論基礎,重點掌握知識點.

需瞭解
內存模型各部分作用,保存哪些數據.

類加載雙親委派加載機制,常用加載器分別加載哪種類型的類.

GC分代回收的思想和依據以及不同垃圾回收算法的回收思路和適合場景.

性能調優常有JVM優化參數作用,參數調優的依據,常用的JVM分析工具能分析哪些問題以及使用方法.

執行模式解釋/編譯/混合模式的優缺點,Java7提供的分層編譯技術,JIT即時編譯技術,OSR棧上替換,C1/C2編譯器針對的場景,C2針對的是server模式,優化更激進.新技術方面Java10的graal編譯器

編譯器優化javac的編譯過程,ast抽象語法樹,編譯器優化和運行器優化.

2、知識點詳解:

1、JVM內存模型:

線程獨佔:棧,本地方法棧,程序計數器
線程共享:堆,方法區

2、棧:

又稱方法棧,線程私有的,線程執行方法是都會創建一個棧陣,用來存儲局部變量表,操作棧,動態鏈接,方法出口等信息.調用方法時執行入棧,方法返回式執行出棧.

3、本地方法棧

與棧類似,也是用來保存執行方法的信息.執行Java方法是使用棧,執行Native方法時使用本地方法棧.

4、程序計數器

保存着當前線程執行的字節碼位置,每個線程工作時都有獨立的計數器,只爲執行Java方法服務,執行Native方法時,程序計數器爲空.

5、堆

JVM內存管理最大的一塊,對被線程共享,目的是存放對象的實例,幾乎所欲的對象實例都會放在這裏,當堆沒有可用空間時,會拋出OOM異常.根據對象的存活週期不同,JVM把對象進行分代管理,由垃圾回收器進行垃圾的回收管理

6、方法區:

又稱非堆區,用於存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器優化後的代碼等數據.1.7的永久代和1.8的元空間都是方法區的一種實現

7、JVM 內存可見性

在這裏插入圖片描述

JMM是定義程序中變量的訪問規則,線程對於變量的操作只能在自己的工作內存中進行,而不能直接對主內存操作.由於指令重排序,讀寫的順序會被打亂,因此JMM需要提供原子性,可見性,有序性保證.

在這裏插入圖片描述

3、類加載與卸載

加載過程

img

其中驗證,準備,解析合稱鏈接

加載通過類的完全限定名,查找此類字節碼文件,利用字節碼文件創建Class對象.

驗證確保Class文件符合當前虛擬機的要求,不會危害到虛擬機自身安全.

準備進行內存分配,爲static修飾的類變量分配內存,並設置初始值(0或null).不包含final修飾的靜態變量,因爲final變量在編譯時分配.

解析將常量池中的符號引用替換爲直接引用的過程.直接引用爲直接指向目標的指針或者相對偏移量等.

初始化主要完成靜態塊執行以及靜態變量的賦值.先初始化父類,再初始化當前類.只有對類主動使用時纔會初始化.

觸發條件包括,創建類的實例時,訪問類的靜態方法或靜態變量的時候,使用Class.forName反射類的時候,或者某個子類初始化的時候.

Java自帶的加載器加載的類,在虛擬機的生命週期中是不會被卸載的,只有用戶自定義的加載器加載的類纔可以被卸.

1、加載機制-雙親委派模式

在這裏插入圖片描述

雙親委派模式,即加載器加載類時先把請求委託給自己的父類加載器執行,直到頂層的啓動類加載器.父類加載器能夠完成加載則成功返回,不能則子類加載器才自己嘗試加載.*

優點:

  1. 避免類的重複加載
  2. 避免Java的核心API被篡改

2、分代回收

分代回收基於兩個事實:大部分對象很快就不使用了,還有一部分不會立即無用,但也不會持續很長時間.
在這裏插入圖片描述

年輕代->標記-複製
老年代->標記-清除

3、回收算法

a、G1算法

1.9後默認的垃圾回收算法,特點保持高回收率的同時減少停頓.採用每次只清理一部分,而不是清理全部的增量式清理,以保證停頓時間不會過長

其取消了年輕代與老年代的物理劃分,但仍屬於分代收集器,算法將堆分爲若干個邏輯區域(region),一部分用作年輕代,一部分用作老年代,還有用來存儲巨型對象的分區.

同CMS相同,會遍歷所有對象,標記引用情況,清除對象後會對區域進行復制移動,以整合碎片空間.

年輕代回收:
並行複製採用複製算法,並行收集,會StopTheWorld.

老年代回收:
會對年輕代一併回收

初始標記完成堆root對象的標記,會StopTheWorld.
併發標記 GC線程和應用線程併發執行.
最終標記完成三色標記週期,會StopTheWorld.
複製/清楚會優先對可回收空間加大的區域進行回收

b、ZGC算法

前面提供的高效垃圾回收算法,針對大堆內存設計,可以處理TB級別的堆,可以做到10ms以下的回收停頓時間.

在這裏插入圖片描述

  • 着色指針
  • 讀屏障
  • 併發處理
  • 基於region
  • 內存壓縮(整理)

roots標記:標記root對象,會StopTheWorld.
併發標記:利用讀屏障與應用線程一起運行標記,可能會發生StopTheWorld.
清除會清理標記爲不可用的對象.
roots重定位:是對存活的對象進行移動,以騰出大塊內存空間,減少碎片產生.重定位最開始會StopTheWorld,卻決於重定位集與對象總活動集的比例.
併發重定位與併發標記類似.

4、簡述一下JVM的內存模型

1.JVM內存模型簡介

JVM定義了不同運行時數據區,他們是用來執行應用程序的。某些區域隨着JVM啓動及銷燬,另外一些區域的數據是線程性獨立的,隨着線程創建和銷燬。jvm內存模型總體架構圖如下:(摘自oracle官方網站

img

JVM在執行Java程序時,會把它管理的內存劃分爲若干個的區域,每個區域都有自己的用途和創建銷燬時間。如下圖所示,可以分爲兩大部分,線程私有區和共享區。下圖是根據自己理解畫的一個JVM內存模型架構圖:

img JVM內存分爲線程私有區和線程共享區

線程私有區

1、程序計數器

當同時進行的線程數超過CPU數或其內核數時,就要通過時間片輪詢分派CPU的時間資源,不免發生線程切換。這時,每個線程就需要一個屬於自己的計數器來記錄下一條要運行的指令。如果執行的是JAVA方法,計數器記錄正在執行的java字節碼地址,如果執行的是native方法,則計數器爲空。

2、虛擬機棧

線程私有的,與線程在同一時間創建。管理JAVA方法執行的內存模型。每個方法執行時都會創建一個楨棧來存儲方法的的變量表、操作數棧、動態鏈接方法、返回值、返回地址等信息。棧的大小決定了方法調用的可達深度(遞歸多少層次,或嵌套調用多少層其他方法,-Xss參數可以設置虛擬機棧大小)。棧的大小可以是固定的,或者是動態擴展的。如果請求的棧深度大於最大可用深度,則拋出stackOverflowError;如果棧是可動態擴展的,但沒有內存空間支持擴展,則拋出OutofMemoryError。
使用jclasslib工具可以查看class類文件的結構。下圖爲棧幀結構圖:

img

3、本地方法棧

與虛擬機棧作用相似。但它不是爲Java方法服務的,而是本地方法(C語言)。由於規範對這塊沒有強制要求,不同虛擬機實現方法不同。

線程共享區

1、方法區

線程共享的,用於存放被虛擬機加載的類的元數據信息,如常量、靜態變量和即時編譯器編譯後的代碼。若要分代,算是永久代(老年代),以前類大多“static”的,很少被卸載或收集,現回收廢棄常量和無用的類。其中運行時常量池存放編譯生成的各種常量。(如果hotspot虛擬機確定一個類的定義信息不會被使用,也會將其回收。回收的基本條件至少有:所有該類的實例被回收,而且裝載該類的ClassLoader被回收)

2、堆

存放對象實例和數組,是垃圾回收的主要區域,分爲新生代和老年代。剛創建的對象在新生代的Eden區中,經過GC後進入新生代的S0區中,再經過GC進入新生代的S1區中,15次GC後仍存在就進入老年代。這是按照一種回收機制進行劃分的,不是固定的。若堆的空間不夠實例分配,則OutOfMemoryError。

img

Young Generation      即圖中的Eden + From Space(s0) + To Space(s1)
Eden                        存放新生的對象
Survivor Space          有兩個,存放每次垃圾回收後存活的對象(s0+s1)
Old Generation          Tenured Generation 即圖中的Old Space
                        主要存放應用程序中生命週期長的存活對象

5、堆和棧的區別

棧是運行時單位,代表着邏輯,內含基本數據類型和堆中對象引用,所在區域連續,沒有碎片;堆是存儲單位,代表着數據,可被多個棧共享(包括成員中基本數據類型、引用和引用對象),所在區域不連續,會有碎片。

1、功能不同

棧內存用來存儲局部變量和方法調用,而堆內存用來存儲Java中的對象。無論是成員變量,局部變量,還是類變量,它們指向的對象都存儲在堆內存中。

2、共享性不同

棧內存是線程私有的。
堆內存是所有線程共有的。

3、異常錯誤不同

如果棧內存或者堆內存不足都會拋出異常。
棧空間不足:java.lang.StackOverFlowError。
堆空間不足:java.lang.OutOfMemoryError。

4、空間大小

棧的空間大小遠遠小於堆的。

6、 什麼時候會觸發FullGC

除直接調用System.gc外,觸發Full GC執行的情況有如下四種。
1. 舊生代空間不足
舊生代空間只有在新生代對象轉入及創建爲大對象、大數組時纔會出現不足的現象,當執行Full GC後空間仍然不足,則拋出如下錯誤:
java.lang.OutOfMemoryError: Java heap space
爲避免以上兩種狀況引起的FullGC,調優時應儘量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要創建過大的對象及數組。

2. Permanet Generation空間滿
PermanetGeneration中存放的爲一些class的信息等,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被佔滿,在未配置爲採用CMS GC的情況下會執行Full GC。如果經過Full GC仍然回收不了,那麼JVM會拋出如下錯誤信息:
java.lang.OutOfMemoryError: PermGen space
爲避免Perm Gen佔滿造成Full GC現象,可採用的方法爲增大Perm Gen空間或轉爲使用CMS GC。

3. CMS GC時出現promotion failed和concurrent mode failure
對於採用CMS進行舊生代GC的程序而言,尤其要注意GC日誌中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現時可能會觸發Full GC。
promotionfailed是在進行Minor GC時,survivor space放不下、對象只能放入舊生代,而此時舊生代也放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有對象要放入舊生代,而此時舊生代空間不足造成的。
應對措施爲:增大survivorspace、舊生代空間或調低觸發併發GC的比率,但在JDK 5.0+、6.0+的版本中有可能會由於JDK的bug29導致CMS在remark完畢後很久才觸發sweeping動作。對於這種狀況,可通過設置-XX:CMSMaxAbortablePrecleanTime=5(單位爲ms)來避免。

4. 統計得到的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間
這是一個較爲複雜的觸發情況,Hotspot爲了避免由於新生代對象晉升到舊生代導致舊生代空間不足的現象,在進行Minor GC時,做了一個判斷,如果之前統計所得到的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間,那麼就直接觸發Full GC。
例如程序第一次觸發MinorGC後,有6MB的對象晉升到舊生代,那麼當下一次Minor GC發生時,首先檢查舊生代的剩餘空間是否大於6MB,如果小於6MB,則執行Full GC。
當新生代採用PSGC時,方式稍有不同,PS GC是在Minor GC後也會檢查,例如上面的例子中第一次Minor GC後,PS GC會檢查此時舊生代的剩餘空間是否大於6MB,如小於,則觸發對舊生代的回收。
除了以上4種狀況外,對於使用RMI來進行RPC或管理的Sun JDK應用而言,默認情況下會一小時執行一次Full GC。可通過在啓動時通過- java-Dsun.rmi.dgc.client.gcInterval=3600000來設置Full GC執行的間隔時間或通過-XX:+ DisableExplicitGC來禁止RMI調用System.gc。

7、什麼是Java虛擬機?爲什麼Java被稱作是“平臺無關的編程語言”?

Java虛擬機是一個可以執行Java字節碼的虛擬機進程。Java源文件被編譯成能被Java虛擬機執行的字節碼文件。 Java被設計成允許應用程序可以運行在任意的平臺,而不需要程序員爲每一個平臺單獨重寫或者是重新編譯。Java虛擬機讓這個變爲可能,因爲它知道底層硬件平臺的指令長度和其他特性。

8、Java內存結構

《24個Jvm面試題總結及答案》

方法區和對是所有線程共享的內存區域;而java棧、本地方法棧和程序員計數器是運行是線程私有的內存區域。

  • Java堆(Heap),是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存。
  • 方法區(Method Area),方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
  • 程序計數器(Program Counter Register),程序計數器(Program Counter Register)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。
  • JVM棧(JVM Stacks),與程序計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
  • 本地方法棧(Native Method Stacks),本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。

9、對象分配規則

  • 對象優先分配在Eden區,如果Eden區沒有足夠的空間時,虛擬機執行一次Minor GC。
  • 大對象直接進入老年代(大對象是指需要大量連續內存空間的對象)。這樣做的目的是避免在Eden區和兩個Survivor區之間發生大量的內存拷貝(新生代採用複製算法收集內存)。
  • 長期存活的對象進入老年代。虛擬機爲每個對象定義了一個年齡計數器,如果對象經過了1次Minor GC那麼對象會進入Survivor區,之後每經過一次Minor GC那麼對象的年齡加1,知道達到閥值對象進入老年區。
  • 動態判斷對象的年齡。如果Survivor區中相同年齡的所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象可以直接進入老年代。
  • 空間分配擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大於老年區的剩餘值大小則進行一次Full GC,如果小於檢查HandlePromotionFailure設置,如果true則只進行Monitor GC,如果false則進行Full GC。

10、描述一下JVM加載class文件的原理機制?

JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。 由於Java的跨平臺性,經過編譯的Java源程序並不是一個可執行程序,而是一個或多個類文件。當Java程序需要使用某個類時,JVM會確保這個類已經被加載、連接(驗證、準備和解析)和初始化。類的加載是指把類的.class文件中的數據讀入到內存中,通常是創建一個字節數組讀入.class文件,然後產生與所加載類對應的Class對象。加載完成後,Class對象還不完整,所以此時的類還不可用。當類被加載後就進入連接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。最後JVM對類進行初始化,包括:1)如果類存在直接的父類並且這個類還沒有被初始化,那麼就先初始化父類;2)如果類中存在初始化語句,就依次執行這些初始化語句。 類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。從Java 2(JDK 1.2)開始,類加載過程採取了父親委託機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。下面是關於幾個類加載器的說明:

  • Bootstrap:一般用本地代碼實現,負責加載JVM基礎核心類庫(rt.jar);
  • Extension:從java.ext.dirs系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;
  • System:又叫應用類加載器,其父類是Extension。它是應用最廣泛的類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。

11、Java對象創建過程

1.JVM遇到一條新建對象的指令時首先去檢查這個指令的參數是否能在常量池中定義到一個類的符號引用。然後加載這個類(類加載過程在後邊講)

2.爲對象分配內存。一種辦法“指針碰撞”、一種辦法“空閒列表”,最終常用的辦法“本地線程緩衝分配(TLAB)”

3.將除對象頭外的對象內存空間初始化爲0

4.對對象頭進行必要設置

12、類的生命週期

類的生命週期包括這幾個部分,加載、連接、初始化、使用和卸載,其中前三部是類的加載的過程,如下圖;

《24個Jvm面試題總結及答案》

  • 加載,查找並加載類的二進制數據,在Java堆中也創建一個java.lang.Class類的對象
  • 連接,連接又包含三塊內容:驗證、準備、初始化。 1)驗證,文件格式、元數據、字節碼、符號引用驗證; 2)準備,爲類的靜態變量分配內存,並將其初始化爲默認值; 3)解析,把類中的符號引用轉換爲直接引用
  • 初始化,爲類的靜態變量賦予正確的初始值
  • 使用,new出對象程序中使用
  • 卸載,執行垃圾回收

13、簡述Java的對象結構

Java對象由三個部分組成:對象頭、實例數據、對齊填充。

對象頭由兩部分組成,第一部分存儲對象自身的運行時數據:哈希碼、GC分代年齡、鎖標識狀態、線程持有的鎖、偏向線程ID(一般佔32/64 bit)。第二部分是指針類型,指向對象的類元數據類型(即對象代表哪個類)。如果是數組對象,則對象頭中還有一部分用來記錄數組長度。

實例數據用來存儲對象真正的有效信息(包括父類繼承下來的和自己定義的)

對齊填充:JVM要求對象起始地址必須是8字節的整數倍(8字節對齊)

14、如何判斷對象可以被回收?

判斷對象是否存活一般有兩種方式:

  • 引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數爲0時可以回收。此方法簡單,無法解決對象相互循環引用的問題。
  • 可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,不可達對象。

15、JVM的永久代中會發生垃圾回收麼?

垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是爲什麼正確的永久代大小對避免Full GC是非常重要的原因。請參考下Java8:從永久代到元數據區 (注:Java8中已經移除了永久代,新加了一個叫做元數據區的native內存區)

16、垃圾收集算法

GC最基礎的算法有三種: 標記 -清除算法、複製算法、標記-壓縮算法,我們常用的垃圾回收器一般都採用分代收集算法。

  • 標記 -清除算法,“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分爲“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收掉所有被標記的對象。
  • 複製算法,“複製”(Copying)的收集算法,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。
  • 標記-壓縮算法,標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存
  • 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。

17、調優命令有哪些?

Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo

  • jps,JVM Process Status Tool,顯示指定系統內所有的HotSpot虛擬機進程。
  • jstat,JVM statistics Monitoring是用於監視虛擬機運行時狀態信息的命令,它可以顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。
  • jmap,JVM Memory Map命令用於生成heap dump文件
  • jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果後,可以在瀏覽器中查看
  • jstack,用於生成java虛擬機當前時刻的線程快照。
  • jinfo,JVM Configuration info 這個命令作用是實時查看和調整虛擬機運行參數。

18、調優工具

常用調優工具分爲兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。

  • jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中內存,線程和類等的監控
  • jvisualvm,jdk自帶全能工具,可以分析內存快照、線程快照;監控內存變化、GC變化等。
  • MAT,Memory Analyzer Tool,一個基於Eclipse的內存分析工具,是一個快速、功能豐富的Java heap分析工具,它可以幫助我們查找內存泄漏和減少內存消耗
  • GChisto,一款專業分析gc日誌的工具

19、Minor GC與Full GC分別在什麼時候發生?

新生代內存不夠用時候發生MGC也叫YGC,JVM內存不夠的時候發生FGC

20、你知道哪些JVM性能調優

  • 設定堆內存大小

-Xmx:堆內存最大限制。

  • 設定新生代大小。 新生代不宜太小,否則會有大量對象涌入老年代

-XX:NewSize:新生代大小

-XX:NewRatio 新生代和老生代佔比

-XX:SurvivorRatio:伊甸園空間和倖存者空間的佔比

  • 設定垃圾回收器 年輕代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC

多線程&併發篇

1、Java中實現多線程有幾種方法

繼承Thread類;
實現Runnable接口;
實現Callable接口通過FutureTask包裝器來創建Thread線程;
使用ExecutorService、Callable、Future實現有返回結果的多線程(也就是使用了ExecutorService來管理前面的三種方式)。

2、如何停止一個正在運行的線程

1、使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。

2、使用stop方法強行終止,但是不推薦這個方法,因爲stop和suspend及resume一樣都是過期作廢的方法。

3、使用interrupt方法中斷線程。

class MyThread extends Thread {
    volatile boolean stop = false;

    public void run() {
        while (!stop) {
            System.out.println(getName() + " is running");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("week up from blcok...");
                stop = true; // 在異常處理代碼中修改共享變量的狀態
            }
        }
        System.out.println(getName() + " is exiting...");
    }
}

class InterruptThreadDemo3 {
    public static void main(String[] args) throws InterruptedException {
        MyThread m1 = new MyThread();
        System.out.println("Starting thread...");
        m1.start();
        Thread.sleep(3000);
        System.out.println("Interrupt thread...: " + m1.getName());
        m1.stop = true; // 設置共享變量爲true
        m1.interrupt(); // 阻塞時退出阻塞狀態
        Thread.sleep(3000); // 主線程休眠3秒以便觀察線程m1的中斷情況
        System.out.println("Stopping application...");
    }
}

3、notify()和notifyAll()有什麼區別?

notify可能會導致死鎖,而notifyAll則不會

任何時候只有一個線程可以獲得鎖,也就是說只有一個線程可以運行synchronized 中的代碼

使用notifyall,可以喚醒
所有處於wait狀態的線程,使其重新進入鎖的爭奪隊列中,而notify只能喚醒一個。

wait() 應配合while循環使用,不應使用if,務必在wait()調用前後都檢查條件,如果不滿足,必須調用notify()喚醒另外的線程來處理,自己繼續wait()直至條件滿足再往下執行。

notify() 是對notifyAll()的一個優化,但它有很精確的應用場景,並且要求正確使用。不然可能導致死鎖。正確的場景應該是 WaitSet中等待的是相同的條件,喚醒任一個都能正確處理接下來的事項,如果喚醒的線程無法正確處理,務必確保繼續notify()下一個線程,並且自身需要重新回到WaitSet中.

4、sleep()和wait() 有什麼區別?

對於sleep()方法,我們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。

sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。在調用sleep()方法的過程中,線程不會釋放對象鎖。

當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備,獲取對象鎖進入運行狀態。

5、volatile 是什麼?可以保證有序性嗎?

一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之後,那麼就具備了兩層語義:

1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的,volatile關鍵字會強制將修改的值立即寫入主存。

2)禁止進行指令重排序。

volatile 不是原子性操作

什麼叫保證部分有序性?

當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;

x = 2;        //語句1
y = 0;        //語句2
flag = true;  //語句3
x = 4;         //語句4
y = -1;       //語句5

由於flag變量爲volatile變量,那麼在進行指令重排序的過程的時候,不會將語句3放到語句1、語句2前面,也不會講語句3放到語句4、語句5後面。但是要注意語句1和語句2的順序、語句4和語句5的順序是不作任何保證的。

使用 Volatile 一般用於 狀態標記量 和 單例模式的雙檢鎖

6、Thread 類中的start() 和 run() 方法有什麼區別?

start()方法被用來啓動新創建的線程,而且start()內部調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啓動,start()方法纔會啓動新線程。

7、爲什麼wait, notify 和 notifyAll這些方法不在thread類裏面?

明顯的原因是JAVA提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。如果線程需要等待某些鎖那麼調用對象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個鎖就不明顯了。簡單的說,由於wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因爲鎖屬於對象。

8、爲什麼wait和notify方法要在同步塊中調用?

  1. 只有在調用線程擁有某個對象的獨佔鎖時,才能夠調用該對象的wait(),notify()和notifyAll()方法。
  2. 如果你不這麼做,你的代碼會拋出IllegalMonitorStateException異常。
  3. 還有一個原因是爲了避免wait和notify之間產生競態條件。

wait()方法強制當前線程釋放對象鎖。這意味着在調用某對象的wait()方法之前,當前線程必須已經獲得該對象的鎖。因此,線程必須在某個對象的同步方法或同步代碼塊中才能調用該對象的wait()方法。

在調用對象的notify()和notifyAll()方法之前,調用線程必須已經得到該對象的鎖。因此,必須在某個對象的同步方法或同步代碼塊中才能調用該對象的notify()或notifyAll()方法。

調用wait()方法的原因通常是,調用線程希望某個特殊的狀態(或變量)被設置之後再繼續執行。調用notify()或notifyAll()方法的原因通常是,調用線程希望告訴其他等待中的線程:“特殊狀態已經被設置”。這個狀態作爲線程間通信的通道,它必須是一個可變的共享狀態(或變量)。

9、Java中interrupted 和 isInterruptedd方法的區別?

interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除而後者不會。Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識爲true。當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態清零。無論如何,一個線程的中斷狀態有有可能被其它線程調用中斷來改變。

10、Java中synchronized 和 ReentrantLock 有什麼不同?

相似點:

這兩種同步方式有很多相似之處,它們都是加鎖方式同步,而且都是阻塞式的同步,也就是說當如果一個線程獲得了對象鎖,進入了同步塊,其他訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進行線程阻塞和喚醒的代價是比較高的.

區別:

這兩種方式最大區別就是對於Synchronized來說,它是java語言的關鍵字,是原生語法層面的互斥,需要jvm實現。而ReentrantLock它是JDK 1.5之後提供的API層面的互斥鎖,需要lock()和unlock()方法配合try/finally語句塊來完成。

Synchronized進過編譯,會在同步塊的前後分別形成monitorenter和monitorexit這個兩個字節碼指令。在執行monitorenter指令時,首先要嘗試獲取對象鎖。如果這個對象沒被鎖定,或者當前線程已經擁有了那個對象鎖,把鎖的計算器加1,相應的,在執行monitorexit指令時會將鎖計算器就減1,當計算器爲0時,鎖就被釋放了。如果獲取對象鎖失敗,那當前線程就要阻塞,直到對象鎖被另一個線程釋放爲止。

由於ReentrantLock是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高級功能,主要有以下3項:

1.等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程可以選擇放棄等待,這相當於Synchronized來說可以避免出現死鎖的情況。

2.公平鎖,多個線程等待同一個鎖時,必須按照申請鎖的時間順序獲得鎖,Synchronized鎖非公平鎖,ReentrantLock默認的構造函數是創建的非公平鎖,可以通過參數true設爲公平鎖,但公平鎖表現的性能不是很好。

3.鎖綁定多個條件,一個ReentrantLock對象可以同時綁定對個對象。

11、有三個線程T1,T2,T3,如何保證順序執行?

在多線程中有多種方法讓線程按特定順序執行,你可以用線程類的join()方法在一個線程中啓動另一個線程,另外一個線程完成該線程繼續執行。爲了確保三個線程的順序你應該先啓動最後一個(T3調用T2,T2調用T1),這樣T1就會先完成而T3最後完成。

實際上先啓動三個線程中哪一個都行,
因爲在每個線程的run方法中用join方法限定了三個線程的執行順序。

public class JoinTest2 {
 
    // 1.現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行
 
    public static void main(String[] args) {
 
        final Thread t1 = new Thread(new Runnable() {
 
            @Override
            public void run() {
                System.out.println("t1");
            }
        });
        final Thread t2 = new Thread(new Runnable() {
 
            @Override
            public void run() {
                try {
                    // 引用t1線程,等待t1線程執行完
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2");
            }
        });
        Thread t3 = new Thread(new Runnable() {
 
            @Override
            public void run() {
                try {
                    // 引用t2線程,等待t2線程執行完
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3");
            }
        });
        t3.start();//這裏三個線程的啓動順序可以任意,大家可以試下!
        t2.start();
        t1.start();
    }
}

12、SynchronizedMap和ConcurrentHashMap有什麼區別?

SynchronizedMap()和Hashtable一樣,實現上在調用map所有方法時,都對整個map進行同步。而ConcurrentHashMap的實現卻更加精細,它對map中的所有桶加了鎖。所以,只要有一個線程訪問map,其他線程就無法進入map,而如果一個線程在訪問ConcurrentHashMap某個桶時,其他線程,仍然可以對map執行某些操作。

所以,ConcurrentHashMap在性能以及安全性方面,明顯比Collections.synchronizedMap()更加有優勢。同時,同步操作精確控制到桶,這樣,即使在遍歷map時,如果其他線程試圖對map進行數據修改,也不會拋出ConcurrentModificationException。

13、什麼是線程安全

線程安全就是說多線程訪問同一代碼,不會產生不確定的結果。

在多線程環境中,當各線程不共享數據的時候,即都是私有(private)成員,那麼一定是線程安全的。但這種情況並不多見,在多數情況下需要共享數據,這時就需要進行適當的同步控制了。

線程安全一般都涉及到synchronized, 就是一段代碼同時只能有一個線程來操作 不然中間過程可能會產生不可預製的結果。

如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

14、Thread類中的yield方法有什麼作用?

Yield方法可以暫停當前正在執行的線程對象,讓其它有相同優先級的線程執行。它是一個靜態方法而且只保證當前線程放棄CPU佔用而不能保證使其它線程一定能佔用CPU,執行yield()的線程有可能在進入到暫停狀態後馬上又被執行。

15、Java線程池中submit() 和 execute()方法有什麼區別?

兩個方法都可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。

16、說一說自己對於 synchronized 關鍵字的瞭解

synchronized關鍵字解決的是多個線程之間訪問資源的同步性,synchronized關鍵字可以保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行。
另外,在 Java 早期版本中,synchronized屬於重量級鎖,效率低下,因爲監視器鎖(monitor)是依賴於底層的操作系統的 Mutex Lock 來實現的,Java 的線程是映射到操作系統的原生線程之上的。如果要掛起或者喚醒一個線程,都需要操作系統幫忙完成,而操作系統實現線程之間的切換時需要從用戶態轉換到內核態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高,這也是爲什麼早期的 synchronized 效率低的原因。慶幸的是在 Java 6 之後 Java 官方對從 JVM 層面對synchronized 較大優化,所以現在的 synchronized 鎖效率也優化得很不錯了。JDK1.6對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。

17、說說自己是怎麼使用 synchronized 關鍵字,在項目中用到了嗎synchronized關鍵字最主要的三種使用方式:

修飾實例方法: 作用於當前對象實例加鎖,進入同步代碼前要獲得當前對象實例的鎖
修飾靜態方法: 也就是給當前類加鎖,會作用於類的所有對象實例,因爲靜態成員不屬於任何一個實例對象,是類成員( static 表明這是該類的一個靜態資源,不管new了多少個對象,只有一份)。所以如果一個線程A調用一個實例對象的非靜態 synchronized 方法,而線程B需要調用這個實例對象所屬類的靜態 synchronized 方法,是允許的,不會發生互斥現象,因爲訪問靜態 synchronized 方法佔用的鎖是當前類的鎖,而訪問非靜態 synchronized 方法佔用的鎖是當前實例對象鎖。
修飾代碼塊: 指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。
總結: synchronized 關鍵字加到 static 靜態方法和 synchronized(class)代碼塊上都是是給 Class 類上鎖。synchronized 關鍵字加到實例方法上是給對象實例上鎖。儘量不要使用 synchronized(String a) 因爲JVM中,字符串常量池具有緩存功能!

18、什麼是線程安全?Vector是一個線程安全類嗎?

如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量 的值也和預期的是一樣的,就是線程安全的。一個線程安全的計數器類的同一個實例對象在被多個線程使用的情況下也不會出現計算失誤。很顯然你可以將集合類分 成兩組,線程安全和非線程安全的。Vector 是用同步方法來實現線程安全的, 而和它相似的ArrayList不是線程安全的。

19、 volatile關鍵字的作用?

一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之後,那麼就具備了兩層語義:

  • 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。

  • 禁止進行指令重排序。

  • volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。

  • volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的。

  • volatile僅能實現變量的修改可見性,並不能保證原子性;synchronized則可以保證變量的修改可見性和原子性。

  • volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。

volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化。

20、常用的線程池有哪些?

  • newSingleThreadExecutor:創建一個單線程的線程池,此線程池保證所有任務的執行順序按照任務的提交順序執行。
  • newFixedThreadPool:創建固定大小的線程池,每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。
  • newCachedThreadPool:創建一個可緩存的線程池,此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
  • newScheduledThreadPool:創建一個大小無限的線程池,此線程池支持定時以及週期性執行任務的需求。
  • newSingleThreadExecutor:創建一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。

21、簡述一下你對線程池的理解

(如果問到了這樣的問題,可以展開的說一下線程池如何用、線程池的好處、線程池的啓動策略)合理利用線程池能夠帶來三個好處。

第一:降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。

第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。

第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

22、Java程序是如何執行的

我們日常的工作中都使用開發工具(IntelliJ IDEA 或 Eclipse 等)可以很方便的調試程序,或者是通過打包工具把項目打包成 jar 包或者 war 包,放入 Tomcat 等 Web 容器中就可以正常運行了,但你有沒有想過 Java 程序內部是如何執行的?其實不論是在開發工具中運行還是在 Tomcat 中運行,Java 程序的執行流程基本都是相同的,它的執行流程如下:

  • 先把 Java 代碼編譯成字節碼,也就是把 .java 類型的文件編譯成 .class 類型的文件。這個過程的大致執行流程:Java 源代碼 -> 詞法分析器 -> 語法分析器 -> 語義分析器 -> 字符碼生成器 -> 最終生成字節碼,其中任何一個節點執行失敗就會造成編譯失敗;
  • 把 class 文件放置到 Java 虛擬機,這個虛擬機通常指的是 Oracle 官方自帶的 Hotspot JVM;
  • Java 虛擬機使用類加載器(Class Loader)裝載 class 文件;
  • 類加載完成之後,會進行字節碼效驗,字節碼效驗通過之後 JVM 解釋器會把字節碼翻譯成機器碼交由操作系統執行。但不是所有代碼都是解釋執行的,JVM 對此做了優化,比如,以 Hotspot 虛擬機來說,它本身提供了 JIT(Just In Time)也就是我們通常所說的動態編譯器,它能夠在運行時將熱點代碼編譯爲機器碼,這個時候字節碼就變成了編譯執行。Java 程序執行流程圖如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gSxSVD2E-1587954092029)(https://pics7.baidu.com/feed/9922720e0cf3d7caf8b227aecb7be60c6a63a90f.png?token=5bc0a1783459586fb4e21b13579950c9&s=B8A05D32150F65491865D0420300F0F1)]

Spring篇

推薦閱讀: 極客學院Spring Wiki

1、 Spring的IOC和AOP機制?

我們是在使用Spring框架的過程中,其實就是爲了使用IOC,依賴注入,和AOP,面向切面編程,這兩個是Spring的靈魂。

主要用到的設計模式有工廠模式和代理模式。

IOC就是典型的工廠模式,通過sessionfactory去注入實例。

AOP就是典型的代理模式的體現。

代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。

spring的IoC容器是spring的核心,spring AOP是spring框架的重要組成部分。

在傳統的程序設計中,當調用者需要被調用者的協助時,通常由調用者來創建被調用者的實例。但在spring裏創建被調用者的工作不再由調用者來完成,因此控制反轉(IoC);創建被調用者實例的工作通常由spring容器來完成,然後注入調用者,因此也被稱爲依賴注入(DI),依賴注入和控制反轉是同一個概念。

面向方面編程(AOP)是以另一個角度來考慮程序結構,通過分析程序結構的關注點來完善面向對象編程(OOP)。OOP將應用程序分解成各個層次的對象,而AOP將程序分解成多個切面。spring AOP 只實現了方法級別的連接點,在J2EE應用中,AOP攔截到方法級別的操作就已經足夠。在spring中,未來使IoC方便地使用健壯、靈活的企業服務,需要利用spring AOP實現爲IoC和企業服務之間建立聯繫。

IOC:控制反轉也叫依賴注入。利用了工廠模式
將對象交給容器管理,你只需要在spring配置文件總配置相應的bean,以及設置相關的屬性,讓spring容器來生成類的實例對象以及管理對象。在spring容器啓動的時候,spring會把你在配置文件中配置的bean都初始化好,然後在你需要調用的時候,就把它已經初始化好的那些bean分配給你需要調用這些bean的類(假設這個類名是A),分配的方法就是調用A的setter方法來注入,而不需要你在A裏面new這些bean了。
注意:面試的時候,如果有條件,畫圖,這樣更加顯得你懂了.

spring ioc初始化流程

Spring IOC的初始化過程

AOP:面向切面編程。(Aspect-Oriented Programming)
AOP可以說是對OOP的補充和完善。OOP引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行爲的一個集合。當我們需要爲分散的對象引入公共行爲的時候,OOP則顯得無能爲力。也就是說,OOP允許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌代碼往往水平地散佈在所有對象層次中,而與它所散佈到的對象的核心功能毫無關係。在OOP設計中,它導致了大量代碼的重複,而不利於各個模塊的重用。
將程序中的交叉業務邏輯(比如安全,日誌,事務等),封裝成一個切面,然後注入到目標對象(具體業務邏輯)中去。

實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼.

簡單點解釋,比方說你想在你的biz層所有類中都加上一個打印‘你好’的功能,這時就可以用aop思想來做.你先寫個類寫個類方法,方法經實現打印‘你好’,然後Ioc這個類 ref=“biz.*”讓每個類都注入即可實現。

2、 Spring中Autowired和Resource關鍵字的區別?

@Resource和@Autowired都是做bean的注入時使用,其實@Resource並不是Spring的註解,它的包是javax.annotation.Resource,需要導入,但是Spring支持該註解的注入。

1、共同點

兩者都可以寫在字段和setter方法上。兩者如果都寫在字段上,那麼就不需要再寫setter方法。

2、不同點

(1)@Autowired

@Autowired爲Spring提供的註解,需要導入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。

public class TestServiceImpl {
    // 下面兩種@Autowired只要使用一種即可
    @Autowired
    private UserDao userDao; // 用於字段上
    
    @Autowired
    public void setUserDao(UserDao userDao) { // 用於屬性的方法上
        this.userDao = userDao;
    }
}

@Autowired註解是按照類型(byType)裝配依賴對象,默認情況下它要求依賴對象必須存在,如果允許null值,可以設置它的required屬性爲false。如果我們想使用按照名稱(byName)來裝配,可以結合@Qualifier註解一起使用。如下:

public class TestServiceImpl {
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao; 
}

(2)@Resource

@Resource默認按照ByName自動注入,由J2EE提供,需要導入包javax.annotation.Resource。@Resource有兩個重要的屬性:name和type,而Spring將@Resource註解的name屬性解析爲bean的名字,而type屬性則解析爲bean的類型。所以,如果使用name屬性,則使用byName的自動注入策略,而使用type屬性時則使用byType自動注入策略。如果既不制定name也不制定type屬性,這時將通過反射機制使用byName自動注入策略。

public class TestServiceImpl {
    // 下面兩種@Resource只要使用一種即可
    @Resource(name="userDao")
    private UserDao userDao; // 用於字段上
    
    @Resource(name="userDao")
    public void setUserDao(UserDao userDao) { // 用於屬性的setter方法上
        this.userDao = userDao;
    }
}

注:最好是將@Resource放在setter方法上,因爲這樣更符合面向對象的思想,通過set、get去操作屬性,而不是直接去操作屬性。

@Resource裝配順序:

①如果同時指定了name和type,則從Spring上下文中找到唯一匹配的bean進行裝配,找不到則拋出異常。

②如果指定了name,則從上下文中查找名稱(id)匹配的bean進行裝配,找不到則拋出異常。

③如果指定了type,則從上下文中找到類似匹配的唯一bean進行裝配,找不到或是找到多個,都會拋出異常。

④如果既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;如果沒有匹配,則回退爲一個原始類型進行匹配,如果匹配則自動裝配。

@Resource的作用相當於@Autowired,只不過@Autowired按照byType自動注入。

3、依賴注入的方式有幾種,各是什麼?

一、構造器注入
將被依賴對象通過構造函數的參數注入給依賴對象,並且在初始化對象的時候注入。

優點:
對象初始化完成後便可獲得可使用的對象。

缺點:
當需要注入的對象很多時,構造器參數列表將會很長;
不夠靈活。若有多種注入方式,每種方式只需注入指定幾個依賴,那麼就需要提供多個重載的構造函數,麻煩。

二、setter方法注入
IoC Service Provider通過調用成員變量提供的setter函數將被依賴對象注入給依賴類。

優點:
靈活。可以選擇性地注入需要的對象。

缺點:
依賴對象初始化完成後由於尚未注入被依賴對象,因此還不能使用。

三、接口注入
依賴類必須要實現指定的接口,然後實現該接口中的一個函數,該函數就是用於依賴注入。該函數的參數就是要注入的對象。

優點
接口注入中,接口的名字、函數的名字都不重要,只要保證函數的參數是要注入的對象類型即可。

缺點:
侵入行太強,不建議使用。

PS:什麼是侵入行?
如果類A要使用別人提供的一個功能,若爲了使用這功能,需要在自己的類中增加額外的代碼,這就是侵入性。

4、講一下什麼是Spring

Spring是一個輕量級的IoC和AOP容器框架。是爲Java應用程序提供基礎性服務的一套框架,目的是用於簡化企業應用程序的開發,它使得開發者只需要關心業務需求。常見的配置方式有三種:基於XML的配置、基於註解的配置、基於Java的配置。

主要由以下幾個模塊組成:

Spring Core:核心類庫,提供IOC服務;

Spring Context:提供框架式的Bean訪問方式,以及企業級功能(JNDI、定時任務等);

Spring AOP:AOP服務;

Spring DAO:對JDBC的抽象,簡化了數據訪問異常的處理;

Spring ORM:對現有的ORM框架的支持;

Spring Web:提供了基本的面向Web的綜合特性,例如多方文件上傳;

Spring MVC:提供面向Web應用的Model-View-Controller實現。

5、Spring MVC流程

工作原理:

img

1、 用戶發送請求至前端控制器DispatcherServlet。

2、 DispatcherServlet收到請求調用HandlerMapping處理器映射器。

3、 處理器映射器找到具體的處理器(可以根據xml配置、註解進行查找),生成處理器對象及處理器攔截器(如果有則生成)一併返回給DispatcherServlet。

4、 DispatcherServlet調用HandlerAdapter處理器適配器。

5、 HandlerAdapter經過適配調用具體的處理器(Controller,也叫後端控制器)。

6、 Controller執行完成返回ModelAndView。

7、 HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。

8、 DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。

9、 ViewReslover解析後返回具體View。

10、DispatcherServlet根據View進行渲染視圖(即將模型數據填充至視圖中)。

11、 DispatcherServlet響應用戶。

組件說明:

以下組件通常使用框架提供實現:

DispatcherServlet:作爲前端控制器,整個流程控制的中心,控制其它組件執行,統一調度,降低組件之間的耦合性,提高每個組件的擴展性。

HandlerMapping:通過擴展處理器映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,註解方式等。

HandlAdapter:通過擴展處理器適配器,支持更多類型的處理器。

ViewResolver:通過擴展視圖解析器,支持更多類型的視圖解析,例如:jsp、freemarker、pdf、excel等。

組件:
1、前端控制器DispatcherServlet(不需要工程師開發),由框架提供
作用:接收請求,響應結果,相當於轉發器,中央處理器。有了dispatcherServlet減少了其它組件之間的耦合度。
用戶請求到達前端控制器,它就相當於mvc模式中的c,dispatcherServlet是整個流程控制的中心,由它調用其它組件處理用戶的請求,dispatcherServlet的存在降低了組件之間的耦合性。

2、處理器映射器HandlerMapping(不需要工程師開發),由框架提供
作用:根據請求的url查找Handler
HandlerMapping負責根據用戶請求找到Handler即處理器,springmvc提供了不同的映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,註解方式等。

3、處理器適配器HandlerAdapter
作用:按照特定規則(HandlerAdapter要求的規則)去執行Handler
通過HandlerAdapter對處理器進行執行,這是適配器模式的應用,通過擴展適配器可以對更多類型的處理器進行執行。

4、處理器Handler(需要工程師開發)
注意:編寫Handler時按照HandlerAdapter的要求去做,這樣適配器纔可以去正確執行Handler
Handler 是繼DispatcherServlet前端控制器的後端控制器,在DispatcherServlet的控制下Handler對具體的用戶請求進行處理。
由於Handler涉及到具體的用戶業務請求,所以一般情況需要工程師根據業務需求開發Handler。

5、視圖解析器View resolver(不需要工程師開發),由框架提供
作用:進行視圖解析,根據邏輯視圖名解析成真正的視圖(view)
View Resolver負責將處理結果生成View視圖,View Resolver首先根據邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成View視圖對象,最後對View進行渲染將處理結果通過頁面展示給用戶。 springmvc框架提供了很多的View視圖類型,包括:jstlView、freemarkerView、pdfView等。
一般情況下需要通過頁面標籤或頁面模版技術將模型數據通過頁面展示給用戶,需要由工程師根據業務需求開發具體的頁面。

6、視圖View(需要工程師開發jsp…)
View是一個接口,實現類支持不同的View類型(jsp、freemarker、pdf…)

核心架構的具體流程步驟如下:
1、首先用戶發送請求——>DispatcherServlet,前端控制器收到請求後自己不進行處理,而是委託給其他的解析器進行處理,作爲統一訪問點,進行全局的流程控制;
2、DispatcherServlet——>HandlerMapping, HandlerMapping 將會把請求映射爲HandlerExecutionChain 對象(包含一個Handler 處理器(頁面控制器)對象、多個HandlerInterceptor 攔截器)對象,通過這種策略模式,很容易添加新的映射策略;
3、DispatcherServlet——>HandlerAdapter,HandlerAdapter 將會把處理器包裝爲適配器,從而支持多種類型的處理器,即適配器設計模式的應用,從而很容易支持很多類型的處理器;
4、HandlerAdapter——>處理器功能處理方法的調用,HandlerAdapter 將會根據適配的結果調用真正的處理器的功能處理方法,完成功能處理;並返回一個ModelAndView 對象(包含模型數據、邏輯視圖名);
5、ModelAndView的邏輯視圖名——> ViewResolver, ViewResolver 將把邏輯視圖名解析爲具體的View,通過這種策略模式,很容易更換其他視圖技術;
6、View——>渲染,View會根據傳進來的Model模型數據進行渲染,此處的Model實際是一個Map數據結構,因此很容易支持其他視圖技術;
7、返回控制權給DispatcherServlet,由DispatcherServlet返回響應給用戶,到此一個流程結束。

下邊兩個組件通常情況下需要開發:

Handler:處理器,即後端控制器用controller表示。

View:視圖,即展示給用戶的界面,視圖中通常需要標籤語言展示模型數據。

在講SpringMVC之前我們先來看一下什麼是MVC模式

MVC:MVC是一種設計模式

MVC的原理圖:

img

分析:

M-Model 模型(完成業務邏輯:有javaBean構成,service+dao+entity)

V-View 視圖(做界面的展示 jsp,html……)

C-Controller 控制器(接收請求—>調用模型—>根據結果派發頁面)

springMVC是什麼:

springMVC是一個MVC的開源框架,springMVC=struts2+spring,springMVC就相當於是Struts2加上sring的整合,但是這裏有一個疑惑就是,springMVC和spring是什麼樣的關係呢?這個在百度百科上有一個很好的解釋:意思是說,springMVC是spring的一個後續產品,其實就是spring在原有基礎上,又提供了web應用的MVC模塊,可以簡單的把springMVC理解爲是spring的一個模塊(類似AOP,IOC這樣的模塊),網絡上經常會說springMVC和spring無縫集成,其實springMVC就是spring的一個子模塊,所以根本不需要同spring進行整合。

SpringMVC的原理圖:

img

看到這個圖大家可能會有很多的疑惑,現在我們來看一下這個圖的步驟:(可以對比MVC的原理圖進行理解)

第一步:用戶發起請求到前端控制器(DispatcherServlet)

第二步:前端控制器請求處理器映射器(HandlerMappering)去查找處理器(Handle):通過xml配置或者註解進行查找

第三步:找到以後處理器映射器(HandlerMappering)像前端控制器返回執行鏈(HandlerExecutionChain)

第四步:前端控制器(DispatcherServlet)調用處理器適配器(HandlerAdapter)去執行處理器(Handler)

第五步:處理器適配器去執行Handler

第六步:Handler執行完給處理器適配器返回ModelAndView

第七步:處理器適配器向前端控制器返回ModelAndView

第八步:前端控制器請求視圖解析器(ViewResolver)去進行視圖解析

第九步:視圖解析器像前端控制器返回View

第十步:前端控制器對視圖進行渲染

第十一步:前端控制器向用戶響應結果

看到這些步驟我相信大家很感覺非常的亂,這是正常的,但是這裏主要是要大家理解springMVC中的幾個組件:

前端控制器(DispatcherServlet):接收請求,響應結果,相當於電腦的CPU。

處理器映射器(HandlerMapping):根據URL去查找處理器

處理器(Handler):(需要程序員去寫代碼處理邏輯的)

處理器適配器(HandlerAdapter):會把處理器包裝成適配器,這樣就可以支持多種類型的處理器,類比筆記本的適配器(適配器模式的應用)

視圖解析器(ViewResovler):進行視圖解析,多返回的字符串,進行處理,可以解析成對應的頁面

6、SpringMVC怎麼樣設定重定向和轉發的?

(1)轉發:在返回值前面加"forward:",譬如"forward:user.do?name=method4"

(2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"

7、 SpringMVC常用的註解有哪些?

@RequestMapping:用於處理請求 url 映射的註解,可用於類或方法上。用於類上,則表示類中的所有響應請求的方法都是以該地址作爲父路徑。

@RequestBody:註解實現接收http請求的json數據,將json轉換爲java對象。

@ResponseBody:註解實現將conreoller方法返回對象轉化爲json對象響應給客戶。

8、 Spring的AOP理解:

OOP面向對象,允許開發者定義縱向的關係,但並適用於定義橫向的關係,導致了大量代碼的重複,而不利於各個模塊的重用。

AOP,一般稱爲面向切面,作爲面向對象的一種補充,用於將那些與業務無關,但卻對多個對象產生影響的公共行爲和邏輯,抽取並封裝爲一個可重用的模塊,這個模塊被命名爲“切面”(Aspect),減少系統中的重複代碼,降低了模塊間的耦合度,同時提高了系統的可維護性。可用於權限認證、日誌、事務處理。

AOP實現的關鍵在於 代理模式,AOP代理主要分爲靜態代理和動態代理。靜態代理的代表爲AspectJ;動態代理則以Spring AOP爲代表。

(1)AspectJ是靜態代理的增強,所謂靜態代理,就是AOP框架會在編譯階段生成AOP代理類,因此也稱爲編譯時增強,他會在編譯階段將AspectJ(切面)織入到Java字節碼中,運行的時候就是增強之後的AOP對象。

(2)Spring AOP使用的動態代理,所謂的動態代理就是說AOP框架不會去修改字節碼,而是每次運行時在內存中臨時爲方法生成一個AOP對象,這個AOP對象包含了目標對象的全部方法,並且在特定的切點做了增強處理,並回調原對象的方法。

Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理:

    ①JDK動態代理只提供接口的代理,不支持類的代理。核心InvocationHandler接口和Proxy類,InvocationHandler 通過invoke()方法反射來調用目標類中的代碼,動態地將橫切邏輯和業務編織在一起;接着,Proxy利用 InvocationHandler動態創建一個符合某一接口的的實例,  生成目標類的代理對象。

    ②如果代理類沒有實現 InvocationHandler 接口,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成指定類的一個子類對象,並覆蓋其中特定方法並添加增強代碼,從而實現AOP。CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記爲final,那麼它是無法使用CGLIB做動態代理的。

(3)靜態代理與動態代理區別在於生成AOP代理對象的時機不同,相對來說AspectJ的靜態代理方式具有更好的性能,但是AspectJ需要特定的編譯器進行處理,而Spring AOP則無需特定的編譯器處理。

9、Spring的IOC理解

(1)IOC就是控制反轉,是指創建對象的控制權的轉移,以前創建對象的主動權和時機是由自己把控的,而現在這種權力轉移到Spring容器中,並由容器根據配置文件去創建實例和管理各個實例之間的依賴關係,對象與對象之間鬆散耦合,也利於功能的複用。DI依賴注入,和控制反轉是同一個概念的不同角度的描述,即 應用程序在運行時依賴IoC容器來動態注入對象需要的外部資源。

(2)最直觀的表達就是,IOC讓對象的創建不用去new了,可以由spring自動生產,使用java的反射機制,根據配置文件在運行時動態的去創建對象以及管理對象,並調用對象的方法的。

(3)Spring的IOC有三種注入方式 :構造器注入、setter方法注入、根據註解注入。

IoC讓相互協作的組件保持鬆散的耦合,而AOP編程允許你把遍佈於應用各層的功能分離出來形成可重用的功能組件。

10、解釋一下spring bean的生命週期

首先說一下Servlet的生命週期:實例化,初始init,接收請求service,銷燬destroy;

Spring上下文中的Bean生命週期也類似,如下:

(1)實例化Bean:

對於BeanFactory容器,當客戶向容器請求一個尚未初始化的bean時,或初始化bean的時候需要注入另一個尚未初始化的依賴時,容器就會調用createBean進行實例化。對於ApplicationContext容器,當容器啓動結束後,通過獲取BeanDefinition對象中的信息,實例化所有的bean。

(2)設置對象屬性(依賴注入):

實例化後的對象被封裝在BeanWrapper對象中,緊接着,Spring根據BeanDefinition中的信息 以及 通過BeanWrapper提供的設置屬性的接口完成依賴注入。

(3)處理Aware接口:

接着,Spring會檢測該對象是否實現了xxxAware接口,並將相關的xxxAware實例注入給Bean:

①如果這個Bean已經實現了BeanNameAware接口,會調用它實現的setBeanName(String beanId)方法,此處傳遞的就是Spring配置文件中Bean的id值;

②如果這個Bean已經實現了BeanFactoryAware接口,會調用它實現的setBeanFactory()方法,傳遞的是Spring工廠自身。

③如果這個Bean已經實現了ApplicationContextAware接口,會調用setApplicationContext(ApplicationContext)方法,傳入Spring上下文;

(4)BeanPostProcessor:

如果想對Bean進行一些自定義的處理,那麼可以讓Bean實現了BeanPostProcessor接口,那將會調用postProcessBeforeInitialization(Object obj, String s)方法。

(5)InitializingBean 與 init-method:

如果Bean在Spring配置文件中配置了 init-method 屬性,則會自動調用其配置的初始化方法。

(6)如果這個Bean實現了BeanPostProcessor接口,將會調用postProcessAfterInitialization(Object obj, String s)方法;由於這個方法是在Bean初始化結束時調用的,所以可以被應用於內存或緩存技術;

以上幾個步驟完成後,Bean就已經被正確創建了,之後就可以使用這個Bean了。

(7)DisposableBean:

當Bean不再需要時,會經過清理階段,如果Bean實現了DisposableBean這個接口,會調用其實現的destroy()方法;

(8)destroy-method:

最後,如果這個Bean的Spring配置中配置了destroy-method屬性,會自動調用其配置的銷燬方法。

11、 解釋Spring支持的幾種bean的作用域。

Spring容器中的bean可以分爲5個範圍:

(1)singleton:默認,每個容器中只有一個bean的實例,單例的模式由BeanFactory自身來維護。

(2)prototype:爲每一個bean請求提供一個實例。

(3)request:爲每一個網絡請求創建一個實例,在請求完成以後,bean會失效並被垃圾回收器回收。

(4)session:與request範圍類似,確保每個session中有一個bean的實例,在session過期後,bean會隨之失效。

(5)global-session:全局作用域,global-session和Portlet應用相關。當你的應用部署在Portlet容器中工作時,它包含很多portlet。如果你想要聲明讓所有的portlet共用全局的存儲變量的話,那麼這全局變量需要存儲在global-session中。全局作用域與Servlet中的session作用域效果相同。

12、 Spring基於xml注入bean的幾種方式:

(1)Set方法注入;

(2)構造器注入:①通過index設置參數的位置;②通過type設置參數類型;

(3)靜態工廠注入;

(4)實例工廠;

詳細內容可以閱讀:https://blog.csdn.net/a745233700/article/details/89307518

13、Spring框架中都用到了哪些設計模式?

(1)工廠模式:BeanFactory就是簡單工廠模式的體現,用來創建對象的實例;

(2)單例模式:Bean默認爲單例模式。

(3)代理模式:Spring的AOP功能用到了JDK的動態代理和CGLIB字節碼生成技術;

(4)模板方法:用來解決代碼重複的問題。比如. RestTemplate, JmsTemplate, JpaTemplate。

(5)觀察者模式:定義對象鍵一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都會得到通知被制動更新,如Spring中listener的實現–ApplicationListener。

MyBatis篇

1、什麼是MyBatis

(1)Mybatis是一個半ORM(對象關係映射)框架,它內部封裝了JDBC,開發時只需要關注SQL語句本身,不需要花費精力去處理加載驅動、創建連接、創建statement等繁雜的過程。程序員直接編寫原生態sql,可以嚴格控制sql執行性能,靈活度高。

(2)MyBatis 可以使用 XML 或註解來配置和映射原生信息,將 POJO映射成數據庫中的記錄,避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。

(3)通過xml 文件或註解的方式將要執行的各種 statement 配置起來,並通過java對象和 statement中sql的動態參數進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射爲java對象並返回。(從執行sql到返回result的過程)。

2、MyBatis的優點和缺點

優點:

(1)基於SQL語句編程,相當靈活,不會對應用程序或者數據庫的現有設計造成任何影響,SQL寫在XML裏,解除sql與程序代碼的耦合,便於統一管理;提供XML標籤,支持編寫動態SQL語句,並可重用。

(2)與JDBC相比,減少了50%以上的代碼量,消除了JDBC大量冗餘的代碼,不需要手動開關連接;

(3)很好的與各種數據庫兼容(因爲MyBatis使用JDBC來連接數據庫,所以只要JDBC支持的數據庫MyBatis都支持)。

(4)能夠與Spring很好的集成;

(5)提供映射標籤,支持對象與數據庫的ORM字段關係映射;提供對象關係映射標籤,支持對象關係組件維護。

缺點

(1)SQL語句的編寫工作量較大,尤其當字段多、關聯表多時,對開發人員編寫SQL語句的功底有一定要求。

(2)SQL語句依賴於數據庫,導致數據庫移植性差,不能隨意更換數據庫。

3、#{}和${}的區別是什麼?

#{}是預編譯處理,${}是字符串替換。

Mybatis在處理#{}時,會將sql中的#{}替換爲?號,調用PreparedStatement的set方法來賦值;

Mybatis在處理{}時,就是把{}替換成變量的值。

使用#{}可以有效的防止SQL注入,提高系統安全性。

4、當實體類中的屬性名和表中的字段名不一樣 ,怎麼辦 ?

第1種: 通過在查詢的sql語句中定義字段名的別名,讓字段名的別名和實體類的屬性名一致。

    <select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>
       select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
    </select>

第2種: 通過來映射字段名和實體類屬性名的一一對應的關係。

 <select id="getOrder" parameterType="int" resultMap="orderresultmap">
        select * from orders where order_id=#{id}
    </select>
 
   <resultMap type=”me.gacl.domain.order” id=”orderresultmap”>
        <!–用id屬性來映射主鍵字段–>
        <id property=”id” column=”order_id”>
 
        <!–用result屬性來映射非主鍵字段,property爲實體類屬性名,column爲數據表中的屬性–>
        <result property = “orderno” column =”order_no”/>
        <result property=”price” column=”order_price” />
    </reslutMap>

5、Mybatis是如何進行分頁的?分頁插件的原理是什麼?

Mybatis使用RowBounds對象進行分頁,它是針對ResultSet結果集執行的內存分頁,而非物理分頁。可以在sql內直接書寫帶有物理分頁的參數來完成物理分頁功能,也可以使用分頁插件來完成物理分頁。

分頁插件的基本原理是使用Mybatis提供的插件接口,實現自定義插件,在插件的攔截方法內攔截待執行的sql,然後重寫sql,根據dialect方言,添加對應的物理分頁語句和物理分頁參數。

6、Mybatis是如何將sql執行結果封裝爲目標對象並返回的?都有哪些映射形式?

第一種是使用標籤,逐一定義數據庫列名和對象屬性名之間的映射關係。

第二種是使用sql列的別名功能,將列的別名書寫爲對象屬性名。

有了列名與屬性名的映射關係後,Mybatis通過反射創建對象,同時使用反射給對象的屬性逐一賦值並返回,那些找不到映射關係的屬性,是無法完成賦值的。

7、 如何執行批量插入?

首先,創建一個簡單的insert語句:

    <insert id=”insertname”>
         insert into names (name) values (#{value})
    </insert>

然後在java代碼中像下面這樣執行批處理插入:

  list<string> names = new arraylist();
    names.add(“fred”);
    names.add(“barney”);
    names.add(“betty”);
    names.add(“wilma”);
 
    // 注意這裏 executortype.batch
    sqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);
    try {
     namemapper mapper = sqlsession.getmapper(namemapper.class);
     for (string name : names) {
         mapper.insertname(name);
     }
     sqlsession.commit();
    }catch(Exception e){
     e.printStackTrace();
     sqlSession.rollback(); 
     throw e; 
    }
     finally {
         sqlsession.close();
    }

8、Xml映射文件中,除了常見的select|insert|updae|delete標籤之外,還有哪些標籤?

、、、、,加上動態sql的9個標籤,其中爲sql片段標籤,通過標籤引入sql片段,爲不支持自增的主鍵生成策略標籤。

9、MyBatis實現一對一有幾種方式?具體怎麼操作的?

有聯合查詢和嵌套查詢,聯合查詢是幾個表聯合查詢,只查詢一次, 通過在resultMap裏面配置association節點配置一對一的類就可以完成;

嵌套查詢是先查一個表,根據這個表裏面的結果的 外鍵id,去再另外一個表裏面查詢數據,也是通過association配置,但另外一個表的查詢通過select屬性配置。

10、Mybatis是否支持延遲加載?如果支持,它的實現原理是什麼?

Mybatis僅支持association關聯對象和collection關聯集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啓用延遲加載lazyLoadingEnabled=true|false。

它的原理是,使用CGLIB創建目標對象的代理對象,當調用目標方法時,進入攔截器方法,比如調用a.getB().getName(),攔截器invoke()方法發現a.getB()是null值,那麼就會單獨發送事先保存好的查詢關聯B對象的sql,把B查詢上來,然後調用a.setB(b),於是a的對象b屬性就有值了,接着完成a.getB().getName()方法的調用。這就是延遲加載的基本原理。

當然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的。

11、Mybatis的一級、二級緩存:

1)一級緩存: 基於 PerpetualCache 的 HashMap 本地緩存,其存儲作用域爲 Session,當 Session flush 或 close 之後,該 Session 中的所有 Cache 就將清空,默認打開一級緩存。

2)二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap 存儲,不同在於其存儲作用域爲 Mapper(Namespace),並且可自定義存儲源,如 Ehcache。默認不打開二級緩存,要開啓二級緩存,使用二級緩存屬性類需要實現Serializable序列化接口(可用來保存對象的狀態),可在它的映射文件中配置 ;

3)對於緩存數據更新機制,當某一個作用域(一級緩存 Session/二級緩存Namespaces)的進行了C/U/D 操作後,默認該作用域下所有 select 中的緩存將被 clear 掉並重新更新,如果開啓了二級緩存,則只根據配置判斷是否刷新。

SpringBoot篇

1、什麼是SpringBoot?爲什麼要用SpringBoot

用來簡化spring應用的初始搭建以及開發過程 使用特定的方式來進行配置(properties或yml文件)

創建獨立的spring引用程序 main方法運行

嵌入的Tomcat 無需部署war文件

簡化maven配置

自動配置spring添加對應功能starter自動化配置

spring boot來簡化spring應用開發,約定大於配置,去繁從簡,just run就能創建一個獨立的,產品級別的應用

Spring Boot 優點非常多,如:

一、獨立運行

Spring Boot而且內嵌了各種servlet容器,Tomcat、Jetty等,現在不再需要打成war包部署到容器中,Spring Boot只要打成一個可執行的jar包就能獨立運行,所有的依賴包都在一個jar包內。

二、簡化配置

spring-boot-starter-web啓動器自動依賴其他組件,簡少了maven的配置。
三、自動配置

Spring Boot能根據當前類路徑下的類、jar包來自動配置bean,如添加一個spring-boot-starter-web啓動器就能擁有web的功能,無需其他配置。

四、無代碼生成和XML配置

Spring Boot配置過程中無代碼生成,也無需XML配置文件就能完成所有配置工作,這一切都是藉助於條件註解完成的,這也是Spring4.x的核心功能之一。

五、應用監控

Spring Boot提供一系列端點可以監控服務及應用,做健康檢測。

2、Spring Boot 的核心註解是哪個?它主要由哪幾個註解組成的?

啓動類上面的註解是@SpringBootApplication,它也是 Spring Boot 的核心註解,主要組合包含了以下 3 個註解:

@SpringBootConfiguration:組合了 @Configuration 註解,實現配置文件的功能。

@EnableAutoConfiguration:打開自動配置的功能,也可以關閉某個自動配置的選項,如關閉數據源自動配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

@ComponentScan:Spring組件掃描。

3、運行Spring Boot有哪幾種方式?

1)打包用命令或者放到容器中運行

2)用 Maven/Gradle 插件運行

3)直接執行 main 方法運行

4、如何理解 Spring Boot 中的 Starters?

Starters是什麼:

Starters可以理解爲啓動器,它包含了一系列可以集成到應用裏面的依賴包,你可以一站式集成Spring及其他技術,而不需要到處找示例代碼和依賴包。如你想使用Spring JPA訪問數據庫,只要加入spring-boot-starter-data-jpa啓動器依賴就能使用了。Starters包含了許多項目中需要用到的依賴,它們能快速持續的運行,都是一系列得到支持的管理傳遞性依賴。

Starters命名:

Spring Boot官方的啓動器都是以spring-boot-starter-命名的,代表了一個特定的應用類型。第三方的啓動器不能以spring-boot開頭命名,它們都被Spring Boot官方保留。一般一個第三方的應該這樣命名,像mybatis的mybatis-spring-boot-starter。

Starters分類:

  1. Spring Boot應用類啓動器

img

  1. Spring Boot生產啓動器

img

  1. Spring Boot技術類啓動器

img

  1. 其他第三方啓動器

5、 如何在Spring Boot啓動的時候運行一些特定的代碼?

如果你想在Spring Boot啓動的時候運行一些特定的代碼,你可以實現接口ApplicationRunner或者CommandLineRunner,這兩個接口實現方式一樣,它們都只提供了一個run方法。

CommandLineRunner:啓動獲取命令行參數

6、 Spring Boot 需要獨立的容器運行嗎?

可以不需要,內置了 Tomcat/ Jetty 等容器。

7、 Spring Boot中的監視器是什麼?

Spring boot actuator是spring啓動框架中的重要功能之一。Spring boot監視器可幫助您訪問生產環境中正在運行的應用程序的當前狀態。有幾個指標必須在生產環境中進行檢查和監控。即使一些外部應用程序可能正在使用這些服務來向相關人員觸發警報消息。監視器模塊公開了一組可直接作爲HTTP URL訪問的REST端點來檢查狀態。

8、 如何使用Spring Boot實現異常處理?

Spring提供了一種使用ControllerAdvice處理異常的非常有用的方法。 我們通過實現一個ControlerAdvice類,來處理控制器類拋出的所有異常。

9、 你如何理解 Spring Boot 中的 Starters?

Starters可以理解爲啓動器,它包含了一系列可以集成到應用裏面的依賴包,你可以一站式集成 Spring 及其他技術,而不需要到處找示例代碼和依賴包。如你想使用 Spring JPA 訪問數據庫,只要加入 spring-boot-starter-data-jpa 啓動器依賴就能使用了。

10、 springboot常用的starter有哪些

spring-boot-starter-web 嵌入tomcat和web開發需要servlet與jsp支持

spring-boot-starter-data-jpa 數據庫支持

spring-boot-starter-data-redis redis數據庫支持

spring-boot-starter-data-solr solr支持

mybatis-spring-boot-starter 第三方的mybatis集成starter

11、 SpringBoot 實現熱部署有哪幾種方式?

主要有兩種方式:

  • Spring Loaded
  • Spring-boot-devtools

12、 如何理解 Spring Boot 配置加載順序?

在 Spring Boot 裏面,可以使用以下幾種方式來加載配置。

1)properties文件;

2)YAML文件;

3)系統環境變量;

4)命令行參數;

等等……

13、 Spring Boot 的核心配置文件有哪幾個?它們的區別是什麼?

pring Boot 的核心配置文件是 application 和 bootstrap 配置文件。

application 配置文件這個容易理解,主要用於 Spring Boot 項目的自動化配置。

bootstrap 配置文件有以下幾個應用場景。

  • 使用 Spring Cloud Config 配置中心時,這時需要在 bootstrap 配置文件中添加連接到配置中心的配置屬性來加載外部配置中心的配置信息;
  • 一些固定的不能被覆蓋的屬性;
  • 一些加密/解密的場景;

14、如何集成 Spring Boot 和 ActiveMQ?

對於集成 Spring Boot 和 ActiveMQ,我們使用
spring-boot-starter-activemq
依賴關係。 它只需要很少的配置,並且不需要樣板代碼。

15、如何重新加載Spring Boot上的更改,而無需重新啓動服務器?

這可以使用DEV工具來實現。通過這種依賴關係,您可以節省任何更改,嵌入式tomcat將重新啓動。

Spring Boot有一個開發工具(DevTools)模塊,它有助於提高開發人員的生產力。Java開發人員面臨的一個主要挑戰是將文件更改自動部署到服務器並自動重啓服務器。

開發人員可以重新加載Spring Boot上的更改,而無需重新啓動服務器。這將消除每次手動部署更改的需要。Spring Boot在發佈它的第一個版本時沒有這個功能。

這是開發人員最需要的功能。DevTools模塊完全滿足開發人員的需求。該模塊將在生產環境中被禁用。它還提供H2數據庫控制檯以更好地測試應用程序。

org.springframework.boot

spring-boot-devtools

true

16、 Spring Boot、Spring MVC 和 Spring 有什麼區別?

1、Spring

Spring最重要的特徵是依賴注入。所有 SpringModules 不是依賴注入就是 IOC 控制反轉。

當我們恰當的使用 DI 或者是 IOC 的時候,我們可以開發鬆耦合應用。鬆耦合應用的單元測試可以很容易的進行。

2、Spring MVC

Spring MVC 提供了一種分離式的方法來開發 Web 應用。通過運用像 DispatcherServelet,MoudlAndView 和 ViewResolver 等一些簡單的概念,開發 Web 應用將會變的非常簡單。

3、SpringBoot

Spring 和 SpringMVC 的問題在於需要配置大量的參數。

img

Spring Boot 通過一個自動配置和啓動的項來目解決這個問題。爲了更快的構建產品就緒應用程序,Spring Boot 提供了一些非功能性特徵。

17、 能否舉一個例子來解釋更多 Staters 的內容?

讓我們來思考一個 Stater 的例子 -Spring Boot Stater Web。

如果你想開發一個 web 應用程序或者是公開 REST 服務的應用程序。Spring Boot Start Web 是首選。讓我們使用 Spring Initializr 創建一個 Spring Boot Start Web 的快速項目。

Spring Boot Start Web 的依賴項

img

下面的截圖是添加進我們應用程序的不同的依賴項

img

依賴項可以被分爲:

  • Spring - core,beans,context,aop
  • Web MVC - (Spring MVC)
  • Jackson - for JSON Binding
  • Validation - Hibernate,Validation API
  • Enbedded Servlet Container - Tomcat
  • Logging - logback,slf4j

任何經典的 Web 應用程序都會使用所有這些依賴項。Spring Boot Starter Web 預先打包了這些依賴項。

作爲一個開發者,我不需要再擔心這些依賴項和它們的兼容版本。

18、 Spring Boot 還提供了其它的哪些 Starter Project Options?

Spring Boot 也提供了其它的啓動器項目包括,包括用於開發特定類型應用程序的典型依賴項。

  • spring-boot-starter-web-services - SOAP Web Services;
  • spring-boot-starter-web - Web 和 RESTful 應用程序;
  • spring-boot-starter-test - 單元測試和集成測試;
  • spring-boot-starter-jdbc - 傳統的 JDBC;
  • spring-boot-starter-hateoas - 爲服務添加 HATEOAS 功能;
  • spring-boot-starter-security - 使用 SpringSecurity 進行身份驗證和授權;
  • spring-boot-starter-data-jpa - 帶有 Hibeernate 的 Spring Data JPA;
  • spring-boot-starter-data-rest - 使用 Spring Data REST 公佈簡單的 REST 服務;

MySQL篇

1、數據庫的三範式是什麼

第一範式:列不可再分
第二範式:行可以唯一區分,主鍵約束
第三範式:表的非主屬性不能依賴與其他表的非主屬性 外鍵約束
且三大範式是一級一級依賴的,第二範式建立在第一範式上,第三範式建立第一第二範式上。

2、數據庫引擎有哪些

如何查看mysql提供的所有存儲引擎

mysql> show engines;

查看MySQL提供的所有存儲引擎

mysql常用引擎包括:MYISAM、Innodb、Memory、MERGE

  • MYISAM:全表鎖,擁有較高的執行速度,不支持事務,不支持外鍵,併發性能差,佔用空間相對較小,對事務完整性沒有要求,以select、insert爲主的應用基本上可以使用這引擎
  • Innodb:行級鎖,提供了具有提交、回滾和崩潰回覆能力的事務安全,支持自動增長列,支持外鍵約束,併發能力強,佔用空間是MYISAM的2.5倍,處理效率相對會差一些
  • Memory:全表鎖,存儲在內容中,速度快,但會佔用和數據量成正比的內存空間且數據在mysql重啓時會丟失,默認使用HASH索引,檢索效率非常高,但不適用於精確查找,主要用於那些內容變化不頻繁的代碼表
  • MERGE:是一組MYISAM表的組合

3、InnoDB與MyISAM的區別

  1. InnoDB支持事務,MyISAM不支持,對於InnoDB每一條SQL語言都默認封裝成事務,自動提交,這樣會影響速度,所以最好把多條SQL語言放在begin和commit之間,組成一個事務;
  2. InnoDB支持外鍵,而MyISAM不支持。對一個包含外鍵的InnoDB錶轉爲MYISAM會失敗;
  3. InnoDB是聚集索引,數據文件是和索引綁在一起的,必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然後再通過主鍵查詢到數據。因此,主鍵不應該過大,因爲主鍵太大,其他索引也都會很大。而MyISAM是非聚集索引,數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。
  4. InnoDB不保存表的具體行數,執行select count(*) from table時需要全表掃描。而MyISAM用一個變量保存了整個表的行數,執行上述語句時只需要讀出該變量即可,速度很快;
  5. Innodb不支持全文索引,而MyISAM支持全文索引,查詢效率上MyISAM要高;

如何選擇引擎?

如果沒有特別的需求,使用默認的Innodb即可。

MyISAM:以讀寫插入爲主的應用程序,比如博客系統、新聞門戶網站。

Innodb:更新(刪除)操作頻率也高,或者要保證數據的完整性;併發量高,支持事務和外鍵。比如OA自動化辦公系統。

4、數據庫的事務

什麼是事務?: 多條sql語句,要麼全部成功,要麼全部失敗。

事務的特性:

數據庫事務特性:原子性(Atomic)、一致性(Consistency)、隔離性(Isolation)、持久性(Durabiliy)。簡稱ACID。

  • 原子性:組成一個事務的多個數據庫操作是一個不可分割的原子單元,只有所有操作都成功,整個事務纔會提交。任何一個操作失敗,已經執行的任何操作都必須撤銷,讓數據庫返回初始狀態。
  • 一致性:事務操作成功後,數據庫所處的狀態和它的業務規則是一致的。即數據不會被破壞。如A轉賬100元給B,不管操作是否成功,A和B的賬戶總額是不變的。
  • 隔離性:在併發數據操作時,不同的事務擁有各自的數據空間,它們的操作不會對彼此產生干擾
  • 持久性:一旦事務提交成功,事務中的所有操作都必須持久化到數據庫中。

5、索引問題

索引是對數據庫表中一個或多個列的值進行排序的結構,建立索引有助於快速獲取信息。

你也可以這樣理解:索引就是加快檢索表中數據的方法。數據庫的索引類似於書籍的索引。在書籍中,索引允許用戶不必翻閱完整個書就能迅速地找到所需要的信息。在數據庫中,索引也允許數據庫程序迅速地找到表中的數據,而不必掃描整個數據庫。

mysql有4種不同的索引:

  • 主鍵索引(PRIMARY)

    數據列不允許重複,不允許爲NULL,一個表只能有一個主鍵。

  • 唯一索引(UNIQUE)

    數據列不允許重複,允許爲NULL值,一個表允許多個列創建唯一索引。

    • 可以通過 ALTER TABLE table_name ADD UNIQUE (column); 創建唯一索引
    • 可以通過 ALTER TABLE table_name ADD UNIQUE (column1,column2); 創建唯一組合索引
  • 普通索引(INDEX)

    • 可以通過ALTER TABLE table_name ADD INDEX index_name (column);創建普通索引
    • 可以通過ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);創建組合索引
  • 全文索引(FULLTEXT)

    可以通過ALTER TABLE table_name ADD FULLTEXT (column);創建全文索引

索引並非是越多越好,創建索引也需要耗費資源,一是增加了數據庫的存儲空間,二是在插入和刪除時要花費較多的時間維護索引

  • 索引加快數據庫的檢索速度
  • 索引降低了插入、刪除、修改等維護任務的速度
  • 唯一索引可以確保每一行數據的唯一性
  • 通過使用索引,可以在查詢的過程中使用優化隱藏器,提高系統的性能
  • 索引需要佔物理和數據空間

6、SQL優化

1、查詢語句中不要使用select *

2、儘量減少子查詢,使用關聯查詢(left join,right join,inner join)替代

3、減少使用IN或者NOT IN ,使用exists,not exists或者關聯查詢語句替代

4、or 的查詢儘量用 union或者union all 代替(在確認沒有重複數據或者不用剔除重複數據時,union all會更好)

5、應儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。

6、應儘量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: select id from t where num is null 可以在num上設置默認值0,確保表中num列沒有null值,然後這樣查詢: select id from t where num=0

7、簡單說一說drop、delete與truncate的區別

SQL中的drop、delete、truncate都表示刪除,但是三者有一些差別

delete和truncate只刪除表的數據不刪除表的結構
速度,一般來說: drop> truncate >delete
delete語句是dml,這個操作會放到rollback segement中,事務提交之後才生效;
如果有相應的trigger,執行的時候將被觸發. truncate,drop是ddl, 操作立即生效,原數據不放到rollback segment中,不能回滾. 操作不觸發trigger.

8、什麼是視圖

視圖是一種虛擬的表,具有和物理表相同的功能。可以對視圖進行增,改,查,操作,試圖通常是有一個表或者多個表的行或列的子集。對視圖的修改不影響基本表。它使得我們獲取數據更容易,相比多表查詢。

9、 什麼是內聯接、左外聯接、右外聯接?

  • 內聯接(Inner Join):匹配2張表中相關聯的記錄。
  • 左外聯接(Left Outer Join):除了匹配2張表中相關聯的記錄外,還會匹配左表中剩餘的記錄,右表中未匹配到的字段用NULL表示。
  • 右外聯接(Right Outer Join):除了匹配2張表中相關聯的記錄外,還會匹配右表中剩餘的記錄,左表中未匹配到的字段用NULL表示。在判定左表和右表時,要根據表名出現在Outer Join的左右位置關係。

10、併發事務帶來哪些問題?

在典型的應用程序中,多個事務併發運行,經常會操作相同的數據來完成各自的任務(多個用戶對同一數據進行操作)。併發雖然是必須的,但可能會導致以下的問題。

  • 髒讀(Dirty read): 當一個事務正在訪問數據並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時另外一個事務也訪問了這個數據,然後使用了這個數據。因爲這個數據是還沒有提交的數據,那麼另外一個事務讀到的這個數據是“髒數據”,依據“髒數據”所做的操作可能是不正確的。
  • 丟失修改(Lost to modify): 指在一個事務讀取一個數據時,另外一個事務也訪問了該數據,那麼在第一個事務中修改了這個數據後,第二個事務也修改了這個數據。這樣第一個事務內的修改結果就被丟失,因此稱爲丟失修改。 例如:事務1讀取某表中的數據A=20,事務2也讀取A=20,事務1修改A=A-1,事務2也修改A=A-1,最終結果A=19,事務1的修改被丟失。
  • 不可重複讀(Unrepeatableread): 指在一個事務內多次讀同一數據。在這個事務還沒有結束時,另一個事務也訪問該數據。那麼,在第一個事務中的兩次讀數據之間,由於第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這就發生了在一個事務內兩次讀到的數據是不一樣的情況,因此稱爲不可重複讀。
  • 幻讀(Phantom read): 幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個併發事務(T2)插入了一些數據時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱爲幻讀。

不可重複讀和幻讀區別:

不可重複讀的重點是修改比如多次讀取一條記錄發現其中某些列的值被修改,幻讀的重點在於新增或者刪除比如多次讀取一條記錄發現記錄增多或減少了。

11、事務隔離級別有哪些?MySQL的默認隔離級別是?

SQL 標準定義了四個隔離級別:

  • READ-UNCOMMITTED(讀取未提交): 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀
  • READ-COMMITTED(讀取已提交): 允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
  • REPEATABLE-READ(可重複讀): 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生
  • SERIALIZABLE(可串行化): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀
隔離級別 髒讀 不可重複讀 幻影讀
READ-UNCOMMITTED
READ-COMMITTED ×
REPEATABLE-READ × ×
SERIALIZABLE × × ×

MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;命令來查看

mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+

這裏需要注意的是:與 SQL 標準不同的地方在於 InnoDB 存儲引擎在 REPEATABLE-READ(可重讀) 事務隔離級別下使用的是Next-Key Lock 鎖算法,因此可以避免幻讀的產生,這與其他數據庫系統(如 SQL Server) 是不同的。所以說InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀) 已經可以完全保證事務的隔離性要求,即達到了 SQL標準的 SERIALIZABLE(可串行化) 隔離級別。因爲隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是 READ-COMMITTED(讀取提交內容) ,但是你要知道的是InnoDB 存儲引擎默認使用 REPEAaTABLE-READ(可重讀) 並不會有任何性能損失。

InnoDB 存儲引擎在 分佈式事務 的情況下一般會用到 SERIALIZABLE(可串行化) 隔離級別。

12、大表如何優化?

當MySQL單表記錄數過大時,數據庫的CRUD性能會明顯下降,一些常見的優化措施如下:

1. 限定數據的範圍

務必禁止不帶任何限制數據範圍條件的查詢語句。比如:我們當用戶在查詢訂單歷史的時候,我們可以控制在一個月的範圍內;

2. 讀/寫分離

經典的數據庫拆分方案,主庫負責寫,從庫負責讀;

3. 垂直分區

根據數據庫裏面數據表的相關性進行拆分。 例如,用戶表中既有用戶的登錄信息又有用戶的基本信息,可以將用戶表拆分成兩個單獨的表,甚至放到單獨的庫做分庫。

簡單來說垂直拆分是指數據表列的拆分,把一張列比較多的表拆分爲多張表。 如下圖所示,這樣來說大家應該就更容易理解了。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NOi4s0GM-1587954092062)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1583307481617.png)]

  • 垂直拆分的優點: 可以使得列數據變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分區可以簡化表的結構,易於維護。
  • 垂直拆分的缺點: 主鍵會出現冗餘,需要管理冗餘列,並會引起Join操作,可以通過在應用層進行Join來解決。此外,垂直分區會讓事務變得更加複雜;

4. 水平分區

保持數據表結構不變,通過某種策略存儲數據分片。這樣每一片數據分散到不同的表或者庫中,達到了分佈式的目的。 水平拆分可以支撐非常大的數據量。

水平拆分是指數據錶行的拆分,表的行數超過200萬行時,就會變慢,這時可以把一張的表的數據拆成多張表來存放。舉個例子:我們可以將用戶信息表拆分成多個用戶信息表,這樣就可以避免單一表數據量過大對性能造成影響。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VqSH0Kju-1587954092065)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1583308353521.png)]

水平拆分可以支持非常大的數據量。需要注意的一點是:分表僅僅是解決了單一表數據過大的問題,但由於表的數據還是在同一臺機器上,其實對於提升MySQL併發能力沒有什麼意義,所以 水平拆分最好分庫

水平拆分能夠 支持非常大的數據量存儲,應用端改造也少,但 分片事務難以解決 ,跨節點Join性能較差,邏輯複雜。《Java工程師修煉之道》的作者推薦 儘量不要對數據進行分片,因爲拆分會帶來邏輯、部署、運維的各種複雜度 ,一般的數據表在優化得當的情況下支撐千萬以下的數據量是沒有太大問題的。如果實在要分片,儘量選擇客戶端分片架構,這樣可以減少一次和中間件的網絡I/O。

下面補充一下數據庫分片的兩種常見方案:

  • 客戶端代理: 分片邏輯在應用端,封裝在jar包中,通過修改或者封裝JDBC層來實現。 噹噹網的 Sharding-JDBC 、阿里的TDDL是兩種比較常用的實現。
  • 中間件代理: 在應用和數據中間加了一個代理層。分片邏輯統一維護在中間件服務中。 我們現在談的 Mycat 、360的Atlas、網易的DDB等等都是這種架構的實現。

詳細內容可以參考: MySQL大表優化方案: https://segmentfault.com/a/1190000006158186

13、分庫分表之後,id 主鍵如何處理?

因爲要是分成多個表之後,每個表都是從 1 開始累加,這樣是不對的,我們需要一個全局唯一的 id 來支持。

生成全局 id 有下面這幾種方式:

  • UUID:不適合作爲主鍵,因爲太長了,並且無序不可讀,查詢效率低。比較適合用於生成唯一的名字的標示比如文件的名字。
  • 數據庫自增 id : 兩臺數據庫分別設置不同步長,生成不重複ID的策略來實現高可用。這種方式生成的 id 有序,但是需要獨立部署數據庫實例,成本高,還會有性能瓶頸。
  • 利用 redis 生成 id : 性能比較好,靈活方便,不依賴於數據庫。但是,引入了新的組件造成系統更加複雜,可用性降低,編碼更加複雜,增加了系統成本。
  • Twitter的snowflake算法 :Github 地址:https://github.com/twitter-archive/snowflake。
  • 美團的Leaf分佈式ID生成系統 :Leaf 是美團開源的分佈式ID生成器,能保證全局唯一性、趨勢遞增、單調遞增、信息安全,裏面也提到了幾種分佈式方案的對比,但也需要依賴關係數據庫、Zookeeper等中間件。感覺還不錯。美團技術團隊的一篇文章:https://tech.meituan.com/2017/04/21/mt-leaf.html 。

14、mysql有關權限的表都有哪幾個

MySQL服務器通過權限表來控制用戶對數據庫的訪問,權限表存放在mysql數據庫裏,由mysql_install_db腳本初始化。這些權限表分別user,db,table_priv,columns_priv和host。下面分別介紹一下這些表的結構和內容:

  • user權限表:記錄允許連接到服務器的用戶帳號信息,裏面的權限是全局級的。

  • db權限表:記錄各個帳號在各個數據庫上的操作權限。

  • table_priv權限表:記錄數據表級的操作權限。

  • columns_priv權限表:記錄數據列級的操作權限。

  • host權限表:配合db權限表對給定主機上數據庫級操作權限作更細緻的控制。這個權限表不受GRANT和REVOKE語句的影響。

15、mysql有哪些數據類型

1、整數類型 ,包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分別表示1字節、2字節、3字節、4字節、8字節整數。任何整數類型都可以加上UNSIGNED屬性,表示數據是無符號的,即非負整數。
長度:整數類型可以被指定長度,例如:INT(11)表示長度爲11的INT類型。長度在大多數場景是沒有意義的,它不會限制值的合法範圍,只會影響顯示字符的個數,而且需要和UNSIGNED ZEROFILL屬性配合使用纔有意義。
例子,假定類型設定爲INT(5),屬性爲UNSIGNED ZEROFILL,如果用戶插入的數據爲12的話,那麼數據庫實際存儲數據爲00012。

2、實數類型,包括FLOAT、DOUBLE、DECIMAL。
DECIMAL可以用於存儲比BIGINT還大的整型,能存儲精確的小數。
而FLOAT和DOUBLE是有取值範圍的,並支持使用標準的浮點進行近似計算。
計算時FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你可以理解成是用字符串進行處理。

3、字符串類型,包括VARCHAR、CHAR、TEXT、BLOB
VARCHAR用於存儲可變長字符串,它比定長類型更節省空間。
VARCHAR使用額外1或2個字節存儲字符串長度。列長度小於255字節時,使用1字節表示,否則使用2字節表示。
VARCHAR存儲的內容超出設置的長度時,內容會被截斷。
CHAR是定長的,根據定義的字符串長度分配足夠的空間。
CHAR會根據需要使用空格進行填充方便比較。
CHAR適合存儲很短的字符串,或者所有值都接近同一個長度。
CHAR存儲的內容超出設置的長度時,內容同樣會被截斷。

使用策略:
對於經常變更的數據來說,CHAR比VARCHAR更好,因爲CHAR不容易產生碎片。
對於非常短的列,CHAR比VARCHAR在存儲空間上更有效率。
使用時要注意只分配需要的空間,更長的列排序時會消耗更多內存。
儘量避免使用TEXT/BLOB類型,查詢時會使用臨時表,導致嚴重的性能開銷。

4、枚舉類型(ENUM),把不重複的數據存儲爲一個預定義的集合。
有時可以使用ENUM代替常用的字符串類型。
ENUM存儲非常緊湊,會把列表值壓縮到一個或兩個字節。
ENUM在內部存儲時,其實存的是整數。
儘量避免使用數字作爲ENUM枚舉的常量,因爲容易混亂。
排序是按照內部存儲的整數

5、日期和時間類型,儘量使用timestamp,空間效率高於datetime,
用整數保存時間戳通常不方便處理。
如果需要存儲微妙,可以使用bigint存儲。
看到這裏,這道真題是不是就比較容易回答了。

16、創建索引的三種方式,刪除索引

第一種方式:在執行CREATE TABLE時創建索引

CREATE TABLE user_index2 (
	id INT auto_increment PRIMARY KEY,
	first_name VARCHAR (16),
	last_name VARCHAR (16),
	id_card VARCHAR (18),
	information text,
	KEY name (first_name, last_name),
	FULLTEXT KEY (information),
	UNIQUE KEY (id_card)
);

第二種方式:使用ALTER TABLE命令去增加索引

ALTER TABLE table_name ADD INDEX index_name (column_list);

ALTER TABLE用來創建普通索引、UNIQUE索引或PRIMARY KEY索引。

其中table_name是要增加索引的表名,column_list指出對哪些列進行索引,多列時各列之間用逗號分隔。

索引名index_name可自己命名,缺省時,MySQL將根據第一個索引列賦一個名稱。另外,ALTER TABLE允許在單個語句中更改多個表,因此可以在同時創建多個索引。

第三種方式:使用CREATE INDEX命令創建

CREATE INDEX index_name ON table_name (column_list);

CREATE INDEX可對錶增加普通索引或UNIQUE索引。(但是,不能創建PRIMARY KEY索引)

刪除索引

根據索引名刪除普通索引、唯一索引、全文索引:alter table 表名 drop KEY 索引名

alter table user_index drop KEY name;
alter table user_index drop KEY id_card;
alter table user_index drop KEY information;

刪除主鍵索引:alter table 表名 drop primary key(因爲主鍵只有一個)。這裏值得注意的是,如果主鍵自增長,那麼不能直接執行此操作(自增長依賴於主鍵索引):

img

需要取消自增長再行刪除:

alter table user_index
-- 重新定義字段
MODIFY id int,
drop PRIMARY KEY

但通常不會刪除主鍵,因爲設計主鍵一定與業務邏輯無關。

Redis篇

1、Redis持久化機制

Redis是一個支持持久化的內存數據庫,通過持久化機制把內存中的數據同步到硬盤文件來保證數據持久化。當Redis重啓後通過把硬盤文件重新加載到內存,就能達到恢復數據的目的。
實現:單獨創建fork()一個子進程,將當前父進程的數據庫數據複製到子進程的內存中,然後由子進程寫入到臨時文件中,持久化的過程結束了,再用這個臨時文件替換上次的快照文件,然後子進程退出,內存釋放。

RDB是Redis默認的持久化方式。按照一定的時間週期策略把內存的數據以快照的形式保存到硬盤的二進制文件。即Snapshot快照存儲,對應產生的數據文件爲dump.rdb,通過配置文件中的save參數來定義快照的週期。( 快照可以是其所表示的數據的一個副本,也可以是數據的一個複製品。)
AOF:Redis會將每一個收到的寫命令都通過Write函數追加到文件最後,類似於MySQL的binlog。當Redis重啓是會通過重新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。
當兩種方式同時開啓時,數據恢復Redis會優先選擇AOF恢復。

2、緩存雪崩、緩存穿透、緩存預熱、緩存更新、緩存降級等問題

一、緩存雪崩

我們可以簡單的理解爲:由於原有緩存失效,新緩存未到期間
(例如:我們設置緩存時採用了相同的過期時間,在同一時刻出現大面積的緩存過期),所有原本應該訪問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。
解決辦法:
大多數系統設計者考慮用加鎖( 最多的解決方案)或者隊列的方式保證來保證不會有大量的線程對數據庫一次性進行讀寫,從而避免失效時大量的併發請求落到底層存儲系統上。還有一個簡單方案就時講緩存失效時間分散開。

二、緩存穿透
緩存穿透是指用戶查詢數據,在數據庫沒有,自然在緩存中也不會有。這樣就導致用戶查詢的時候,在緩存中找不到,每次都要去數據庫再查詢一遍,然後返回空(相當於進行了兩次無用的查詢)。這樣請求就繞過緩存直接查數據庫,這也是經常提的緩存命中率問題。
解決辦法;
最常見的則是採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。
另外也有一個更爲簡單粗暴的方法,如果一個查詢返回的數據爲空(不管是數據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。通過這個直接設置的默認值存放到緩存,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問數據庫,這種辦法最簡單粗暴。
5TB的硬盤上放滿了數據,請寫一個算法將這些數據進行排重。如果這些數據是一些32bit大小的數據該如何解決?如果是64bit的呢?

對於空間的利用到達了一種極致,那就是Bitmap和布隆過濾器(Bloom Filter)。
Bitmap: 典型的就是哈希表
缺點是,Bitmap對於每個元素只能記錄1bit信息,如果還想完成額外的功能,恐怕只能靠犧牲更多的空間、時間來完成了。

布隆過濾器(推薦)
就是引入了k(k>1)k(k>1)個相互獨立的哈希函數,保證在給定的空間、誤判率下,完成元素判重的過程。
它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。
Bloom-Filter算法的核心思想就是利用多個不同的Hash函數來解決“衝突”。
Hash存在一個衝突(碰撞)的問題,用同一個Hash得到的兩個URL的值有可能相同。爲了減少衝突,我們可以多引入幾個Hash,如果通過其中的一個Hash值我們得出某元素不在集合中,那麼該元素肯定不在集合中。只有在所有的Hash函數告訴我們該元素在集合中時,才能確定該元素存在於集合中。這便是Bloom-Filter的基本思想。
Bloom-Filter一般用於在大數據量的集合中判定某元素是否存在。

三、緩存預熱
緩存預熱這個應該是一個比較常見的概念,相信很多小夥伴都應該可以很容易的理解,緩存預熱就是系統上線後,將相關的緩存數據直接加載到緩存系統。這樣就可以避免在用戶請求的時候,先查詢數據庫,然後再將數據緩存的問題!用戶直接查詢事先被預熱的緩存數據!
解決思路:
1、直接寫個緩存刷新頁面,上線時手工操作下;
2、數據量不大,可以在項目啓動的時候自動進行加載;
3、定時刷新緩存;

四、緩存更新
除了緩存服務器自帶的緩存失效策略之外(Redis默認的有6中策略可供選擇),我們還可以根據具體的業務需求進行自定義的緩存淘汰,常見的策略有兩種:
(1)定時去清理過期的緩存;
(2)當有用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新數據並更新緩存。
兩者各有優劣,第一種的缺點是維護大量緩存的key是比較麻煩的,第二種的缺點就是每次用戶請求過來都要判斷緩存失效,邏輯相對比較複雜!具體用哪種方案,大家可以根據自己的應用場景來權衡。

五、緩存降級
當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵數據進行自動降級,也可以配置開關實現人工降級。
降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。
以參考日誌級別設置預案:
(1)一般:比如有些服務偶爾因爲網絡抖動或者服務正在上線而超時,可以自動降級;
(2)警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,併發送告警;
(3)錯誤:比如可用率低於90%,或者數據庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;
(4)嚴重錯誤:比如因爲特殊原因數據錯誤了,此時需要緊急人工降級。

服務降級的目的,是爲了防止Redis服務故障,導致數據庫跟着一起發生雪崩問題。因此,對於不重要的緩存數據,可以採取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去數據庫查詢,而是直接返回默認值給用戶。

3、熱點數據和冷數據是什麼

熱點數據,緩存才有價值
對於冷數據而言,大部分數據可能還沒有再次訪問到就已經被擠出內存,不僅佔用內存,而且價值不大。頻繁修改的數據,看情況考慮使用緩存
對於上面兩個例子,壽星列表、導航信息都存在一個特點,就是信息修改頻率不高,讀取通常非常高的場景。
對於熱點數據,比如我們的某IM產品,生日祝福模塊,當天的壽星列表,緩存以後可能讀取數十萬次。再舉個例子,某導航產品,我們將導航信息,緩存以後可能讀取數百萬次。
**數據更新前至少讀取兩次,**緩存纔有意義。這個是最基本的策略,如果緩存還沒有起作用就失效了,那就沒有太大價值了。
那存不存在,修改頻率很高,但是又不得不考慮緩存的場景呢?有!比如,這個讀取接口對數據庫的壓力很大,但是又是熱點數據,這個時候就需要考慮通過緩存手段,減少數據庫的壓力,比如我們的某助手產品的,點贊數,收藏數,分享數等是非常典型的熱點數據,但是又不斷變化,此時就需要將數據同步保存到Redis緩存,減少數據庫壓力。

4、Memcache與Redis的區別都有哪些?

1)、存儲方式 Memecache把數據全部存在內存之中,斷電後會掛掉,數據不能超過內存大小。 Redis有部份存在硬盤上,redis可以持久化其數據
2)、數據支持類型 memcached所有的值均是簡單的字符串,redis作爲其替代者,支持更爲豐富的數據類型 ,提供list,set,zset,hash等數據結構的存儲
3)、使用底層模型不同 它們之間底層實現方式 以及與客戶端之間通信的應用協議不一樣。 Redis直接自己構建了VM 機制 ,因爲一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。
4). value 值大小不同:Redis 最大可以達到 1gb;memcache 只有 1mb。
5)redis的速度比memcached快很多
6)Redis支持數據的備份,即master-slave模式的數據備份。

5、單線程的redis爲什麼這麼快

(一)純內存操作
(二)單線程操作,避免了頻繁的上下文切換
(三)採用了非阻塞I/O多路複用機制

6、redis的數據類型,以及每種數據類型的使用場景

回答:一共五種
(一)String
這個其實沒啥好說的,最常規的set/get操作,value可以是String也可以是數字。一般做一些複雜的計數功能的緩存。
(二)hash
這裏value存放的是結構化的對象,比較方便的就是操作其中的某個字段。博主在做單點登錄的時候,就是用這種數據結構存儲用戶信息,以cookieId作爲key,設置30分鐘爲緩存過期時間,能很好的模擬出類似session的效果。
(三)list
使用List的數據結構,可以做簡單的消息隊列的功能。另外還有一個就是,可以利用lrange命令,做基於redis的分頁功能,性能極佳,用戶體驗好。本人還用一個場景,很合適—取行情信息。就也是個生產者和消費者的場景。LIST可以很好的完成排隊,先進先出的原則。
(四)set
因爲set堆放的是一堆不重複值的集合。所以可以做全局去重的功能。爲什麼不用JVM自帶的Set進行去重?因爲我們的系統一般都是集羣部署,使用JVM自帶的Set,比較麻煩,難道爲了一個做一個全局去重,再起一個公共服務,太麻煩了。
另外,就是利用交集、並集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。
(五)sorted set
sorted set多了一個權重參數score,集合中的元素能夠按score進行排列。可以做排行榜應用,取TOP N操作。

7、redis的過期策略以及內存淘汰機制

redis採用的是定期刪除+惰性刪除策略。
爲什麼不用定時刪除策略?
定時刪除,用一個定時器來負責監視key,過期則自動刪除。雖然內存及時釋放,但是十分消耗CPU資源。在大併發請求下,CPU要將時間應用在處理請求,而不是刪除key,因此沒有采用這一策略.
定期刪除+惰性刪除是如何工作的呢?
定期刪除,redis默認每個100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每個100ms將所有的key檢查一次,而是隨機抽取進行檢查(如果每隔100ms,全部key進行檢查,redis豈不是卡死)。因此,如果只採用定期刪除策略,會導致很多key到時間沒有刪除。
於是,惰性刪除派上用場。也就是說在你獲取某個key的時候,redis會檢查一下,這個key如果設置了過期時間那麼是否過期了?如果過期了此時就會刪除。
採用定期刪除+惰性刪除就沒其他問題了麼?
不是的,如果定期刪除沒刪除key。然後你也沒即時去請求key,也就是說惰性刪除也沒生效。這樣,redis的內存會越來越高。那麼就應該採用內存淘汰機制。
在redis.conf中有一行配置

maxmemory-policy volatile-lru

該配置就是配內存淘汰策略的(什麼,你沒配過?好好反省一下自己)
volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
no-enviction(驅逐):禁止驅逐數據,新寫入操作會報錯
ps:如果沒有設置 expire 的key, 不滿足先決條件(prerequisites); 那麼 volatile-lru, volatile-random 和 volatile-ttl 策略的行爲, 和 noeviction(不刪除) 基本上一致。

8、Redis 爲什麼是單線程的

官方FAQ表示,因爲Redis是基於內存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器內存的大小或者網絡帶寬。既然單線程容易實現,而且CPU不會成爲瓶頸,那就順理成章地採用單線程的方案了(畢竟採用多線程會有很多麻煩!)Redis利用隊列技術將併發訪問變爲串行訪問
1)絕大部分請求是純粹的內存操作(非常快速)2)採用單線程,避免了不必要的上下文切換和競爭條件
3)非阻塞IO優點:

  • 速度快,因爲數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間複雜度都是O(1)

  • 支持豐富數據類型,支持string,list,set,sorted set,hash

  • 支持事務,操作都是原子性,所謂的原子性就是對數據的更改要麼全部執行,要麼全部不執行

  • 豐富的特性:可用於緩存,消息,按key設置過期時間,過期後將會自動刪除如何解決redis的併發競爭key問題

同時有多個子系統去set一個key。這個時候要注意什麼呢? 不推薦使用redis的事務機制。因爲我們的生產環境,基本都是redis集羣環境,做了數據分片操作。你一個事務中有涉及到多個key操作的時候,這多個key不一定都存儲在同一個redis-server上。因此,redis的事務機制,十分雞肋。
(1)如果對這個key操作,不要求順序: 準備一個分佈式鎖,大家去搶鎖,搶到鎖就做set操作即可
(2)如果對這個key操作,要求順序: 分佈式鎖+時間戳。 假設這會系統B先搶到鎖,將key1設置爲{valueB 3:05}。接下來系統A搶到鎖,發現自己的valueA的時間戳早於緩存中的時間戳,那就不做set操作了。以此類推。
(3) 利用隊列,將set方法變成串行訪問也可以redis遇到高併發,如果保證讀寫key的一致性
對redis的操作都是具有原子性的,是線程安全的操作,你不用考慮併發問題,redis內部已經幫你處理好併發的問題了。

9、Redis 常見性能問題和解決方案?

(1) Master 最好不要做任何持久化工作,如 RDB 內存快照和 AOF 日誌文件
(2) 如果數據比較重要,某個 Slave 開啓 AOF 備份數據,策略設置爲每秒同步一次
(3) 爲了主從複製的速度和連接的穩定性, Master 和 Slave 最好在同一個局域網內
(4) 儘量避免在壓力很大的主庫上增加從庫
(5) 主從複製不要用圖狀結構,用單向鏈表結構更爲穩定,即: Master <- Slave1 <- Slave2 <-Slave3…

10、爲什麼Redis的操作是原子性的,怎麼保證原子性的?

對於Redis而言,命令的原子性指的是:一個操作的不可以再分,操作要麼執行,要麼不執行。
Redis的操作之所以是原子性的,是因爲Redis是單線程的。
Redis本身提供的所有API都是原子操作,Redis中的事務其實是要保證批量操作的原子性。
多個命令在併發中也是原子性的嗎?
不一定, 將get和set改成單命令操作,incr 。使用Redis的事務,或者使用Redis+Lua==的方式實現.

11、Redis事務

Redis事務功能是通過MULTI、EXEC、DISCARD和WATCH 四個原語實現的
Redis會將一個事務中的所有命令序列化,然後按順序執行。
1.redis 不支持回滾“Redis 在事務失敗時不進行回滾,而是繼續執行餘下的命令”, 所以 Redis 的內部可以保持簡單且快速。
2.如果在一個事務中的命令出現錯誤,那麼所有的命令都不會執行;
3.如果在一個事務中出現運行錯誤,那麼正確的命令會被執行。

1)MULTI命令用於開啓一個事務,它總是返回OK。 MULTI執行之後,客戶端可以繼續向服務器發送任意多條命令,這些命令不會立即被執行,而是被放到一個隊列中,當EXEC命令被調用時,所有隊列中的命令纔會被執行。
2)EXEC:執行所有事務塊內的命令。返回事務塊內所有命令的返回值,按命令執行的先後順序排列。 當操作被打斷時,返回空值 nil 。
3)通過調用DISCARD,客戶端可以清空事務隊列,並放棄執行事務, 並且客戶端會從事務狀態中退出。
4)WATCH 命令可以爲 Redis 事務提供 check-and-set (CAS)行爲。 可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行,監控一直持續到EXEC命令。

SpringCloud篇

1、什麼是SpringCloud

Spring cloud 流應用程序啓動器是基於 Spring Boot 的 Spring 集成應用程序,提供與外部系統的集成。Spring cloud Task,一個生命週期短暫的微服務框架,用於快速構建執行有限數據處理的應用程序。

2、什麼是微服務

微服務架構是一種架構模式或者說是一種架構風格,它提倡將單一應用程序劃分爲一組小的服務,每個服務運行在其獨立的自己的進程中,服務之間相互協調、互相配合,爲用戶提供最終價值。服務之間採用輕量級的通信機制互相溝通(通常是基於HTTP的RESTful API),每個服務都圍繞着具體的業務進行構建,並且能夠被獨立的構建在生產環境、類生產環境等。另外,應避免統一的、集中式的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建,可以有一個非常輕量級的集中式管理來協調這些服務,可以使用不同的語言來編寫服務,也可以使用不同的數據存儲。

3、SpringCloud有什麼優勢

使用 Spring Boot 開發分佈式微服務時,我們面臨以下問題

(1)與分佈式系統相關的複雜性-這種開銷包括網絡問題,延遲開銷,帶寬問題,安全問題。

(2)服務發現-服務發現工具管理羣集中的流程和服務如何查找和互相交談。它涉及一個服務目錄,在該目錄中註冊服務,然後能夠查找並連接到該目錄中的服務。

(3)冗餘-分佈式系統中的冗餘問題。

(4)負載平衡 --負載平衡改善跨多個計算資源的工作負荷,諸如計算機,計算機集羣,網絡鏈路,中央處理單元,或磁盤驅動器的分佈。

(5)性能-問題 由於各種運營開銷導致的性能問題。

(6)部署複雜性-Devops 技能的要求。

4、 什麼是服務熔斷?什麼是服務降級?

熔斷機制是應對雪崩效應的一種微服務鏈路保護機制。當某個微服務不可用或者響應時間太長時,會進行服務降級,進而熔斷該節點微服務的調用,快速返回“錯誤”的響應信息。當檢測到該節點微服務調用響應正常後恢復調用鏈路。在SpringCloud框架裏熔斷機制通過Hystrix實現,Hystrix會監控微服務間調用的狀況,當失敗的調用到一定閾值,缺省是5秒內調用20次,如果失敗,就會啓動熔斷機制。

服務降級,一般是從整體負荷考慮。就是當某個服務熔斷之後,服務器將不再被調用,此時客戶端可以自己準備一個本地的fallback回調,返回一個缺省值。這樣做,雖然水平下降,但好歹可用,比直接掛掉強。

Hystrix相關注解
@EnableHystrix:開啓熔斷
@HystrixCommand(fallbackMethod=”XXX”):聲明一個失敗回滾處理函數XXX,當被註解的方法執行超時(默認是1000毫秒),就會執行fallback函數,返回錯誤提示。

a3e7daec2343b9188bfd745b7dfe0a93693.jpg

5、 Eureka和zookeeper都可以提供服務註冊與發現的功能,請說說兩個的區別?

Zookeeper保證了CP(C:一致性,P:分區容錯性),Eureka保證了AP(A:高可用)
1.當向註冊中心查詢服務列表時,我們可以容忍註冊中心返回的是幾分鐘以前的信息,但不能容忍直接down掉不可用。也就是說,服務註冊功能對高可用性要求比較高,但zk會出現這樣一種情況,當master節點因爲網絡故障與其他節點失去聯繫時,剩餘節點會重新選leader。問題在於,選取leader時間過長,30 ~ 120s,且選取期間zk集羣都不可用,這樣就會導致選取期間註冊服務癱瘓。在雲部署的環境下,因網絡問題使得zk集羣失去master節點是較大概率會發生的事,雖然服務能夠恢復,但是漫長的選取時間導致的註冊長期不可用是不能容忍的。

2.Eureka保證了可用性,Eureka各個節點是平等的,幾個節點掛掉不會影響正常節點的工作,剩餘的節點仍然可以提供註冊和查詢服務。而Eureka的客戶端向某個Eureka註冊或發現時發生連接失敗,則會自動切換到其他節點,只要有一臺Eureka還在,就能保證註冊服務可用,只是查到的信息可能不是最新的。除此之外,Eureka還有自我保護機制,如果在15分鐘內超過85%的節點沒有正常的心跳,那麼Eureka就認爲客戶端與註冊中心發生了網絡故障,此時會出現以下幾種情況:
①、Eureka不在從註冊列表中移除因爲長時間沒有收到心跳而應該過期的服務。
②、Eureka仍然能夠接受新服務的註冊和查詢請求,但是不會被同步到其他節點上(即保證當前節點仍然可用)
③、當網絡穩定時,當前實例新的註冊信息會被同步到其他節點。

因此,Eureka可以很好的應對因網絡故障導致部分節點失去聯繫的情況,而不會像Zookeeper那樣使整個微服務癱瘓

6、SpringBoot和SpringCloud的區別?

SpringBoot專注於快速方便的開發單個個體微服務。

SpringCloud是關注全局的微服務協調整理治理框架,它將SpringBoot開發的一個個單體微服務整合並管理起來,

爲各個微服務之間提供,配置管理、服務發現、斷路器、路由、微代理、事件總線、全局鎖、決策競選、分佈式會話等等集成服務

SpringBoot可以離開SpringCloud獨立使用開發項目, 但是SpringCloud離不開SpringBoot ,屬於依賴的關係.

SpringBoot專注於快速、方便的開發單個微服務個體,SpringCloud關注全局的服務治理框架。

7、負載平衡的意義什麼?

在計算中,負載平衡可以改善跨計算機,計算機集羣,網絡鏈接,中央處理單元或磁盤驅動器等多種計算資源的工作負載分佈。負載平衡旨在優化資源使用,最大化吞吐量,最小化響應時間並避免任何單一資源
的過載。使用多個組件進行負載平衡而不是單個組件可能會通過冗餘來提高可靠性和可用性。負載平衡通常涉及專用軟件或硬件,例如多層交換機或域名系統服務器進程。

8、什麼是Hystrix?它如何實現容錯?

Hystrix是一個延遲和容錯庫,旨在隔離遠程系統,服務和第三方庫的訪問點,當出現故障是不可避免的故障時,停止級聯故障並在複雜的分佈式系統中實現彈性。

通常對於使用微服務架構開發的系統,涉及到許多微服務。這些微服務彼此協作。

思考以下微服務

img

假設如果上圖中的微服務9失敗了,那麼使用傳統方法我們將傳播一個異常。但這仍然會導致整個系統崩潰。

隨着微服務數量的增加,這個問題變得更加複雜。微服務的數量可以高達1000.這是hystrix出現的地方 我們將使用Hystrix在這種情況下的Fallback方法功能。我們有兩個服務employee-consumer使用由employee-consumer公開的服務。

簡化圖如下所示

img

現在假設由於某種原因,employee-producer公開的服務會拋出異常。我們在這種情況下使用Hystrix定義了一個回退方法。這種後備方法應該具有與公開服務相同的返回類型。如果暴露服務中出現異常,則回退方法將返回一些值。

9、什麼是Hystrix斷路器?我們需要它嗎?

由於某些原因,employee-consumer公開服務會引發異常。在這種情況下使用Hystrix我們定義了一個回退方法。如果在公開服務中發生異常,則回退方法返回一些默認值。

img

如果firstPage method() 中的異常繼續發生,則Hystrix電路將中斷,並且員工使用者將一起跳過firtsPage方法,並直接調用回退方法。 斷路器的目的是給第一頁方法或第一頁方法可能調用的其他方法留出時間,並導致異常恢復。可能發生的情況是,在負載較小的情況下,導致異常的問題有更好的恢復機會 。

img

10、說說 RPC 的實現原理

首先需要有處理網絡連接通訊的模塊,負責連接建立、管理和消息的傳輸。其次需要有編
解碼的模塊,因爲網絡通訊都是傳輸的字節碼,需要將我們使用的對象序列化和反序列
化。剩下的就是客戶端和服務器端的部分,服務器端暴露要開放的服務接口,客戶調用服
務接口的一個代理實現,這個代理實現負責收集數據、編碼並傳輸給服務器然後等待結果
返回。

Nginx篇

1、簡述一下什麼是Nginx,它有什麼優勢和功能?

Nginx是一個web服務器和方向代理服務器,用於HTTP、HTTPS、SMTP、POP3和IMAP協議。因它的穩定性、豐富的功能集、示例配置文件和低系統資源的消耗而聞名。

Nginx—Ngine X,是一款免費的、自由的、開源的、高性能HTTP服務器和反向代理服務器;也是一個IMAP、POP3、SMTP代理服務器;Nginx以其高性能、穩定性、豐富的功能、簡單的配置和低資源消耗而聞名。

也就是說Nginx本身就可以託管網站(類似於Tomcat一樣),進行Http服務處理,也可以作爲反向代理服務器 、負載均衡器和HTTP緩存。

Nginx 解決了服務器的C10K(就是在一秒之內連接客戶端的數目爲10k即1萬)問題。它的設計不像傳統的服務器那樣使用線程處理請求,而是一個更加高級的機制—事件驅動機制,是一種異步事件驅動結構。

優點:
(1)更快
這表現在兩個方面:一方面,在正常情況下,單次請求會得到更快的響應;另一方面,在高峯期(如有數以萬計的併發請求),Nginx可以比其他Web服務器更快地響應請求。
(2)高擴展性,跨平臺
Nginx的設計極具擴展性,它完全是由多個不同功能、不同層次、不同類型且耦合度極低的模塊組成。因此,當對某一個模塊修復Bug或進行升級時,可以專注於模塊自身,無須在意其他。而且在HTTP模塊中,還設計了HTTP過濾器模塊:一個正常的HTTP模塊在處理完請求後,會有一串HTTP過濾器模塊對請求的結果進行再處理。這樣,當我們開發一個新的HTTP模塊時,不但可以使用諸如HTTP核心模塊、events模塊、log模塊等不同層次或者不同類型的模塊,還可以原封不動地複用大量已有的HTTP過濾器模塊。這種低耦合度的優秀設計,造就了Nginx龐大的第三方模塊,當然,公開的第三方模塊也如官方發佈的模塊一樣容易使用。
Nginx的模塊都是嵌入到二進制文件中執行的,無論官方發佈的模塊還是第三方模塊都是如此。這使得第三方模塊一樣具備極其優秀的性能,充分利用Nginx的高併發特性,因此,許多高流量的網站都傾向於開發符合自己業務特性的定製模塊。
(3)高可靠性:用於反向代理,宕機的概率微乎其微
高可靠性是我們選擇Nginx的最基本條件,因爲Nginx的可靠性是大家有目共睹的,很多家高流量網站都在覈心服務器上大規模使用Nginx。Nginx的高可靠性來自於其核心框架代碼的優秀設計、模塊設計的簡單性;另外,官方提供的常用模塊都非常穩定,每個worker進程相對獨立,master進程在1個worker進程出錯時可以快速“拉起”新的worker子進程提供服務。

(4)低內存消耗
一般情況下,10 000個非活躍的HTTP Keep-Alive連接在Nginx中僅消耗2.5MB的內存,這是Nginx支持高併發連接的基礎。
(5)單機支持10萬以上的併發連接
這是一個非常重要的特性!隨着互聯網的迅猛發展和互聯網用戶數量的成倍增長,各大公司、網站都需要應付海量併發請求,一個能夠在峯值期頂住10萬以上併發請求的Server,無疑會得到大家的青睞。理論上,Nginx支持的併發連接上限取決於內存,10萬遠未封頂。當然,能夠及時地處理更多的併發請求,是與業務特點緊密相關的。
(6)熱部署
master管理進程與worker工作進程的分離設計,使得Nginx能夠提供熱部署功能,即可以在7×24小時不間斷服務的前提下,升級Nginx的可執行文件。當然,它也支持不停止服務就更新配置項、更換日誌文件等功能。
(7)最自由的BSD許可協議
這是Nginx可以快速發展的強大動力。BSD許可協議不只是允許用戶免費使用Nginx,它還允許用戶在自己的項目中直接使用或修改Nginx源碼,然後發佈。這吸引了無數開發者繼續爲Nginx貢獻自己的智慧。
以上7個特點當然不是Nginx的全部,擁有無數個官方功能模塊、第三方功能模塊使得Nginx能夠滿足絕大部分應用場景,這些功能模塊間可以疊加以實現更加強大、複雜的功能,有些模塊還支持Nginx與Perl、Lua等腳本語言集成工作,大大提高了開發效率。這些特點促使用戶在尋找一個Web服務器時更多考慮Nginx。
選擇Nginx的核心理由還是它能在支持高併發請求的同時保持高效的服務

2、Nginx是如何處理一個HTTP請求的呢?

Nginx 是一個高性能的 Web 服務器,能夠同時處理大量的併發請求。它結合多進程機制和異步機制 ,異步機制使用的是異步非阻塞方式 ,接下來就給大家介紹一下 Nginx 的多線程機制和異步非阻塞機制 。

1、多進程機制

服務器每當收到一個客戶端時,就有 服務器主進程 ( master process )生成一個 子進程( worker process )出來和客戶端建立連接進行交互,直到連接斷開,該子進程就結束了。

使用進程的好處是各個進程之間相互獨立,不需要加鎖,減少了使用鎖對性能造成影響,同時降低編程的複雜度,降低開發成本。其次,採用獨立的進程,可以讓進程互相之間不會影響 ,如果一個進程發生異常退出時,其它進程正常工作, master 進程則很快啓動新的 worker 進程,確保服務不會中斷,從而將風險降到最低。

缺點是操作系統生成一個子進程需要進行 內存複製等操作,在資源和時間上會產生一定的開銷。當有大量請求時,會導致系統性能下降 。

2、異步非阻塞機制

每個工作進程 使用 異步非阻塞方式 ,可以處理 多個客戶端請求 。

當某個 工作進程 接收到客戶端的請求以後,調用 IO 進行處理,如果不能立即得到結果,就去 處理其他請求 (即爲 非阻塞 );而 客戶端 在此期間也 無需等待響應 ,可以去處理其他事情(即爲 異步 )。

當 IO 返回時,就會通知此 工作進程 ;該進程得到通知,暫時 掛起 當前處理的事務去 響應客戶端請求 。

3、列舉一些Nginx的特性

  1. Nginx服務器的特性包括:
  2. 反向代理/L7負載均衡器
  3. 嵌入式Perl解釋器
  4. 動態二進制升級
  5. 可用於重新編寫URL,具有非常好的PCRE支持

4、請列舉Nginx和Apache 之間的不同點

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PTgrX24M-1587954092090)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1583476168205.png)]

5、在Nginx中,如何使用未定義的服務器名稱來阻止處理請求?

只需將請求刪除的服務器就可以定義爲:

Server{
    listen 80;
    server_name "";
    return 444;
}

這裏,服務器名被保留爲一個空字符串,它將在沒有“主機”頭字段的情況下匹配請求,而一個特殊的Nginx的非標準代碼444被返回,從而終止連接。

一般推薦 worker 進程數與CPU內核數一致,這樣一來不存在大量的子進程生成和管理任務,避免了進程之間競爭CPU 資源和進程切換的開銷。而且 Nginx 爲了更好的利用 多核特性 ,提供了 CPU 親緣性的綁定選項,我們可以將某一個進程綁定在某一個核上,這樣就不會因爲進程的切換帶來 Cache 的失效。

對於每個請求,有且只有一個工作進程 對其處理。首先,每個 worker 進程都是從 master進程 fork 過來。在 master 進程裏面,先建立好需要 listen 的 socket(listenfd) 之後,然後再 fork 出多個 worker 進程。

所有 worker 進程的 listenfd 會在新連接到來時變得可讀 ,爲保證只有一個進程處理該連接,所有 worker 進程在註冊 listenfd 讀事件前搶佔 accept_mutex ,搶到互斥鎖的那個進程註冊 listenfd 讀事件 ,在讀事件裏調用 accept 接受該連接。

當一個 worker 進程在 accept 這個連接之後,就開始讀取請求、解析請求、處理請求,產生數據後,再返回給客戶端 ,最後才斷開連接。這樣一個完整的請求就是這樣的了。我們可以看到,一個請求,完全由 worker 進程來處理,而且只在一個 worker 進程中處理。

640?

在 Nginx 服務器的運行過程中, 主進程和工作進程 需要進程交互。交互依賴於 Socket 實現的管道來實現。

6、請解釋Nginx服務器上的Master和Worker進程分別是什麼?

  • 主程序 Master process 啓動後,通過一個 for 循環來 接收 和 處理外部信號 ;
  • 主進程通過 fork() 函數產生 worker 子進程 ,每個子進程執行一個 for循環來實現Nginx服務器對事件的接收和處理 。

640?

7、請解釋代理中的正向代理和反向代理

首先,代理服務器一般指局域網內部的機器通過代理服務器發送請求到互聯網上的服務器,代理服務器一般作用在客戶端。例如:GoAgent翻牆軟件。我們的客戶端在進行翻牆操作的時候,我們使用的正是正向代理,通過正向代理的方式,在我們的客戶端運行一個軟件,將我們的HTTP請求轉發到其他不同的服務器端,實現請求的分發。

640?

反向代理服務器作用在服務器端,它在服務器端接收客戶端的請求,然後將請求分發給具體的服務器進行處理,然後再將服務器的相應結果反饋給客戶端。Nginx就是一個反向代理服務器軟件。

640?

從上圖可以看出:客戶端必須設置正向代理服務器,當然前提是要知道正向代理服務器的IP地址,還有代理程序的端口。
反向代理正好與正向代理相反,對於客戶端而言代理服務器就像是原始服務器,並且客戶端不需要進行任何特別的設置。客戶端向反向代理的命名空間(name-space)中的內容發送普通請求,接着反向代理將判斷向何處(原始服務器)轉交請求,並將獲得的內容返回給客戶端。

640?

8、解釋Nginx用途

Nginx服務器的最佳用法是在網絡上部署動態HTTP內容,使用SCGI、WSGI應用程序服務器、用於腳本的FastCGI處理程序。它還可以作爲負載均衡器。

MQ篇

1、爲什麼使用MQ

核心:解耦,異步,削峯

**1)解耦:**A 系統發送數據到 BCD 三個系統,通過接口調用發送。如果 E 系統也要這個數據呢?那如果 C 系統現在不需要了呢?A 系統負責人幾乎崩潰…A 系統跟其它各種亂七八糟的系統嚴重耦合,A 系統產生一條比較關鍵的數據,很多系統都需要 A 系統將這個數據發送過來。如果使用 MQ,A 系統產生一條數據,發送到 MQ 裏面去,哪個系統需要數據自己去 MQ 裏面消費。如果新系統需要數據,直接從 MQ 裏消費即可;如果某個系統不需要這條數據了,就取消對 MQ 消息的消費即可。這樣下來,A 系統壓根兒不需要去考慮要給誰發送數據,不需要維護這個代碼,也不需要考慮人家是否調用成功、失敗超時等情況。

就是一個系統或者一個模塊,調用了多個系統或者模塊,互相之間的調用很複雜,維護起來很麻煩。但是其實這個調用是不需要直接同步調用接口的,如果用 MQ 給它異步化解耦。

**(2)異步:**A 系統接收一個請求,需要在自己本地寫庫,還需要在 BCD 三個系統寫庫,自己本地寫庫要 3ms,BCD 三個系統分別寫庫要 300ms、450ms、200ms。最終請求總延時是 3 + 300 + 450 + 200 = 953ms,接近 1s,用戶感覺搞個什麼東西,慢死了慢死了。用戶通過瀏覽器發起請求。如果使用 MQ,那麼 A 系統連續發送 3 條消息到 MQ 隊列中,假如耗時 5ms,A 系統從接受一個請求到返回響應給用戶,總時長是 3 + 5 = 8ms。

**(3)削峯:**減少高峯時期對服務器壓力。

2、MQ優缺點

優點上面已經說了,就是在特殊場景下有其對應的好處,解耦、異步、削峯。

缺點有以下幾個:

系統可用性降低
系統引入的外部依賴越多,越容易掛掉。萬一 MQ 掛了,MQ 一掛,整套系統崩潰,你不就完了?

系統複雜度提高
硬生生加個 MQ 進來,你怎麼保證消息沒有重複消費?怎麼處理消息丟失的情況?怎麼保證消息傳遞的順序性?問題一大堆。

一致性問題
A 系統處理完了直接返回成功了,人都以爲你這個請求就成功了;但是問題是,要是 BCD 三個系統那裏,BD 兩個系統寫庫成功了,結果 C 系統寫庫失敗了,咋整?你這數據就不一致了。

3、Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什麼區別?

對於吞吐量來說kafka和RocketMQ支撐高吞吐,ActiveMQ和RabbitMQ比他們低一個數量級。對於延遲量來說RabbitMQ是最低的。

1.從社區活躍度

按照目前網絡上的資料,RabbitMQ 、activeM 、ZeroMQ 三者中,綜合來看,RabbitMQ 是首選。

2.持久化消息比較

ActiveMq 和RabbitMq 都支持。持久化消息主要是指我們機器在不可抗力因素等情況下掛掉了,消息不會丟失的機制。

3.綜合技術實現

可靠性、靈活的路由、集羣、事務、高可用的隊列、消息排序、問題追蹤、可視化管理工具、插件系統等等。

RabbitMq / Kafka 最好,ActiveMq 次之,ZeroMq 最差。當然ZeroMq 也可以做到,不過自己必須手動寫代碼實現,代碼量不小。尤其是可靠性中的:持久性、投遞確認、發佈者證實和高可用性。

4.高併發

毋庸置疑,RabbitMQ 最高,原因是它的實現語言是天生具備高併發高可用的erlang 語言。

5.比較關注的比較, RabbitMQ 和 Kafka

RabbitMq 比Kafka 成熟,在可用性上,穩定性上,可靠性上, RabbitMq 勝於 Kafka (理論上)。

另外,Kafka 的定位主要在日誌等方面, 因爲Kafka 設計的初衷就是處理日誌的,可以看做是一個日誌(消息)系統一個重要組件,針對性很強,所以 如果業務方面還是建議選擇 RabbitMq 。

還有就是,Kafka 的性能(吞吐量、TPS )比RabbitMq 要高出來很多。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oKnb2oih-1587954092114)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1583479010214.png)]

4、如何保證高可用的?

RabbitMQ 是比較有代表性的,因爲是基於主從(非分佈式)做高可用性的,我們就以 RabbitMQ 爲例子講解第一種 MQ 的高可用性怎麼實現。RabbitMQ 有三種模式:單機模式、普通集羣模式、鏡像集羣模式。

單機模式,就是 Demo 級別的,一般就是你本地啓動了玩玩兒的?,沒人生產用單機模式

普通集羣模式,意思就是在多臺機器上啓動多個 RabbitMQ 實例,每個機器啓動一個。你創建的 queue,只會放在一個 RabbitMQ 實例上,但是每個實例都同步 queue 的元數據(元數據可以認爲是 queue 的一些配置信息,通過元數據,可以找到 queue 所在實例)。你消費的時候,實際上如果連接到了另外一個實例,那麼那個實例會從 queue 所在實例上拉取數據過來。這方案主要是提高吞吐量的,就是說讓集羣中多個節點來服務某個 queue 的讀寫操作。

鏡像集羣模式:這種模式,纔是所謂的 RabbitMQ 的高可用模式。跟普通集羣模式不一樣的是,在鏡像集羣模式下,你創建的 queue,無論元數據還是 queue 裏的消息都會存在於多個實例上,就是說,每個 RabbitMQ 節點都有這個 queue 的一個完整鏡像,包含 queue 的全部數據的意思。然後每次你寫消息到 queue 的時候,都會自動把消息同步到多個實例的 queue 上。RabbitMQ 有很好的管理控制檯,就是在後臺新增一個策略,這個策略是鏡像集羣模式的策略,指定的時候是可以要求數據同步到所有節點的,也可以要求同步到指定數量的節點,再次創建 queue 的時候,應用這個策略,就會自動將數據同步到其他的節點上去了。這樣的話,好處在於,你任何一個機器宕機了,沒事兒,其它機器(節點)還包含了這個 queue 的完整數據,別的 consumer 都可以到其它節點上去消費數據。壞處在於,第一,這個性能開銷也太大了吧,消息需要同步到所有機器上,導致網絡帶寬壓力和消耗很重!RabbitMQ 一個 queue 的數據都是放在一個節點裏的,鏡像集羣下,也是每個節點都放這個 queue 的完整數據。

Kafka 一個最基本的架構認識:由多個 broker 組成,每個 broker 是一個節點;你創建一個 topic,這個 topic 可以劃分爲多個 partition,每個 partition 可以存在於不同的 broker 上,每個 partition 就放一部分數據。這就是天然的分佈式消息隊列,就是說一個 topic 的數據,是分散放在多個機器上的,每個機器就放一部分數據。Kafka 0.8 以後,提供了 HA 機制,就是 replica(複製品) 副本機制。每個 partition 的數據都會同步到其它機器上,形成自己的多個 replica 副本。所有 replica 會選舉一個 leader 出來,那麼生產和消費都跟這個 leader 打交道,然後其他 replica 就是 follower。寫的時候,leader 會負責把數據同步到所有 follower 上去,讀的時候就直接讀 leader 上的數據即可。只能讀寫 leader?很簡單,要是你可以隨意讀寫每個 follower,那麼就要 care 數據一致性的問題,系統複雜度太高,很容易出問題。Kafka 會均勻地將一個 partition 的所有 replica 分佈在不同的機器上,這樣纔可以提高容錯性。因爲如果某個 broker 宕機了,沒事兒,那個 broker上面的 partition 在其他機器上都有副本的,如果這上面有某個 partition 的 leader,那麼此時會從 follower 中重新選舉一個新的 leader 出來,大家繼續讀寫那個新的 leader 即可。這就有所謂的高可用性了。寫數據的時候,生產者就寫 leader,然後 leader 將數據落地寫本地磁盤,接着其他 follower 自己主動從 leader 來 pull 數據。一旦所有 follower 同步好數據了,就會發送 ack 給 leader,leader 收到所有 follower 的 ack 之後,就會返回寫成功的消息給生產者。(當然,這只是其中一種模式,還可以適當調整這個行爲)消費的時候,只會從 leader 去讀,但是隻有當一個消息已經被所有 follower 都同步成功返回 ack 的時候,這個消息纔會被消費者讀到。

5、如何保證消息的可靠傳輸?如果消息丟了怎麼辦

數據的丟失問題,可能出現在生產者、MQ、消費者中

生產者丟失:生產者將數據發送到 RabbitMQ 的時候,可能數據就在半路給搞丟了,因爲網絡問題啥的,都有可能。此時可以選擇用 RabbitMQ 提供的事務功能,就是生產者發送數據之前開啓 RabbitMQ 事務channel.txSelect,然後發送消息,如果消息沒有成功被 RabbitMQ 接收到,那麼生產者會收到異常報錯,此時就可以回滾事務channel.txRollback,然後重試發送消息;如果收到了消息,那麼可以提交事務channel.txCommit。吞吐量會下來,因爲太耗性能。所以一般來說,如果你要確保說寫 RabbitMQ 的消息別丟,可以開啓confirm模式,在生產者那裏設置開啓confirm模式之後,你每次寫的消息都會分配一個唯一的 id,然後如果寫入了 RabbitMQ 中,RabbitMQ 會給你回傳一個ack消息,告訴你說這個消息 ok 了。如果 RabbitMQ 沒能處理這個消息,會回調你一個nack接口,告訴你這個消息接收失敗,你可以重試。而且你可以結合這個機制自己在內存裏維護每個消息 id 的狀態,如果超過一定時間還沒接收到這個消息的回調,那麼你可以重發。事務機制和cnofirm機制最大的不同在於,事務機制是同步的,你提交一個事務之後會阻塞在那兒,但是confirm機制是異步的,你發送個消息之後就可以發送下一個消息,然後那個消息RabbitMQ 接收了之後會異步回調你一個接口通知你這個消息接收到了。所以一般在生產者這塊避免數據丟失,都是用confirm機制的。

MQ中丟失:就是 RabbitMQ 自己弄丟了數據,這個你必須開啓 RabbitMQ 的持久化,就是消息寫入之後會持久化到磁盤,哪怕是 RabbitMQ 自己掛了,恢復之後會自動讀取之前存儲的數據,一般數據不會丟。設置持久化有兩個步驟:創建 queue 的時候將其設置爲持久化,這樣就可以保證 RabbitMQ 持久化 queue 的元數據,但是不會持久化 queue 裏的數據。第二個是發送消息的時候將消息的 deliveryMode 設置爲 2,就是將消息設置爲持久化的,此時 RabbitMQ 就會將消息持久化到磁盤上去。必須要同時設置這兩個持久化才行,RabbitMQ 哪怕是掛了,再次重啓,也會從磁盤上重啓恢復 queue,恢復這個 queue 裏的數據。持久化可以跟生產者那邊的confirm機制配合起來,只有消息被持久化到磁盤之後,纔會通知生產者ack了,所以哪怕是在持久化到磁盤之前,RabbitMQ 掛了,數據丟了,生產者收不到ack,你也是可以自己重發的。注意,哪怕是你給 RabbitMQ 開啓了持久化機制,也有一種可能,就是這個消息寫到了 RabbitMQ 中,但是還沒來得及持久化到磁盤上,結果不巧,此時 RabbitMQ 掛了,就會導致內存裏的一點點數據丟失。

消費端丟失:你消費的時候,剛消費到,還沒處理,結果進程掛了,比如重啓了,那麼就尷尬了,RabbitMQ 認爲你都消費了,這數據就丟了。這個時候得用 RabbitMQ 提供的ack機制,簡單來說,就是你關閉 RabbitMQ 的自動ack,可以通過一個 api 來調用就行,然後每次你自己代碼裏確保處理完的時候,再在程序裏ack一把。這樣的話,如果你還沒處理完,不就沒有ack?那 RabbitMQ 就認爲你還沒處理完,這個時候 RabbitMQ 會把這個消費分配給別的 consumer 去處理,消息是不會丟的。
img

6、如何保證消息的順序性

先看看順序會錯亂的場景:RabbitMQ:一個 queue,多個 consumer,這不明顯亂了;

img

解決:

img

7、 如何解決消息隊列的延時以及過期失效問題?消息隊列滿了以後該怎麼處理?有幾百萬消息持續積壓幾小時,說說怎麼解決?

消息積壓處理辦法:臨時緊急擴容:

先修復 consumer 的問題,確保其恢復消費速度,然後將現有 cnosumer 都停掉。
新建一個 topic,partition 是原來的 10 倍,臨時建立好原先 10 倍的 queue 數量。
然後寫一個臨時的分發數據的 consumer 程序,這個程序部署上去消費積壓的數據,消費之後不做耗時的處理,直接均勻輪詢寫入臨時建立好的 10 倍數量的 queue。
接着臨時徵用 10 倍的機器來部署 consumer,每一批 consumer 消費一個臨時 queue 的數據。這種做法相當於是臨時將 queue 資源和 consumer 資源擴大 10 倍,以正常的 10 倍速度來消費數據。
等快速消費完積壓數據之後,得恢復原先部署的架構,重新用原先的 consumer 機器來消費消息。
MQ中消息失效:假設你用的是 RabbitMQ,RabbtiMQ 是可以設置過期時間的,也就是 TTL。如果消息在 queue 中積壓超過一定的時間就會被 RabbitMQ 給清理掉,這個數據就沒了。那這就是第二個坑了。這就不是說數據會大量積壓在 mq 裏,而是大量的數據會直接搞丟。我們可以採取一個方案,就是批量重導,這個我們之前線上也有類似的場景幹過。就是大量積壓的時候,我們當時就直接丟棄數據了,然後等過了高峯期以後,比如大家一起喝咖啡熬夜到晚上12點以後,用戶都睡覺了。這個時候我們就開始寫程序,將丟失的那批數據,寫個臨時程序,一點一點的查出來,然後重新灌入 mq 裏面去,把白天丟的數據給他補回來。也只能是這樣了。假設 1 萬個訂單積壓在 mq 裏面,沒有處理,其中 1000 個訂單都丟了,你只能手動寫程序把那 1000 個訂單給查出來,手動發到 mq 裏去再補一次。

mq消息隊列塊滿了:如果消息積壓在 mq 裏,你很長時間都沒有處理掉,此時導致 mq 都快寫滿了,咋辦?這個還有別的辦法嗎?沒有,誰讓你第一個方案執行的太慢了,你臨時寫程序,接入數據來消費,消費一個丟棄一個,都不要了,快速消費掉所有的消息。然後走第二個方案,到了晚上再補數據吧。

8、設計MQ的思路

比如說這個消息隊列系統,我們從以下幾個角度來考慮一下:

首先這個 mq 得支持可伸縮性吧,就是需要的時候快速擴容,就可以增加吞吐量和容量,那怎麼搞?設計個分佈式的系統唄,參照一下 kafka 的設計理念,broker -> topic -> partition,每個 partition 放一個機器,就存一部分數據。如果現在資源不夠了,簡單啊,給 topic 增加 partition,然後做數據遷移,增加機器,不就可以存放更多數據,提供更高的吞吐量了?

其次你得考慮一下這個 mq 的數據要不要落地磁盤吧?那肯定要了,落磁盤才能保證別進程掛了數據就丟了。那落磁盤的時候怎麼落啊?順序寫,這樣就沒有磁盤隨機讀寫的尋址開銷,磁盤順序讀寫的性能是很高的,這就是 kafka 的思路。

其次你考慮一下你的 mq 的可用性啊?這個事兒,具體參考之前可用性那個環節講解的 kafka 的高可用保障機制。多副本 -> leader & follower -> broker 掛了重新選舉 leader 即可對外服務。

能不能支持數據 0 丟失啊?可以的,參考我們之前說的那個 kafka 數據零丟失方案。

數據結構與算法篇

1、常用的數據結構

原文:The top data structures you should know for your next coding interview

譯者:Fundebug

我們首先列出最常用的數據結構

  • 數組
  • 堆棧
  • 隊列
  • 鏈表
  • 字典樹
  • 哈希表
1. 數組

**數組(Array)**大概是最簡單,也是最常用的數據結構了。其他數據結構,比如棧和隊列都是由數組衍生出來的。

下圖展示了 1 個數組,它有 4 個元素:

img

每一個數組元素的位置由數字編號,稱爲下標或者索引(index)。大多數編程語言的數組第一個元素的下標是 0。

根據維度區分,有 2 種不同的數組:

  • 一維數組(如上圖所示)
  • 多維數組(數組的元素爲數組)

數組的基本操作

  • Insert - 在某個索引處插入元素
  • Get - 讀取某個索引處的元素
  • Delete - 刪除某個索引處的元素
  • Size - 獲取數組的長度

常見數組代碼面試題

2. 棧

撤回,即 Ctrl+Z,是我們最常見的操作之一,大多數應用都會支持這個功能。你知道它是怎麼實現的嗎?答案是這樣的:把之前的應用狀態(限制個數)保存到內存中,最近的狀態放到第一個。這時,我們需要**棧(stack)**來實現這個功能。

棧中的元素採用 LIFO (Last In First Out),即後進先出

下圖的棧有 3 個元素,3 在最上面,因此它會被第一個移除:

img

棧的基本操作

  • Push —  在棧的最上方插入元素
  • Pop — 返回棧最上方的元素,並將其刪除
  • isEmpty —  查詢棧是否爲空
  • Top —  返回棧最上方的元素,並不刪除

常見的棧代碼面試題

3. 隊列

隊列(Queue)與棧類似,都是採用線性結構存儲數據。它們的區別在於,棧採用 LIFO 方式,而隊列採用先進先出,即FIFO(First in First Out)

下圖展示了一個隊列,1 是最上面的元素,它會被第一個移除:

img

隊列的基本操作

  • Enqueue —  在隊列末尾插入元素
  • Dequeue —  將隊列第一個元素刪除
  • isEmpty —  查詢隊列是否爲空
  • Top —  返回隊列的第一個元素

常見的隊列代碼面試題

4. 鏈表

**鏈表(Linked List)**也是線性結構,它與數組看起來非常像,但是它們的內存分配方式、內部結構和插入刪除操作方式都不一樣。

鏈表是一系列節點組成的鏈,每一個節點保存了數據以及指向下一個節點的指針。鏈表頭指針指向第一個節點,如果鏈表爲空,則頭指針爲空或者爲 null。

鏈表可以用來實現文件系統、哈希表和鄰接表。

下圖展示了一個鏈表,它有 3 個節點:

img

鏈表分爲 2 種:

  • 單向鏈表
  • 雙向鏈表

鏈表的基本操作

  • InsertAtEnd —  在鏈表結尾插入元素
  • InsertAtHead —  在鏈表開頭插入元素
  • Delete —  刪除鏈表的指定元素
  • DeleteAtHead —  刪除鏈表第一個元素
  • Search —  在鏈表中查詢指定元素
  • isEmpty —  查詢鏈表是否爲空

常見的隊列代碼面試題

5. 圖

圖(graph)由多個節點(vertex)構成,節點之間闊以互相連接組成一個網絡。(x, y)表示一條邊(edge),它表示節點 x 與 y 相連。邊可能會有權值(weight/cost)

img

圖分爲兩種:

  • 無向圖
  • 有向圖

在編程語言中,圖有可能有以下兩種形式表示:

  • 鄰接矩陣(Adjacency Matrix)
  • 鄰接表(Adjacency List)

遍歷圖有兩週算法

  • 廣度優先搜索(Breadth First Search)
  • 深度優先搜索(Depth First Search)

常見的圖代碼面試題

6. 樹

**樹(Tree)**是一個分層的數據結構,由節點和連接節點的邊組成。樹是一種特殊的圖,它與圖最大的區別是沒有循環。

樹被廣泛應用在人工智能和一些複雜算法中,用來提供高效的存儲結構。

下圖是一個簡單的樹以及與樹相關的術語:

img

樹有很多分類:

  • N 叉樹(N-ary Tree)
  • 平衡樹(Balanced Tree)
  • 二叉樹(Binary Tree)
  • 二叉查找樹(Binary Search Tree)
  • 平衡二叉樹(AVL Tree)
  • 紅黑樹(Red Black Tree)
  • 2-3 樹(2–3 Tree)

其中,二叉樹和二叉查找樹是最常用的樹。

常見的樹代碼面試題

7. 前綴樹

**前綴樹(Prefix Trees 或者 Trie)**與樹類似,用於處理字符串相關的問題時非常高效。它可以實現快速檢索,常用於字典中的單詞查詢,搜索引擎的自動補全甚至 IP 路由。

下圖展示了“top”, “thus”和“their”三個單詞在前綴樹中如何存儲的:

img

單詞是按照字母從上往下存儲,“p”, “s”和“r”節點分別表示“top”, “thus”和“their”的單詞結尾。

常見的樹代碼面試題

8. 哈希表

哈希(Hash)將某個對象變換爲唯一標識符,該標識符通常用一個短的隨機字母和數字組成的字符串來代表。哈希可以用來實現各種數據結構,其中最常用的就是哈希表(hash table)

哈希表通常由數組實現。

哈希表的性能取決於 3 個指標:

  • 哈希函數
  • 哈希表的大小
  • 哈希衝突處理方式

下圖展示了有數組實現的哈希表,數組的下標即爲哈希值,由哈希函數計算,作爲哈希表的鍵(key),而數組中保存的數據即爲值(value)

img

常見的哈希表代碼面試題

2、 數據裏有{1,2,3,4,5,6,7,8,9},請隨機打亂順序,生成一個新的數組(請以代碼實現)

import java.util.Arrays;

//打亂數組
public class Demo1 {
    
    //隨機打亂
    public static int[] srand(int[] a) {
        int[] b = new int[a.length];
        
        for(int i = 0; i < a.length;i++) {
            //隨機獲取下標
            int tmp = (int)(Math.random()*(a.length - i)); //隨機數:[ 0 ,a.length - i )  
            b[i] = a[tmp];
            
            //將此時a[tmp]的下標移動到靠後的位置
            int change = a[a.length - i - 1];
            a[a.length - i - 1] = a[tmp];
            a[tmp] = change;
        }
        
        return b;
    }
    
    public static void main(String[] args) {
        int[] a = {1,2,3,4,5,6,7,8,9};
        System.out.println(Arrays.toString(srand(a)));
    }
}

3、 寫出代碼判斷一個整數是不是2的階次方(請代碼實現,謝絕調用API方法)

import java.util.Scanner;

//判斷整數是不是2的階次方
public class Demo2 {
    
    public static boolean check(int sum) {
        boolean flag = true; //判斷標誌
        while(sum > 1) {
            if (sum % 2 == 0) {
                sum = sum/2;
            } else {
                flag = false;
                break;
            }
        }
        return flag;
    }
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入一個整數:");
        int sum = scanner.nextInt();
        System.out.println(sum + " 是不是2的階次方:" + check(sum));
    }
}

4、 假設今日是2015年3月1日,星期日,請算出13個月零6天后是星期幾,距離現在多少天(請用代碼實現,謝絕調用API方法)

import java.util.Scanner;

//算出星期幾
public class Demo4 {
    public static String[] week = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
    public static int i = 0;
    public static int[] monthday1 = {0,31,28,31,30,31,30,31,31,30,31,30,31};
    public static int[] monthday2 = {0,31,29,31,30,31,30,31,31,30,31,30,31};
    
    //查看距離當前天數的差值
    public static String distance(int year,int month,int day,int newMonth,int newDay) {
        int sum = 0; //設定初始距離天數
        if (month + newMonth >= 12) {
            if (((year + 1) % 4 == 0 && (year + 1) % 100 != 0)||(year + 1) % 400 == 0) {
                sum += 366 + newDay;
                for(int i = 0;i < newMonth - 12;i++) {
                    sum += monthday1[month + i];
                }
            } else {
                sum += 365 + newDay;
                for(int i = 0;i < newMonth - 12;i++) {
                    sum += monthday1[month + i];
                }
            }
        } else {
            for(int i = 0;i < newMonth;i++) {
                sum += monthday1[month + i];
            }
            sum += newDay;
        }
        return week[sum%7];
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入當前年份");
        int year = scanner.nextInt();
        System.out.println("請輸入當前月份");
        int month = scanner.nextInt();
        System.out.println("請輸入當前天數");
        int day = scanner.nextInt();
        System.out.println("請輸入當前是星期幾:以數字表示,如:星期天 爲 0");
        int index = scanner.nextInt();
        System.out.println("今天是:" + year + "-" + month + "-" + day + "  " + week[index]);
        
        System.err.println("請輸入相隔月份");
        int newMonth = scanner.nextInt();
        System.out.println("請輸入剩餘天數");
        int newDay = scanner.nextInt();
        
        System.out.println("經過" + newMonth + "月" + newDay + "天后,是" + distance(year,month,day,newMonth,newDay));
    }
}

5、 有兩個籃子,分別爲A 和 B,籃子A裏裝有雞蛋,籃子B裏裝有蘋果,請用面向對象的思想實現兩個籃子裏的物品交換(請用代碼實現)

//面向對象思想實現籃子物品交換
public class Demo5 {
    public static void main(String[] args) {
        //創建籃子
        Basket A = new Basket("A");
        Basket B = new Basket("B");
        
        //裝載物品
        A.load("雞蛋");
        B.load("蘋果");
        
        //交換物品
        A.change(B);
        
        A.show();
        B.show();
    }
}

class Basket{
    public String name; //籃子名稱
    private Goods goods; //籃子中所裝物品
    
    public Basket(String name) {
        // TODO Auto-generated constructor stub
        this.name = name;
        System.out.println(name + "籃子被創建");
    }
    
    //裝物品函數
    public void load(String name) {
        goods = new Goods(name);
        System.out.println(this.name + "裝載了" + name + "物品");
    }
    
    public void change(Basket B) {
        System.out.println(this.name + " 和 " + B.name + "中的物品發生了交換");
        String tmp = this.goods.getName();
        this.goods.setName(B.goods.getName());
        B.goods.setName(tmp);
    }
    
    public void show() {
        System.out.println(this.name + "中有" + goods.getName() + "物品");
    }
}

class Goods{
    private String name; //物品名稱
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Goods(String name) {
        // TODO Auto-generated constructor stub
        this.name = name;
    }
}

6、更多算法練習

更多算法練習題,請訪問 https://leetcode-cn.com/problemset/algorithms/

Linux篇

1、 絕對路徑用什麼符號表示?當前目錄、上層目錄用什麼表示?主目錄用什麼表示? 切換目錄用什麼命令?

絕對路徑: 如/etc/init.d

當前目錄和上層目錄: ./ …/

主目錄: ~/

切換目錄: cd

2、 怎麼查看當前進程?怎麼執行退出?怎麼查看當前路徑?

查看當前進程: ps

ps -l 列出與本次登錄有關的進程信息;
ps -aux 查詢內存中進程信息;
ps -aux | grep *** 查詢***進程的詳細信息;
top 查看內存中進程的動態信息;
kill -9 pid 殺死進程。

執行退出: exit

查看當前路徑: pwd

3、查看文件有哪些命令

vi 文件名 #編輯方式查看,可修改

cat 文件名 #顯示全部文件內容

more 文件名 #分頁顯示文件內容

less 文件名 #與 more 相似,更好的是可以往前翻頁

tail 文件名 #僅查看尾部,還可以指定行數

head 文件名 #僅查看頭部,還可以指定行數

4、列舉幾個常用的Linux命令

  • 列出文件列表:ls【參數 -a -l】
  • 創建目錄和移除目錄:mkdir rmdir
  • 用於顯示文件後幾行內容:tail,例如: tail -n 1000:顯示最後1000行
  • 打包:tar -xvf
  • 打包並壓縮:tar -zcvf
  • 查找字符串:grep
  • 顯示當前所在目錄:pwd創建空文件:touch
  • 編輯器:vim vi

5、你平時是怎麼查看日誌的?

Linux查看日誌的命令有多種: tail、cat、tac、head、echo等,本文只介紹幾種常用的方法。

1、tail

最常用的一種查看方式

命令格式: tail[必要參數][選擇參數][文件]

-f 循環讀取
-q 不顯示處理信息
-v 顯示詳細的處理信息
-c<數目> 顯示的字節數
-n<行數> 顯示行數
-q, --quiet, --silent 從不輸出給出文件名的首部
-s, --sleep-interval=S 與-f合用,表示在每次反覆的間隔休眠S秒

例如:

tail -n 10 test.log 查詢日誌尾部最後10行的日誌;
tail -n +10 test.log 查詢10行之後的所有日誌;
tail -fn 10 test.log 循環實時查看最後1000行記錄(最常用的)

一般還會配合着grep搜索用,例如 :

tail -fn 1000 test.log | grep '關鍵字'

如果一次性查詢的數據量太大,可以進行翻頁查看,例如:

tail -n 4700 aa.log |more -1000 可以進行多屏顯示(ctrl + f 或者 空格鍵可以快捷鍵)

2、head

跟tail是相反的head是看前多少行日誌

head -n 10 test.log 查詢日誌文件中的頭10行日誌;
head -n -10 test.log 查詢日誌文件除了最後10行的其他所有日誌; 

head其他參數參考tail

3、cat

cat 是由第一行到最後一行連續顯示在屏幕上

一次顯示整個文件 :

$ cat filename

從鍵盤創建一個文件 :

$cat > filename

將幾個文件合併爲一個文件:

$cat file1 file2 > file 只能創建新文件,不能編輯已有文件

將一個日誌文件的內容追加到另外一個 :

$cat -n textfile1 > textfile2

清空一個日誌文件:

$cat : >textfile2

注意:> 意思是創建,>>是追加。千萬不要弄混了。

cat其他參數參考tail

4、more

more命令是一個基於vi編輯器文本過濾器,它以全屏幕的方式按頁顯示文本文件的內容,支持vi中的關鍵字定位操作。more名單中內置了若干快捷鍵,常用的有H(獲得幫助信息),Enter(向下翻滾一行),空格(向下滾動一屏),Q(退出命令)。more命令從前向後讀取文件,因此在啓動時就加載整個文件。

該命令一次顯示一屏文本,滿屏後停下來,並且在屏幕的底部出現一個提示信息,給出至今己顯示的該文件的百分比:–More–(XX%)

  • more的語法:more 文件名
  • Enter 向下n行,需要定義,默認爲1行
  • Ctrl f 向下滾動一屏
  • 空格鍵 向下滾動一屏
  • Ctrl b 返回上一屏
  • = 輸出當前行的行號
  • :f 輸出文件名和當前行的行號
  • v 調用vi編輯器
  • !命令 調用Shell,並執行命令
  • q退出more

5、sed

這個命令可以查找日誌文件特定的一段 , 根據時間的一個範圍查詢,可以按照行號和時間範圍查詢

按照行號

sed -n '5,10p' filename 這樣你就可以只查看文件的第5行到第10行。

按照時間段

sed -n '/2014-12-17 16:17:20/,/2014-12-17 16:17:36/p' test.log

6、less

less命令在查詢日誌時,一般流程是這樣的

less log.log

shift + G 命令到文件尾部 然後輸入 ?加上你要搜索的關鍵字例如 ?1213

按 n 向上查找關鍵字

shift+n 反向查找關鍵字
less與more類似,使用less可以隨意瀏覽文件,而more僅能向前移動,不能向後移動,而且 less 在查看之前不會加載整個文件。
less log2013.log 查看文件
ps -ef | less ps查看進程信息並通過less分頁顯示
history | less 查看命令歷史使用記錄並通過less分頁顯示
less log2013.log log2014.log 瀏覽多個文件

常用命令參數:

less與more類似,使用less可以隨意瀏覽文件,而more僅能向前移動,不能向後移動,而且 less 在查看之前不會加載整個文件。
less log2013.log 查看文件
ps -ef | less ps查看進程信息並通過less分頁顯示
history | less 查看命令歷史使用記錄並通過less分頁顯示
less log2013.log log2014.log 瀏覽多個文件
常用命令參數:
-b <緩衝區大小> 設置緩衝區的大小
-g 只標誌最後搜索的關鍵詞
-i 忽略搜索時的大小寫
-m 顯示類似more命令的百分比
-N 顯示每行的行號
-o <文件名> 將less 輸出的內容在指定文件中保存起來
-Q 不使用警告音
-s 顯示連續空行爲一行
/字符串:向下搜索"字符串"的功能
?字符串:向上搜索"字符串"的功能
n:重複前一個搜索(與 / 或 ? 有關)
N:反向重複前一個搜索(與 / 或 ? 有關)
b 向後翻一頁
h 顯示幫助界面
q 退出less 命令

一般本人查日誌配合應用的其他命令

history // 所有的歷史記錄

history | grep XXX // 歷史記錄中包含某些指令的記錄

history | more // 分頁查看記錄

history -c // 清空所有的歷史記錄

!! 重複執行上一個命令

查詢出來記錄後選中 : !323

簡歷篇

原文: https://www.cnblogs.com/QQ12538552/p/12332620.html

本篇文章除了教大家用Markdown如何寫一份程序員專屬的簡歷,後面還會給大家推薦一些不錯的用來寫Markdown簡歷的軟件或者網站,以及如何優雅的將Markdown格式轉變爲PDF格式或者其他格式。

推薦大家使用Markdown語法寫簡歷,然後再將Markdown格式轉換爲PDF格式後進行簡歷投遞。

如果你對Markdown語法不太瞭解的話,可以花半個小時簡單看一下Markdown語法說明: http://www.markdown.cn

爲什麼說簡歷很重要?

一份好的簡歷可以在整個申請面試以及面試過程中起到非常好的作用。 在不誇大自己能力的情況下,寫出一份好的簡歷也是一項很棒的能力。爲什麼說簡歷很重要呢?

先從面試來說

假如你是網申,你的簡歷必然會經過HR的篩選,一張簡歷HR可能也就花費10秒鐘看一下,然後HR就會決定你這一關是Fail還是Pass。

假如你是內推,如果你的簡歷沒有什麼優勢的話,就算是內推你的人再用心,也無能爲力。

另外,就算你通過了篩選,後面的面試中,面試官也會根據你的簡歷來判斷你究竟是否值得他花費很多時間去面試。

所以,簡歷就像是我們的一個門面一樣,它在很大程度上決定了你能否進入到下一輪的面試中。

再從面試說起

我發現大家比較喜歡看面經 ,這點無可厚非,但是大部分面經都沒告訴你很多問題都是在特定條件下才問的。舉個簡單的例子:一般情況下你的簡歷上註明你會的東西纔會被問到(Java、數據結構、網絡、算法這些基礎是每個人必問的),比如寫了你會 redis,那面試官就很大概率會問你 redis 的一些問題。比如:redis的常見數據類型及應用場景、redis是單線程爲什麼還這麼快、 redis 和 memcached 的區別、redis 內存淘汰機制等等。

所以,首先,你要明確的一點是:你不會的東西就不要寫在簡歷上。另外,你要考慮你該如何才能讓你的亮點在簡歷中凸顯出來,比如:你在某某項目做了什麼事情解決了什麼問題(只要有項目就一定有要解決的問題)、你的某一個項目裏使用了什麼技術後整體性能和併發量提升了很多等等。

面試和工作是兩回事,聰明的人會把面試官往自己擅長的領域領,其他人則被面試官牽着鼻子走。雖說面試和工作是兩回事,但是你要想要獲得自己滿意的 offer ,你自身的實力必須要強。

必知必會的幾點

大部分公司的HR都說我們不看重學歷(騙你的!),但是如果你的學校不出衆的話,很難在一堆簡歷中脫穎而出,除非你的簡歷上有特別的亮點,比如:某某大廠的實習經歷、獲得了某某大賽的獎等等。

大部分應屆生找工作的硬傷是沒有工作經驗或實習經歷,所以如果你是應屆生就不要錯過秋招和春招。一旦錯過,你後面就極大可能會面臨社招,這個時候沒有工作經驗的你可能就會面臨各種碰壁,導致找不到一個好的工作

寫在簡歷上的東西一定要慎重,這是面試官大量提問的地方;

將自己的項目經歷完美的展示出來非常重要。

必須瞭解的兩大法則

STAR法則(Situation Task Action Result)

  • Situation: 事情是在什麼情況下發生;
  • Task:: 你是如何明確你的任務的;
  • Action: 針對這樣的情況分析,你採用了什麼行動方式;
  • Result: 結果怎樣,在這樣的情況下你學習到了什麼。

簡而言之,STAR法則,就是一種講述自己故事的方式,或者說,是一個清晰、條理的作文模板。不管是什麼,合理熟練運用此法則,可以輕鬆的對面試官描述事物的邏輯方式,表現出自己分析闡述問題的清晰性、條理性和邏輯性。

FAB 法則(Feature Advantage Benefit)

  • Feature: 是什麼;
  • Advantage: 比別人好在哪些地方;
  • Benefit: 如果僱傭你,招聘方會得到什麼好處。

簡單來說,這個法則主要是讓你的面試官知道你的優勢、招了你之後對公司有什麼幫助。

項目經歷怎麼寫

簡歷上有一兩個項目經歷很正常,但是真正能把項目經歷很好的展示給面試官的非常少。對於項目經歷大家可以考慮從如下幾點來寫:

  1. 對項目整體設計的一個感受
  2. 在這個項目中你負責了什麼、做了什麼、擔任了什麼角色
  3. 從這個項目中你學會了那些東西,使用到了那些技術,學會了那些新技術的使用
  4. 另外項目描述中,最好可以體現自己的綜合素質,比如你是如何協調項目組成員協同開發的或者在遇到某一個棘手的問題的時候你是如何解決的又或者說你在這個項目用了什麼技術實現了什麼功能比如:用redis做緩存提高訪問速度和併發量、使用消息隊列削峯和降流等等。

專業技能怎麼寫

先問一下你自己會什麼,然後看看你意向的公司需要什麼。一般HR可能並不太懂技術,所以他在篩選簡歷的時候可能就盯着你專業技能的關鍵詞來看。對於公司有要求而你不會的技能,你可以花幾天時間學習一下,然後在簡歷上可以寫上自己瞭解這個技能。比如你可以這樣寫(下面這部分內容摘自我的簡歷,大家可以根據自己的情況做一些修改和完善):

  • 計算機網絡、數據結構、算法、操作系統等課內基礎知識:掌握
  • Java 基礎知識:掌握
  • JVM 虛擬機(Java內存區域、虛擬機垃圾算法、虛擬垃圾收集器、JVM內存管理):掌握
  • 高併發、高可用、高性能系統開發:掌握
  • Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery :掌握
  • SSH 整合、SSM 整合、 SOA 架構:掌握
  • Dubbo: 掌握
  • Zookeeper: 掌握
  • 常見消息隊列: 掌握
  • Linux:掌握
  • MySQL常見優化手段:掌握
  • Spring Boot +Spring Cloud +Docker:瞭解
  • Hadoop 生態相關技術中的 HDFS、Storm、MapReduce、Hive、Hbase :瞭解
  • Python 基礎、一些常見第三方庫比如OpenCV、wxpy、wordcloud、matplotlib:熟悉

排版注意事項

  1. 儘量簡潔,不要太花裏胡哨;
  2. 一些技術名詞不要弄錯了大小寫比如MySQL不要寫成mysql,Java不要寫成java。這個在我看來還是比較忌諱的,所以一定要注意這個細節;
  3. 中文和數字英文之間加上空格的話看起來會舒服一點;

其他一些小tips

  1. 儘量避免主觀表述,少一點語義模糊的形容詞,儘量要簡潔明瞭,邏輯結構清晰。
  2. 如果自己有博客或者個人技術棧點的話,寫上去會爲你加分很多。
  3. 如果自己的Github比較活躍的話,寫上去也會爲你加分很多。
  4. 注意簡歷真實性,一定不要寫自己不會的東西,或者帶有欺騙性的內容
  5. 項目經歷建議以時間倒序排序,另外項目經歷不在於多,而在於有亮點。
  6. 如果內容過多的話,不需要非把內容壓縮到一頁,保持排版乾淨整潔就可以了。
  7. 簡歷最後最好能加上:“感謝您花時間閱讀我的簡歷,期待能有機會和您共事。”這句話,顯的你會很有禮貌。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章