Java高級面試題積累(一)

個人感覺越是資深的JAVA工程師,應該越注重JAVA基礎知識。
因此我在工作,面試遇到的問題,自學中積累的相關知識點把我理解的結論記錄在這裏,以便以後複習。(一直在更新ing)

JAVA 位運算符原理:

35 >> 2 = 8  : 將值每右移一次,就相當於該值除以2並且捨棄餘數。(35 除以2的2次方)
*無符號右移>>>與帶符號右移>>的區別就是 無符號始終補0
2 << 2 = 8  : 將值每左移一次,就相當於該值乘以2。(2乘以2的2次方)


HashMap 實現原理:

JDK7:底層是數組,通過傳入的KEY的特定哈希算法定位數組索引。
當哈希值相同時判斷equals方法如果相同就用新值替換爲舊值。
如果不相同將以單項鍊表形式存入(一般爲了性能會重寫hashcode和equals方法,從而減少哈希值衝突來提高性能,因爲哈希值相同的數據多時hashmap將退化爲單項鍊表)
JDK8(相對於JDK7性能顯著提高 get方法提高了20%左右):底層依然是數組,實現原理基本和JDK7一樣。
不同的是單向鏈表長度超過指定個數時候會將鏈表升級爲二叉樹的結構來存儲,使用哈希值作爲樹的分支變量,如果兩個哈希值不等,但指向同一個位置的話,較大的那個會插入到右子樹裏。如果哈希值相等,HashMap希望key值最好是實現了Comparable接口的,這樣它可以按照順序來進行插入。如果沒有實現這個接口,在出現嚴重的哈希碰撞的時候,你就別指望能獲得性能提升了。

賦:Set 底層其實就是HashMap的key部分。只是將沒有用到的值部分統一插入了Object而已。

ConcurrentHashMap原理,還有和HashTable的區別


HashTable與ConcurrentHashMap內部結構參考圖
ConcurrentHashMap是一個線程安全的HashMap,它的主要功能是提供了一組和HashMap功能相同但是線程安全的方法。ConcurrentHashMap可以做到讀取數據不加鎖,並且其內部的結構可以讓其在進行寫操作的時候通過segments(默認將哈希表分割成16個segments)來將鎖的粒度儘量最小化,不用對整個ConcurrentHashMap加鎖。
由於ConcurrentHashMap內部是類似二維數組形式,所以爲了減少哈希衝突一共進行了2次hash來定位數據。(爲了均勻分佈在不同的Segment上,從而提高容器的存取效率)。
get操作不需要鎖,第一步訪問volatile變量count,由於所以修改操作最後一步更新count變量,確保get操作得到幾乎最新的結構更新(HashEntry的value也是volatile,也能保證讀取最新的值)。而且除了value以外的別的變量都爲final所以遍歷鏈表也不需要加鎖。
最後,如果找到的節點如果非空直接範圍,否則在有鎖狀態下載讀一次。(空值的唯一源頭就是HashEntry中的默認值,因爲HashEntry中的value不是final的,非同步讀取有可能讀取到空值)

HashTablesynchronizedMap通過同步HashTable和Map包裝器中的每個方法(包括查詢方法),確保一次只有一個線程訪問hash表來實現線程安全。


樂觀鎖和悲觀鎖的區別

悲觀鎖:在整個數據處理過程中,將數據處於鎖定狀態(依靠數據庫提供的鎖機制實現),因此會大大消耗數據庫開銷。(傳統的關係型數據庫運用比較常見,比如行鎖,表鎖,讀鎖,寫鎖等)

樂觀鎖:大多是基於數據版本記錄機制實現。就是爲數據增加一個版本標識(version字段),在對數據更改時比對版本號如果一致,則批准更改並將版本號+1,否則更新失敗。


String類爲什麼定義爲final

Java中String、Long、Double、Integer等,這些數據類型的類,都是final,因爲這些類是基本數據類型的延伸。設計成一個不變類,這樣有助於共享,提高性能。可以將字符串對象保存在字符串常量池中以供與字面值相同字符串對象共享。如果String對象是可變的,那就不能這樣共享,因爲一旦對某一個String類型變量引用的對象值改變,將同時改變一起共享字符串對象的其他String類型變量所引用的對象的值。


ThreadLocal是什麼

首先此一次接觸ThreadLocal可能會被它的命名誤解,這裏說明一下不是本地線程,而是 一個關於創建線程局部變量的類,簡單來說就是當使用ThreadLocal維護變量時,ThreadLocal爲每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。 (自JDK5.0之後ThreadLocal已經支持泛型。)

這裏結合spring說明一下,spring作爲單例模式的典範,是如何避免線程安全問題呢,我們知道Spring通過各種DAO模板類降低了開發者使用各種數據持久技術的難度。這些模板類都是線程安全的,也就是說,多個DAO可以複用同一個模板實例而不會發生衝突。 

我們使用模板類訪問底層數據,根據持久化技術的不同,模板類需要綁定數據連接或會話的資源。但這些資源本身是非線程安全的,也就是說它們不能在同一時刻被多個線程共享。 

