1. 程式人生 > >Java面試常見知識點

Java面試常見知識點

基本語法

static、final、java內部類、transient、volatile關鍵字的作用,foreach迴圈的原理 

①static表示“全域性”或者“靜態”的意思,用來修飾成員變數和成員方法,也可以形成靜態static程式碼塊,甚至靜態導包(靜態導包就是java包的靜態匯入,用import static代替import靜態匯入包是JDK1.5中的新特性)。 被static修飾的成員變數和成員方法獨立於該類的任何物件。也就是說,它不依賴類特定的例項,被類的所有例項共享。只要這個類被載入,Java虛擬機器就能根據類名在執行時資料區的方法區內定找到他們。因此,static物件可以在它的任何物件建立之前訪問,無需引用任何物件。

②final關鍵字可以用來修飾類、方法和變數(包括成員變數和區域性變數)。注意final類中的所有成員方法都會被隱式地指定為final方法。類的private方法會隱式地被指定為final方法。對於一個final變數,如果是基本資料型別的變數,則其數值一旦在初始化之後便不能更改;如果是引用型別的變數,則在對其初始化之後便不能再讓其指向另一個物件。

關於final的一個例子:

public class Test {

    public static void main(String[] args)  {

        String a = "hello2"; 

        final String b = "hello";

        String d = "hello";

        String c = b + 2; 

        String e = d + 2;

        System.out.println((a == c));

        System.out.println((a == e));

    }

}

True
false

解釋:當final變數是基本資料型別以及String型別時,如果在編譯期間能知道它的確切值,則編譯器會把它當做編譯期常量使用。也就是說在用到該final變數的地方,相當於直接訪問的這個常量,不需要在執行時確定。因此在上面的一段程式碼中,由於變數b被final修飾,因此會被當做編譯器常量,所以在使用到b的地方會直接將變數b 替換為它的值。而對於變數d的訪問卻需要在執行時通過連結來進行。

final和static的區別:static作用於成員變數用來表示只儲存一份副本,而final的作用是用來保證變數不可變。

區域性內部類和匿名內部類只能訪問區域性final變數:區域性變數的值在編譯期間就可以確定,則直接在匿名內部裡面建立一個拷貝。如果區域性變數的值無法在編譯期間確定,則通過構造器傳參的方式來對拷貝進行初始化賦值。java編譯器就限定必須將變數限制為final變數,不允許對變數進行更改(對於引用型別的變數,是不允許指向新的物件),這樣資料不一致性的問題就得以解決了。

③Java內部類:內部類的存在使得Java的多繼承機制變得更加完善

  • 成員內部類:成員內部類擁有和外部類同名的成員變數或者方法時,會發生隱藏現象,即預設情況下訪問的是成員內部類的成員。內部類可以擁有private訪問許可權、protected訪問許可權、public訪問許可權及包訪問許可權。這一點和外部類有一點不一樣,外部類只能被public和包訪問兩種許可權修飾。要建立成員內部類的物件,前提是必須存在一個外部類的物件。建立成員內部類物件的一般方式如下:
//第一種方式:

 Outter outter = new Outter();

 Outter.Inner inner = outter.new Inner();  //必須通過Outter物件來建立

         

 //第二種方式:

 Outter.Inner inner1 = outter.getInnerInstance();

 

  • 區域性內部類:區域性內部類就像是方法裡面的一個區域性變數一樣,是不能有public、protected、private以及static修飾符的。

  • 匿名內部類:使用匿名內部類能夠在實現父類或者介面中的方法情況下同時產生一個相應的物件,但是前提是這個父類或者介面必須先存在才能這樣使用。

  • 靜態內部類:靜態內部類是不需要依賴於外部類的

transient

java中物件的序列化指的是將物件轉換成以位元組序列的形式來表示,這些位元組序列包含了物件的資料和資訊,一個序列化後的物件可以被寫到資料庫或檔案中,也可用於網路傳輸。transient關鍵字的作用,簡單地說,就是讓某些被修飾的成員屬性變數不被序列化,例如:HashMap原始碼有個欄位modCount是用transient修飾的,modCount主要用於判斷HashMap是否被修改(像put、remove操作的時候,modCount都會自增),對於這種變數,一開始可以為任何值,0當然也是可以,沒必要持久化其值。

⑤volatile

