專門爲了面試而學的 Java

1. hashmap hashtable

HashMap 是一個散列表,它存儲的內容是鍵值對(key-value)映射。java

HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable接口。面試

HashMap 的實現不是同步的,這意味着它不是線程安全的。它的key、value均可覺得null。此外,HashMap中的映射不是有序的。算法

HashMap 的實例有兩個參數影響其性能:「初始容量」 和 「加載因子」。容量 是哈希表中桶的數量,初始容量 只是哈希表在建立時的容量。加載因子 是哈希表在其容量自動增長以前能夠達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 rehash 操做(即重建內部數據結構),從而哈希表將具備大約兩倍的桶數。數據庫


繼承關係數組

 

hashmap底層就是一個數組,而後數組裏每一個元素裝了個鏈表。緩存

這個數組元素稱爲bucket桶安全

圖例數據結構

HashTable和HashMap區別多線程

第一,繼承不一樣。架構

Java

 

1

2

public class Hashtable extends Dictionary implements Map

public class HashMap  extends AbstractMap implements Map

第二,Hashtable 中的方法是同步的,而HashMap中的方法在缺省狀況下是非同步的。在多線程併發的環境下,能夠直接使用Hashtable,可是要使用HashMap的話就要本身增長同步處理了,對HashMap的同步處理可使用Collections類提供的synchronizedMap靜態方法,或者直接使用JDK 5.0以後提供的java.util.concurrent包裏的ConcurrentHashMap類。

第三,Hashtable中,key和value都不容許出現null值。在HashMap中,null能夠做爲鍵,這樣的鍵只有一個;能夠有一個或多個鍵所對應的值爲null。當get()方法返回null值時,便可以表示 HashMap中沒有該鍵,也能夠表示該鍵所對應的值爲null。所以,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵,而應該用containsKey()方法來判斷。

第四,兩個遍歷方式的內部實現上不一樣。

Hashtable、HashMap都使用了 Iterator。而因爲歷史緣由,Hashtable還使用了Enumeration的方式 。

第五,哈希值的使用不一樣,HashTable直接使用對象的hashCode。而HashMap從新計算hash值( indexFor )。

第六,Hashtable和HashMap它們兩個內部實現方式的數組的初始大小和擴容的方式。HashTable中hash數組默認大小是11,增長的方式是 old*2+1。HashMap中hash數組的默認大小是16,並且必定是2的指數。

2. 爲何用枚舉實現的單例是最好的方式

  • 枚舉寫法簡單

 

Java

 

1

2

3

public enum Singleton{

  INSTANCE;

}

 

  • 枚舉本身處理序列化

在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是經過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。

3. jvm 內存模型

內存模型架構圖

  • 程序計數器

每一個線程有要有一個獨立的程序計數器,記錄下一條要運行的指令。線程私有的內存區域。若是執行的是JAVA方法,計數器記錄正在執行的java字節碼地址,若是執行的是native方法,則計數器爲空。

  • 虛擬機棧

線程私有的,與線程在同一時間建立。管理JAVA方法執行的內存模型。

  • 本地方法區

和虛擬機棧功能類似,但管理的不是JAVA方法,是本地方法

  • 方法區

線程共享的,用於存放被虛擬機加載的類的元數據信息:如常量、靜態變量、即時編譯器編譯後的代碼。也稱爲永久代。

  • JAVA 堆

線程共享的,存放全部對象實例和數組。垃圾回收的主要區域。能夠分爲新生代和老年代(tenured)。

4. jdk 線程池

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

package cn.gaialine.threadpool;  

 

import java.util.concurrent.ArrayBlockingQueue;  

import java.util.concurrent.BlockingQueue;  

import java.util.concurrent.ThreadPoolExecutor;  

import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;  

import java.util.concurrent.TimeUnit;  

 

/**

* 線程池測試用例

* @author yangyong

*

*/  

public class TestThreadPool {  

    //線程池維護線程的最少數量  

    private static final int COREPOOLSIZE = 2;  

    //線程池維護線程的最大數量  

    private static final int MAXINUMPOOLSIZE = 5;  

    //線程池維護線程所容許的空閒時間  

    private static final long KEEPALIVETIME = 4;  

    //線程池維護線程所容許的空閒時間的單位  

    private static final TimeUnit UNIT = TimeUnit.SECONDS;  

    //線程池所使用的緩衝隊列,這裏隊列大小爲3  

    private static final BlockingQueue<Runnable> WORKQUEUE = new ArrayBlockingQueue<Runnable>(3);  

    //線程池對拒絕任務的處理策略:AbortPolicy爲拋出異常;CallerRunsPolicy爲重試添加當前的任務,他會自動重複調用execute()方法;DiscardOldestPolicy爲拋棄舊的任務,DiscardPolicy爲拋棄當前的任務  

    private static final AbortPolicy HANDLER = new ThreadPoolExecutor.AbortPolicy();  

 