按照傳統經驗,如果某個對象是非線程安全的,在多線程環境下,對對象的訪問必須採用synchronized進行線程同步。但Spring的DAO模板類並未採用線程同步機制,因爲線程同步限制了併發訪問,會帶來很大的性能損失。 此外,通過代碼同步解決性能安全問題挑戰性很大,可能會增強好幾倍的實現難度。那模板類究竟仰丈何種魔法神功,可以在無需同步的情況下就化解線程安全的難題呢?答案就是ThreadLocal!

ThreadLocal是如何做到爲每一個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個靜態內部類ThreadLocalMap,用於存儲每一個線程的變量的副本。
參照下面的源碼實現可以看出,其實很簡單每個ThreadLocal有個靜態內部類ThreadLocalMap,key存儲當前線程,value存儲entry來實現的。

      <pre name="code" class="java">public class ThreadLocal<T> {
/* ThreadLocal values pertaining to this thread. This map is maintained
    * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
/**
    * Returns the value in the current thread's copy of this
    * thread-local variable.  If the variable has no value for the
    * current thread, it is first initialized to the value returned
    * by an invocation of the {@link #initialValue} method.
    *
    * @return the current thread's value of this thread-local
    */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }


    static class ThreadLocalMap {
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;


            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];


            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    ThreadLocal key = e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

        private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal key, Object value) {


            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.


            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);


            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();


                if (k == key) {
                    e.value = value;
                    return;
                }


                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }


            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        private void remove(ThreadLocal key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
<span style="white-space:pre">	</span>}
}

用法其實也比較簡單,將多線程中有可能發生衝突的變量通過set方法封裝進threadlocal中獲取時候通過get方法獲取,就可以避免多線程中變量衝突的問題。

WebService是什麼

Web service是一個平臺獨立的,低耦合的,自包含的、基於可編程的web的應用程序,可使用開放的XML(標準通用標記語言下的一個子集)標準來描述、發佈、發現、協調和配置這些應用程序,用於開發分佈式的互操作的應用程序。

webservice也就是向外界暴露一個能夠通過web進行調用的API來實現分佈式系統之前的數據交互。


Web Service 的優點:

  • 可以讓異構的程序相互訪問(跨平臺)
  • 鬆耦合
  • 基於標準協議(通用語言,允許其他程序訪問)

抽象類和接口的區別

1.抽象類 和 接口 都是用來抽象具體對象的. 但是接口的抽象級別最高。
2.抽象類可以有具體的方法 和屬性,  接口只能有抽象方法和不可變常量。
3.抽象類主要用來抽象類別,接口主要用來抽象功能。
4.抽象的子類可以選擇性的重寫抽象方法,而接口的子類必須實現全部抽象方法。(一般的應用裏,最頂級的是接口,然後是抽象類實現接口,最後纔到具體類實現。)
5.一個類只能繼承一個類,但是接口可以實現多個。

Java事務的作用

事務的作用就是保證數據的一致性和完整性。(這裏最好不要說一堆概念或舉例來說明,會使面試官更反感。用簡單易懂一句話說明一下就好。)

線程的幾種狀態

線程有新建狀態(New)、就緒狀態(Runnable)、運行狀態(Running)、阻塞狀態(Blocked)、死亡狀態(Dead)等。

線程狀態參考圖

Mysql存儲引擎

MySQL常用的存儲引擎爲MyISAM、InnoDB、MEMORY、MERGE,其中InnoDB提供事務安全表,其他存儲引擎都是非事務安全表。 
MyISAM 是MySQL的默認存儲引擎。MyISAM不支持事務、也不支持外鍵,但其訪問速度快,對事務完整性沒有要求。 
InnoDB 存儲引擎提供了具有提交、回滾和崩潰恢復能力的事務安全。但是比起MyISAM存儲引擎,InnoDB寫的處理效率差一些並且會佔用更多的磁盤空間以保留數據和索引。 
MEMORY 存儲引擎使用存在內存中的內容來創建表。每個MEMORY表只實際對應一個磁盤文件。MEMORY類型的表訪問非常得快,因爲它的數據是放在內存中的,並且默認使用HASH索引。但是一旦服務關閉,表中的數據就會丟失掉。其次它有空間大小的限制。
MERGE 存儲引擎是一組MyISAM表的組合,這些MyISAM表必須結構完全相同。MERGE表本身沒有數據,對MERGE類型的表進行查詢、更新、刪除的操作,就是對內部的MyISAM表進行的。
Archive 非常適合存儲大量的獨立的,作爲歷史記錄的數據。因爲它們不經常被讀取。Archive擁有高效的插入速度,但其對查詢的支持相對較差。
Federated 將不同的Mysql服務器聯合起來,邏輯上組成一個完整的數據庫。非常適合分佈式應用。
Cluster/NDB 高冗餘的存儲引擎,用多臺數據機器聯合提供服務以提高整體性能和安全性。適合數據量大,安全和性能要求高的應用。
CSV 把數據以逗號分隔的格式存儲在文本文件中。
BLACKHOLE 接受但不存儲數據,並且檢索總是返回一個空集。

賦:歡迎廣大java程序猿將自身經歷的面試題留言,博主會更新到文章中,謝謝支持~






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