可見性是指當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。Java提供了volatile關鍵字來保證可見性。當一個共享變數被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他執行緒需要讀取時,它會去記憶體中讀取新值。volatile關鍵字無法保證操作的原子性。volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編程式碼發現,加入volatile關鍵字時,會多出一個lock字首指令。

⑥foreach

對Collection集合類的foreach遍歷,foreach之所以能工作,是因為這些集合類都實現了Iterable介面,該介面中定義了Iterator迭代器的產生方法,並且foreach就是通過Iterable介面在序列中進行移動。

集合

List、Map、Set各種實現類的底層實現原理,實現類的優缺點

①ArrayList

ArrayList繼承AbstractList 並且實現了List和RandomAccess,Cloneable, Serializable介面,預設情況下使用ArrayList會生成一個大小為10的Object型別的陣列。

新增元素比較複雜,涉及到擴充陣列容量的問題。如果加入元素後陣列大小不夠會先進行擴容,每次擴容都將陣列大小增大一半比如陣列大小為10一次擴容後的大小為10+5=15;ArrayList的最大長度為 2^32

public boolean add(E e) {

        ensureCapacityInternal(size + 1);  // 加入元素前檢查陣列的容量是否足夠

        elementData[size++] = e;

        return true;

    }

 

private void ensureCapacityInternal(int minCapacity) {

        modCount++;

        // 如果新增元素後大於當前陣列的長度,則進行擴容

        if (minCapacity - elementData.length > 0)

            grow(minCapacity);

    }

//3----------------------- 

private void grow(int minCapacity) {

        // overflow-conscious code

        int oldCapacity = elementData.length;

        //將陣列的長度增加原來陣列的一半。

        int newCapacity = oldCapacity + (oldCapacity >> 1);

        if (newCapacity - minCapacity < 0)

            newCapacity = minCapacity;

            //如果擴充一半後仍然不夠,則 newCapacity = minCapacity;minCapacity實際元素的個數。

        if (newCapacity - MAX_ARRAY_SIZE > 0)

            newCapacity = hugeCapacity(minCapacity);

            //陣列最大位2^32

        // minCapacity is usually close to size, so this is a win:

        elementData = Arrays.copyOf(elementData, newCapacity);

}   

②LinkedList(基於jdk 1.8實現了List介面和Deque介面的雙端連結串列)

Node節點的定義,Node類LinkedList的靜態內部類

private static class Node<E> {

        E item;

        Node<E> next;

        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {

            this.item = element;

            this.next = next;

            this.prev = prev;

        }

}

新增操作

public boolean add(E e) {

        linkLast(e);

        return true;

}

void linkLast(E e) {

        final Node<E> l = last;//指向連結串列尾部

        final Node<E> newNode = new Node<>(l, e, null);//以尾部為前驅節點建立一個新節點

        last = newNode;//將連結串列尾部指向新節點

        if (l == null)//如果連結串列為空,那麼該節點既是頭節點也是尾節點

            first = newNode;

        else//連結串列不為空,那麼將該結點作為原連結串列尾部的後繼節點

            l.next = newNode;

        size++;//增加尺寸

        modCount++;

}

 獲得頭節點資料

- getFirst()和element()方法在連結串列為空時會丟擲NoSuchElementException

- peek()和peekFirst()方法在連結串列為空時會返回null

- 獲得尾節點資料

- getLast()在連結串列為空時會丟擲NoSuchElementException

- peekLast()在連結串列為空時會返回null

③queue

  • ConcurrentLinkedQueue:是一個適用於高併發場景下的佇列,通過無鎖的方式,實現了高併發狀態下的高效能,通常ConcurrentLinkedQueue效能好於BlockingQueueo它是一個基於連結節點的無界執行緒安全佇列。該佇列的元素遵循先講先出的原則。頭是最先加入的,尾是最近加入的,該佇列不允許null元素。

  • ArrayBlockingQueue:基於陣列的阻塞佇列實現,在ArrayBlockingQueue內部,維護了一個定長陣列,以便快取佇列中的資料物件,其內部沒實現讀寫分離,也就意味著生產和消費不能完全並行,長度是需要定義的,可以指定先講先出或者先講後出,也叫有界佇列,在很多場合非常適合使用。

  • LinkedBlockingQueue:基於連結串列的阻塞佇列,同ArrayBlockingQueue類似,其內部也維持著一個數據緩衝佇列〈該佇列由一個連結串列構成),LinkedBlockingQueue之所以能夠高效的處理併發資料,是因為其內部實現採用分離鎖(讀寫分離兩個鎖),從而實現生產者和消費者操作的完全並行執行,他是一個無界佇列。

  • SynchronousQueue:一種沒有緩衝的佇列,生產者產生的資料直接會被消費者獲取並消費。

  • PriorityBlockingQueue:基於優先順序的阻塞佇列(優先順序的判斷通過建構函式傳入的Compator物件來決定,也就是說傳入佇列的物件必須實現Comparable介面),在實現PriorityBlockingQueue時,內部控制執行緒同步的鎖採用的是公平鎖,他也是一個無界的佇列。

  • DelayQueue:帶有延遲時間的Queue,其中的元素只有當其指定的延遲時間到了,才能夠從佇列中獲取到該元素。DelayQueue中的元素必須實現Delayed介面,DelayQueue是一個沒有大小限制的佇列,應用場景很多,比如對快取超時的資料進行移除、任務超時處理、空閒連線的關閉。