    public static void main(String[] args) {  

        // TODO 初始化線程池  

        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(COREPOOLSIZE, MAXINUMPOOLSIZE, KEEPALIVETIME, UNIT, WORKQUEUE, HANDLER);  

        for (int i = 1; i < 11; i++) {  

            String task = "task@"+i;  

            System.out.println("put->"+task);  

            //一個任務經過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是 Runnable類型對象的run()方法  

            //處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,若是三者都滿了,使用handler處理被拒絕的任務  

            //設此時線程池中的數量爲currentPoolSize,若currentPoolSize>corePoolSize,則建立新的線程執行被添加的任務,  

            //當corePoolSize+workQueue>currentPoolSize>=corePoolSize,新增任務被放入緩衝隊列,  

            //當maximumPoolSize>currentPoolSize>=corePoolSize+workQueue,建新線程來處理被添加的任務,  

            //當currentPoolSize>=maximumPoolSize,經過 handler所指定的策略來處理新添加的任務  

            //本例中能夠同時能夠被處理的任務最多爲maximumPoolSize+WORKQUEUE=8個,其中最多5個在線程中正在處理,3個在緩衝隊列中等待被處理  

            //當currentPoolSize>corePoolSize時,若是某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池能夠動態的調整池中的線程數  

            threadPool.execute(new ThreadPoolTask(task));  

            try {  

                Thread.sleep(1000);  

            } catch (InterruptedException e) {  

                // TODO Auto-generated catch block  

                e.printStackTrace();  

            }  

        }  

        threadPool.shutdown();//關閉主線程,但線程池會繼續運行,直到全部任務執行完纔會中止。若不調用該方法線程池會一直保持下去,以便隨時添加新的任務  

    }  

}

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

package cn.gaialine.threadpool;  

 

import java.io.Serializable;  

 

/**

* 任務task

* @author yangyong

*

*/  

public class ThreadPoolTask implements Runnable,Serializable{  

    private static final long serialVersionUID = -8568367025140842876L;  

 

    private Object threadPoolTaskData;  

    private static int produceTaskSleepTime = 10000;  

 

    public ThreadPoolTask(Object threadPoolTaskData) {  

        super();  

        this.threadPoolTaskData = threadPoolTaskData;  

    }  

 

    public void run() {  

        // TODO Auto-generated method stub  

        System.out.println("start..."+threadPoolTaskData);  

        try {  

            //模擬線程正在執行任務  

            Thread.sleep(produceTaskSleepTime);  

        } catch (InterruptedException e) {  

            // TODO Auto-generated catch block  

            e.printStackTrace();  

        }  

        System.out.println("stop..."+threadPoolTaskData);  

        threadPoolTaskData = null;  

    }  

 

    public Object getTask(){  

        return this.threadPoolTaskData;  

    }  

//---------------

put->task@1  

start...task@1  

put->task@2  

start...task@2  

put->task@3  

put->task@4  

put->task@5  

put->task@6  

start...task@6  

put->task@7  

start...task@7  

put->task@8  

start...task@8  

put->task@9  

Exception in thread "main" java.util.concurrent.RejectedExecutionException  

    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)  

    at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)  

    at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)  

    at cn.gaialine.threadpool.TestThreadPool.main(TestThreadPool.java:42)  

stop...task@1  

start...task@3  

stop...task@2  

start...task@4  

stop...task@6  

start...task@5  

stop...task@7  

stop...task@8  

stop...task@3  

stop...task@4  

stop...task@5

從中能夠看出task1和task2依次最早執行,這時候currentPoolSize=2達到了corePoolSize,task三、task四、task5被送入緩衝隊列,達到了workQueue最大值3,task六、task七、task8開啓新的線程開始執行,此時currentPoolSize=5達到了maximumPoolSize,task九、task10根據AbortPolicy策略拋出異常,再也不執行task9和task10。10秒鐘後task一、task2….依次執行完畢釋放線程,開始執行隊列裏的task三、task四、task5,最後task三、四、5執行完畢,全部任務完成。

JDK根據ThreadPoolExecutor配置好的線程池

 

1

2

3

4

5

6

7

8

9

10

11

// 固定工做線程數量的線程池  

ExecutorService executorService1 = Executors.newFixedThreadPool(3);  

 

// 一個可緩存的線程池  

ExecutorService executorService2 = Executors.newCachedThreadPool();  

 

// 單線程化的Executor  

ExecutorService executorService3 = Executors.newSingleThreadExecutor();  

 

// 支持定時的以及週期性的任務執行  

ExecutorService executorService4 = Executors.newScheduledThreadPool(3);

 

5. Java 內存模型

內存模型描述了程序中各個變量(實例域、靜態域和數組元素)之間的關係,以及在實際計算機系統中將變量存儲到內存和從內存中取出變量這樣的底層細節。