④TreeMap

TreeMap 是一個有序的key-value集合,它是通過紅黑樹實現的。
TreeMap 繼承於AbstractMap,所以它是一個Map,即一個key-value集合。
TreeMap 實現了NavigableMap介面,意味著它支援一系列的導航方法。比如返回有序的key集合。
TreeMap 實現了Cloneable介面,意味著它能被克隆。
TreeMap 實現了java.io.Serializable介面,意味著它支援序列化。

TreeMap基於紅黑樹(Red-Black tree)實現。該對映根據其鍵的自然順序進行排序,或者根據建立對映時提供的 Comparator 進行排序,具體取決於使用的構造方法。
TreeMap的基本操作 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。

紅黑樹的特性:
(1)每個節點或者是黑色,或者是紅色。
(2)根節點是黑色。
(3)每個葉子節點(NIL)是黑色。 [注意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!]
(4)如果一個節點是紅色的,則它的子節點必須是黑色的。
(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。

常見問題

為什麼wait()和notify()屬於Object類

wait()暫停的是持有鎖的物件,所以想呼叫wait()必須為:物件.wait();

notify()喚醒的是等待鎖的物件,呼叫:物件.notify();注意:wait(),notify(),notifyAll()都必須使用在同步中,因為要對持有監視器(鎖)的執行緒操作。所以要使用在同步中,因為只有同步才具有鎖。

多執行緒下HashMap的死迴圈問題

在put操作的時候,如果size>initialCapacity*loadFactor,那麼這時候HashMap就會進行rehash操作,隨之HashMap的結構就會發生翻天覆地的變化。很有可能就是在兩個執行緒在這個時候同時觸發了rehash操作,產生了閉合的迴路。

參考連結:https://www.cnblogs.com/dongguacai/p/5599100.html

ConcurrentHashMap 的工作原理及程式碼實現,如何統計所有的元素個數

資料結構

jdk1.7中採用Segment + HashEntry的方式進行實現

先採用不加鎖的方式,連續計算元素的個數,最多計算3次:

1、如果前後兩次計算結果相同,則說明計算出來的元素個數是準確的;

2、如果前後兩次計算結果都不同,則給每個Segment進行加鎖,再計算一次元素的個數

jdk1.8中放棄了Segment臃腫的設計,取而代之的是採用Node + CAS + Synchronized來保證併發安全進行實現

元素個數儲存baseCount中,部分元素的變化個數儲存在CounterCell陣列中,累加即可

Spring AOP的實現原理

Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。JDK動態代理通過反射來接收被代理的類,並且要求被代理的類必須實現一個介面。JDK動態代理的核心是InvocationHandler介面和Proxy類。

如果目標類沒有實現介面,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個程式碼生成的類庫,可以在執行時動態的生成某個類的子類,注意,CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那麼它是無法使用CGLIB做動態代理的。

Spring之BeanFactory和bean的生命週期

Bean生命週期

 

容器在內部實現的時候,採用“策略模式”來決定採用何種方式初始化bean例項。通常,可以通過反射或者CGLIB動態位元組碼生成來初始化相應的bean例項或者動態生成其子類。預設情況下,容器內部採用CglibSubclassingInstantiationStartegy。

BeanFactoty容器中, Bean的生命週期如上圖所示,與ApplicationContext相比,有如下幾點不同:

1. BeanFactory容器中,不會呼叫ApplicationContextAware介面的setApplicationContext()方法

2. BeanPostProcessor介面的postProcessBeforeInitialization方法和postProcessAfterInitialization方法不會自動呼叫,必須自己通過程式碼手動註冊

3. BeanFactory容器啟動的時候,不會去例項化所有bean,包括所有scope為singleton且非延遲載入的bean也是一樣,而是在呼叫的時候去例項化。

執行緒是一個動態執行的過程,它也有一個從產生到死亡的過程。

(1)生命週期的五種狀態

  • 新建(new Thread)

當建立Thread類的一個例項(物件)時,此執行緒進入新建狀態(未被啟動)。

例如:Thread  t1=new Thread();

  • 就緒(runnable)

執行緒已經被啟動,正在等待被分配給CPU時間片,也就是說此時執行緒正在就緒佇列中排隊等候得到CPU資源。例如:t1.start();

  • 執行(running)

執行緒獲得CPU資源正在執行任務(run()方法),此時除非此執行緒自動放棄CPU資源或者有優先順序更高的執行緒進入,執行緒將一直執行到結束。

  • 死亡(dead)

當執行緒執行完畢或被其它執行緒殺死,執行緒就進入死亡狀態,這時執行緒不可能再進入就緒狀態等待執行。

自然終止:正常執行run()方法後終止

異常終止:呼叫stop()方法讓一個執行緒終止執行

  • 堵塞(blocked)

由於某種原因導致正在執行的執行緒讓出CPU並暫停自己的執行,即進入堵塞狀態。

正在睡眠:用sleep(long t) 方法可使執行緒進入睡眠方式。一個睡眠著的執行緒在指定的時間過去可進入就緒狀態。

正在等待:呼叫wait()方法。(呼叫notify()方法回到就緒狀態)

被另一個執行緒所阻塞:呼叫suspend()方法。(呼叫resume()方法恢復)

Java中執行緒池的實現原理

執行緒池中的核心執行緒數,當提交一個任務時,執行緒池建立一個新執行緒執行任務,直到當前執行緒數等於corePoolSize;如果當前執行緒數為corePoolSize,繼續提交的任務被儲存到阻塞佇列中,等待被執行;如果阻塞佇列滿了,那就建立新的執行緒執行當前任務;直到執行緒池中的執行緒數達到maxPoolSize,這時再有任務來,只能執行reject()處理該任務;

Java執行緒池的工廠類:Executors類,

初始化4種類型的執行緒池:

  • newFixedThreadPool()

說明:初始化一個指定執行緒數的執行緒池,其中corePoolSize == maxPoolSize,使用LinkedBlockingQuene作為阻塞佇列

特點:即使當執行緒池沒有可執行任務時,也不會釋放執行緒。

  • newCachedThreadPool()

說明:初始化一個可以快取執行緒的執行緒池,預設快取60s,執行緒池的執行緒數可達到Integer.MAX_VALUE,即2147483647,內部使用SynchronousQueue作為阻塞佇列;

特點:在沒有任務執行時,當執行緒的空閒時間超過keepAliveTime,會自動釋放執行緒資源;當提交新任務時,如果沒有空閒執行緒,則建立新執行緒執行任務,會導致一定的系統開銷;

因此,使用時要注意控制併發的任務數,防止因建立大量的執行緒導致而降低效能。

  • newSingleThreadExecutor()

說明:初始化只有一個執行緒的執行緒池,內部使用LinkedBlockingQueue作為阻塞佇列。

特點:如果該執行緒異常結束,會重新建立一個新的執行緒繼續執行任務,唯一的執行緒可以保證所提交任務的順序執行

  • newScheduledThreadPool()

特定:初始化的執行緒池可以在指定的時間內週期性的執行所提交的任務,在實際的業務場景中可以使用該執行緒池定期的同步資料。

所謂重入鎖,指的是以執行緒為單位,當一個執行緒獲取物件鎖之後,這個執行緒可以再次獲取本物件上的鎖,而其他的執行緒是不可以的
 synchronized 和   ReentrantLock 都是可重入鎖

 可重入鎖的意義在於防止死鎖
 實現原理實現是通過為每個鎖關聯一個請求計數和一個佔有它的執行緒。
 當計數為0時,認為鎖是未被佔有的。執行緒請求一個未被佔有的鎖時,jvm講記錄鎖的佔有者,並且講請求計數器置為1 。
 如果同一個執行緒再次請求這個鎖,計數將遞增;
 每次佔用執行緒退出同步塊,計數器值將遞減。直到計數器為0,鎖被釋放。