JVM中存在一個主存區(Main Memory或Java Heap Memory),Java中全部變量都是存在主存中的,對於全部線程進行共享,而每一個線程又存在本身的工做內存(Working Memory),工做內存中保存的是主存中某些變量的拷貝,線程對全部變量的操做並不是發生在主存區,而是發生在工做內存中,而線程之間是不能直接相互訪問,變量在程序中的傳遞,是依賴主存來完成的。

JMM的最初目的,就是爲了可以支持多線程程序設計的,每一個線程能夠認爲是和其餘線程不一樣的CPU上運行,或者對於多處理器的機器而言,該模型須要實現的就是使得每個線程就像運行在不一樣的機器、不一樣的CPU或者自己就不一樣的線程上同樣。

6. volatile 與 synchronized

在Java中,爲了保證多線程讀寫數據時保證數據的一致性,能夠採用兩種方式:

  • 同步

如用synchronized關鍵字,或者使用鎖對象.

  • volatile

使用volatile關鍵字

用一句話歸納volatile,它可以使變量在值發生改變時能儘快地讓其餘線程知道.

volatile本質是在告訴jvm當前變量在寄存器中的值是不肯定的,須要從主存中讀取,synchronized則是鎖定當前變量,只有當前線程能夠訪問該變量,其餘線程被阻塞住.

volatile僅能使用在變量級別,synchronized則可使用在變量,方法.

volatile僅能實現變量的修改可見性,但不具有原子特性,而synchronized則能夠保證變量的修改可見性和原子性.

volatile不會形成線程的阻塞,而synchronized可能會形成線程的阻塞.

volatile標記的變量不會被編譯器優化,而synchronized標記的變量能夠被編譯器優化.

7. 面試中遇到的問題

求一個無序數組的中位數,白板寫 code

由於時間有限,沒有多想,直接使用冒泡排序冒一半的數據,另外一半保持無序。若是有更好的方法請告訴我謝謝!

數據庫表的行轉列

使用case when就能夠實現了,可是要注意須要對每一個case when作max,以及最後的group by,這樣能夠去除null值。

使用map = new hashmap 比 hashmap = new hashmap 的好處

一時間沒想明白,由於以爲不會有人寫成hashmap = new hashmap,就答了一下實現依賴抽象,方便複用和修改,減小棧存儲之類亂七八糟的東西。。。若是有更好的答案也請告訴我謝謝😢

== 和 equals 的區別

Object類中的equals方法和「==」是同樣的,沒有區別,而String類,Integer類等等一些類,是重寫了equals方法,才使得equals和「==不一樣」,因此,當本身建立類時,自動繼承了Object的equals方法,要想實現不一樣的等於比較,必須重寫equals方法。
「==」比」equal」運行速度快,由於」==」只是比較引用.

hashcode 和 equals 的具體實現方式

這真是個高頻問題,從大四找實習面試到研究生找工做面試到兩次跳槽一共幾十場技術面試幾乎全都問到了。惟一一次沒問到的一次是由於我面的是算法工程師的職位,那次都在面機器學習的算法和實現……

默認 equals 方法直接調用了 ==

 

1

2

3

public boolean equals(Object obj) {

    return (this == obj);

}

String 改寫了 equals

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public boolean equals(Object anObject) {

         if (this == anObject) {

             return true;

         }

         if (anObject instanceof String) {

             String anotherString = (String) anObject;

             int n = value.length;

             if (n == anotherString.value.length) {

                 char v1[] = value;

                 char v2[] = anotherString.value;

                 int i = 0;

                 while (n-- != 0) {

                     if (v1[i] != v2[i])

                             return false;

                     i++;

                 }

                 return true;

             }

         }

         return false;

     }

hashCode是根類Obeject中的方法。默認狀況下,Object中的hashCode() 返回對象的32位jvm內存地址。也就是說若是對象不重寫該方法,則返回相應對象的32爲JVM內存地址。

String類源碼中重寫的hashCode方法以下:

 

1

2

3

4

5

6

7

8

9

10

11

12

public int hashCode() {

    int h = hash;    //Default to 0 ### String類中的私有變量,

    if (h == 0 && value.length > 0) {    //private final char value[]; ### Sting類中保存的字符串內容的的數組

        char val[] = value;

 

        for (int i = 0; i < value.length; i++) {

            h = 31 * h + val[i];

        }

        hash = h;

    }

    return h;

}

 

總結:

(1)綁定。當equals方法被重寫時,一般有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。

(2)綁定緣由。Hashtable實現一個哈希表,爲了成功地在哈希表中存儲和檢索對象,用做鍵的對象必須實現 hashCode 方法和 equals 方法。同(1),必須保證equals相等的對象,hashCode 也相等。由於哈希表經過hashCode檢索對象。

(3)默認。

==默認比較對象在JVM中的地址。

hashCode 默認返回對象在JVM中的存儲地址。

equal比較對象,默認也是比較對象在JVM中的地址,同==

相關文章
相關標籤/搜索