Java高級工程師常見面試題(一)-Java基礎

博主其他相關文章:《Java高級工程師常見面試題-總結》

1. String類爲什麼是final的。

多線程安全,將字符串對象保存在字符串常量池中共享效率高。

2. HashMap的源碼,實現原理,底層結構。

HashMap基於哈希表的 Map 接口的實現。允許使用 null 值和 null 鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。

值得注意的是HashMap不是線程安全的,如果想要線程安全的HashMap,可以通過Collections類的靜態方法synchronizedMap獲得線程安全的HashMap。

Map map = Collections.synchronizedMap(new HashMap());

HashMap的底層主要是基於數組和鏈表來實現的,它之所以有相當快的查詢速度主要是因爲它是通過計算散列碼來決定存儲的位置。HashMap中主要是通過key的hashCode來計算hash值的,只要hashCode相同,計算出來的hash值就一樣。如果存儲的對象對多了,就有可能不同的對象所算出來的hash值是相同的,這就出現了所謂的hash衝突。學過數據結構的同學都知道,解決hash衝突的方法有很多,HashMap底層是通過鏈表來解決hash衝突的。

HashMap其實也是一個線性的數組實現的,所以可以理解爲其存儲數據的容器就是一個線性數組。

HashMap其實就是一個Entry數組,Entry對象中包含了鍵和值,其中next也是一個Entry對象,它就是用來處理hash衝突的,形成一個鏈表。

默認初始容量爲16,默認加載因子爲0.75。

3. 說說你知道的幾個Java集合類:list、set、queue、map實現類

Java容器類類庫的用途是"保存對象",並將其劃分爲兩個不同的概念:

1) Collection

一組"對立"的元素,通常這些元素都服從某種規則

  1.1) List必須保持元素特定的順序

  1.2) Set不能有重複元素

  1.3) Queue保持一個隊列(先進先出)的順序

2) Map

一組成對的"鍵值對"對象

Collection和Map的區別在於容器中每個位置保存的元素個數:

1) Collection 每個位置只能保存一個元素(對象)

2) Map保存的是"鍵值對",就像一個小型數據庫。我們可以通過"鍵"找到該鍵對應的"值"

總圖:

 

下圖展示了Collection的類層次關係:

 

Collection繼承的事Iterable接口(迭代器接口),Iterable接口只有一個方法: iterator(),因此Collection集合對象都具有"foreach可遍歷性"。

1) Set集合類似於一個罐子,裏面的對象沒有順序不能包含有重複元素。

1.1) HashSet是Set接口的典型實現,HashSet使用HASH算法來存儲集合中的元素,因此具有良好的存取和查找性能。

1.1.1) LinkedHashSet也是根據元素的hashCode值來決定元素的存儲位置,但和HashSet不同的是,它同時使用鏈表維護元素的次序,這樣使得元素看起來是以插入的順序保存的。需要維護元素的插入順序,因此性能略低於HashSet的性能,但遍歷將有很好的性能(鏈表很適合進行遍歷)。

1.2) SortedSet主要用於排序操作,實現此接口的子類都屬於排序的子類。

1.2.1) TreeSet是SortedSet接口的實現類,TreeSet可以確保集合元素處於排序狀態。

1.3) EnumSet是一個專門爲枚舉類設計的集合類,EnumSet中所有元素都必須是指定枚舉類型的枚舉值,該枚舉類型在創建EnumSet時顯式、或隱式地指定。EnumSet的集合元素也是有序的,它們以枚舉值在Enum類內的定義順序來決定集合元素的順序。

2) List集合代表一個元素有序、可重複的集合,集合中每個元素都有其對應的順序索引。默認按元素的添加順序設置元素的索引。

2.1) ArrayList是基於數組實現的List類,它封裝了一個動態的增長的、允許再分配的Object[]數組。

2.2) Vector和ArrayList在用法上幾乎完全相同,但由於Vector是一個古老的集合,所以Vector提供了一些方法名很長的方法,但隨着JDK1.2以後,java提供了系統的集合框架,就將Vector改爲實現List接口,統一歸入集合框架體系中。

2.2.1) Stack是Vector提供的一個子類,用於模擬"棧"這種數據結構(LIFO後進先出)

2.3) LinkedList實現List接口,能對它進行隊列操作,可以根據索引來隨機訪問集合中的元素。同時它還實現Deque接口,即能將LinkedList當作雙端隊列使用。自然也可以被當作"棧來使用"

3) Queue用於模擬"隊列"這種數據結構(先進先出 FIFO),新元素插入(offer)到隊列的尾部,訪問元素(poll)操作會返回隊列頭部的元素,隊列不允許隨機訪問隊列中的元素。

3.1) PriorityQueue並不是一個比較標準的隊列實現,PriorityQueue保存隊列元素的順序並不是按照加入隊列的順序,而是按照隊列元素的大小進行重新排序,這點從它的類名也可以看出來

3.2) Deque接口代表一個"雙端隊列",雙端隊列可以同時從兩端來添加、刪除元素,因此Deque的實現類既可以當成隊列使用、也可以當成棧使用

3.2.1) ArrayDeque是一個基於數組的雙端隊列,和ArrayList類似,它們的底層都採用一個動態的、可重分配的Object[]數組來存儲集合元素,當集合元素超出該數組的容量時,系統會在底層重新分配一個Object[]數組來存儲集合元素

3.2.2) LinkedList

下圖展示了Map的類層次關係:

 

Map用於保存具有"映射關係"的數據,因此Map集合裏保存着兩組值,一組值用於保存Map裏的key,另外一組值用於保存Map裏的value。

key和value都可以是任何引用類型的數據。Map的key不允許重複。從代碼複用的角度去理解,java是先實現了Map,然後通過包裝了一個所有value都爲null的Map就實現了Set集合。

Map的這些實現類和子接口中key集的存儲形式和Set集合完全相同(即key不能重複)。

Map的這些實現類和子接口中value集的存儲形式和List非常類似(即value可以重複、根據索引來查找)

1) HashMap不能保證key-value對的順序。

1.1) LinkedHashMap使用雙向鏈表來維護key-value對的次序,該鏈表負責維護Map的迭代順序,與key-value對的插入順序一致(注意和TreeMap對所有的key-value進行排序進行區分)。

2) Hashtable是一個古老的Map實現類。

2.1) Properties 對象在處理屬性文件時特別方便,可以把Map對象中的key-value對寫入到屬性文件中,也可以把屬性文件中的"屬性名-屬性值"加載到Map對象中。

3) SortedMap類似SortedSet。

3.1) TreeMap就是一個紅黑樹數據結構,每個key-value對即作爲紅黑樹的一個節點。TreeMap存儲key-value對(節點)時,需要根據key對節點進行排序。TreeMap可以保證所有的key-value對處於有序狀態。同樣,TreeMap也有兩種排序方式: 自然排序、定製排序。

4) WeakHashMap與HashMap的用法基本相似。區別在於,HashMap的key保留了對實際對象的"強引用",這意味着只要該HashMap對象不被銷燬,該HashMap所引用的對象就不會被垃圾回收。但WeakHashMap的key只保留了對實際對象的弱引用,這意味着如果WeakHashMap對象的key所引用的對象沒有被其他強引用變量所引用,則這些key所引用的對象可能被垃圾回收,當垃圾回收了該key所對應的實際對象之後,WeakHashMap也可能自動刪除這些key所對應的key-value對。

5) IdentityHashMap的實現機制與HashMap基本相似,在IdentityHashMap中,當且僅當兩個key嚴格相等(key1 == key2)時,IdentityHashMap才認爲兩個key相等

6) EnumMap是一個與枚舉類一起使用的Map實現,EnumMap中的所有key都必須是單個枚舉類的枚舉值。創建EnumMap時必須顯式或隱式指定它對應的枚舉類。EnumMap根據key的自然順序(即枚舉值在枚舉類中的定義順序)

補充,Collections與Arrays事關於集合的兩個工具類:

 

屬性總結圖:

4. 描述一下ArrayList和LinkedList各自實現和區別

ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。

對於隨機訪問get和set,ArrayList優於LinkedList,因爲LinkedList要移動指針。

對於新增和刪除操作add和remove,LinedList比較佔優勢,因爲ArrayList要移動數據。

5. Java中的隊列都有哪些,有什麼區別。

 

雙端隊列、阻塞隊列、非阻塞隊列

阻塞隊列,當隊列是空的時,從隊列中獲取元素的操作將會被阻塞,或者當隊列是滿時,往隊列裏添加元素的操作會被阻塞。試圖從空的阻塞隊列中獲取元素的線程將會被阻塞,直到其他的線程往空的隊列插入新的元素。同樣,試圖往已滿的阻塞隊列中添加新元素的線程同樣也會被阻塞,直到其他的線程使隊列重新變得空閒起來,如從隊列中移除一個或者多個元素,或者完全清空隊列。

JDK 7提供了7個阻塞隊列,如下。

·ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列。

·LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列。

·PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。

·DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。

·SynchronousQueue:一個不存儲元素的阻塞隊列。

·LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。

·LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

阻塞隊列常用於生產者和消費者的場景,生產者是向隊列裏添加元素的線程,消費者是從隊列裏取元素的線程。阻塞隊列就是生產者用來存放元素、消費者用來獲取元素的容器。

在併發編程中,一般推薦使用阻塞隊列,這樣實現可以儘量地避免程序出現意外的錯誤(非阻塞算法堆死鎖和優先級倒置有“免疫性”(但它們可能會出現飢餓和活鎖,因爲它們允許重進入))。阻塞隊列使用最經典的場景就是socket客戶端數據的讀取和解析,讀取數據的線程不斷將數據放入隊列,然後解析線程不斷從隊列取數據解析。還有其他類似的場景,只要符合生產者-消費者模型的都可以使用阻塞隊列。

非阻塞隊列

實現java.util.Queue的LinkList,實現java.util.AbstractQueue接口內置的不阻塞隊列: PriorityQueue 和 ConcurrentLinkedQueue

ConcurrentLinkedQueue是一個基於鏈接節點的無界線程安全隊列,它採用先進先出的規則對節點進行排序,當我們添加一個元素的時候,它會添加到隊列的尾部;當我們獲取一個元素時,它會返回隊列頭部的元素。

對於出隊操作,也是使用CAS的方式循環嘗試將元素從頭部移除。因爲採用CAS操作,允許多個線程併發執行,並且不會因爲加鎖而阻塞線程,使得併發性能更好。

使用非阻塞隊列,雖然能即時返回結果(消費結果),但必須自行編碼解決返回爲空的情況處理(以及消費重試等問題)。

另外他們都是線程安全的,不用考慮線程同步問題。

6. 反射中,Class.forName和classloader的區別

Class.forName(className)方法,內部實際調用的方法是 Class.forName(className,true,classloader);第2個boolean參數表示類是否需要初始化, Class.forName(className)默認是需要初始化。一旦初始化,就會觸發目標對象的 static塊代碼執行,static參數也也會被再次初始化。

ClassLoader.loadClass(className)方法,內部實際調用的方法是 ClassLoader.loadClass(className,false);第2個 boolean參數,表示目標對象是否進行鏈接,false表示不進行鏈接,由上面介紹可以,不進行鏈接意味着不進行包括初始化等一些列步驟,那麼靜態塊和靜態對象就不會得到執行。

7. Java7、Java8的新特性(baidu問的,好BT)

Java7:

1,switch中可以使用字串了

2.運用List<String> tempList = new ArrayList<>(); 即泛型實例化類型自動推斷

3.語法上支持集合,而不一定是數組final List<Integer> piDigits = [ 1,2,3,4,5,8 ];

4.新增一些取環境信息的工具方法

File System.getJavaIoTempDir() // IO臨時文件夾

File System.getJavaHomeDir() // JRE的安裝目錄

File System.getUserHomeDir() // 當前用戶目錄

File System.getUserDir() // 啓動java進程時所在的目錄5

5.Boolean類型反轉,空指針安全,參與位運算

Boolean Booleans.negate(Boolean booleanObj)

True => False , False => True, Null => Null

boolean Booleans.and(boolean[] array)

boolean Booleans.or(boolean[] array)

boolean Booleans.xor(boolean[] array)

boolean Booleans.and(Boolean[] array)

boolean Booleans.or(Boolean[] array)

boolean Booleans.xor(Boolean[] array)

6.兩個char間的equals

boolean Character.equalsIgnoreCase(char ch1, char ch2)

7.安全的加減乘除

int Math.safeToInt(long value)

int Math.safeNegate(int value)

long Math.safeSubtract(long value1, int value2)

long Math.safeSubtract(long value1, long value2)

int Math.safeMultiply(int value1, int value2)

long Math.safeMultiply(long value1, int value2)

long Math.safeMultiply(long value1, long value2)

long Math.safeNegate(long value)

int Math.safeAdd(int value1, int value2)

long Math.safeAdd(long value1, int value2)

long Math.safeAdd(long value1, long value2)

int Math.safeSubtract(int value1, int value2)

8.map集合支持併發請求,且可以寫成 Map map = {name:"xxx",age:18};

JAVA8:

java8的新特新逐一列出,並將使用簡單的代碼示例來指導你如何使用默認接口方法,lambda表達式,方法引用以及多重Annotation,之後你將會學到最新的API上的改進,比如流,函數式接口,Map以及全新的日期API

一、接口的默認方法

二、Lambda 表達式

三、函數式接口

四、方法與構造函數引用

五、Lambda 作用域

六、訪問局部變量

七、訪問對象字段與靜態變量

八、訪問接口的默認方法

九、Date API

十、Annotation 註解

8. Java數組和鏈表兩種結構的操作效率,在哪些情況下(從開頭開始,從結尾開始,從中間開始),哪些操作(插入,查找,刪除)的效率高

數組的查詢效率較高,增刪的效率很低,而鏈表卻剛好相反。

數組有數組下標也就是序號,所以查詢非常方便,只要你指定要哪個下標號碼的值就行。

增刪效率低也是因爲需要維護這個序號。

鏈表不維護序號,所以增刪直接操作,將前面指向他後面一個操作就完成了。但查詢效率就低啊,得全鏈表掃描。

9. Java內存泄露的問題調查定位:jmap,jstack的使用等等

一、 jps(Java Virtual Machine Process Status Tool):基礎工具

主要用來輸出JVM中運行的進程狀態信息。jps [options] [hostid] 如果不指定hostid就默認爲當前主機或服務器。

二、 jstack jstack主要用來查看某個Java進程內的線程堆棧信息。

jstack可以定位到線程堆棧,根據堆棧信息我們可以定位到具體代碼,所以它在JVM性能調優中使用得非常多。

三、 jmap(Memory Map)和 jhat(Java Heap Analysis Tool):

jmap導出堆內存,然後使用jhat來進行分析

jmap用來查看堆內存使用狀況,一般結合jhat使用。

1、打印進程的類加載器和類加載器加載的持久代對象信息: jmap -permstat pid

2、查看進程堆內存使用情況:包括使用的GC算法、堆配置參數和各代中堆內存使用:jmap -heap pid

3、查看堆內存中的對象數目、大小統計直方圖,如果帶上live則只統計活對象:jmap -histo[:live] pid

4、還有一個很常用的情況是:用jmap把進程內存使用情況dump到文件中,再用jhat分析查看。需要注意的是 dump出來的文件還可以用MAT、VisualVM等工具查看。 注意如果Dump文件太大,可能需要加上-J-Xmx512m參數以指定最大堆內存,即jhat -J-Xmx512m -port 8888 /home/dump.dat。然後就可以在瀏覽器中輸入主機地址:8888查看了:

四、jstat(JVM統計監測工具): 看看各個區內存和GC的情況

五、hprof(Heap/CPU Profiling Tool): hprof能夠展現CPU使用率,統計堆內存使用情況。

10. string、stringbuilder、stringbuffer區別

1)可變與不可變

String類中使用字符數組保存字符串,如下就是,因爲有“final”修飾符,所以可以知道string對象是不可變的。

private final char value[];

StringBuilder與StringBuffer都繼承自AbstractStringBuilder類,在AbstractStringBuilder中也是使用字符數組保存字符串,如下就是,可知這兩種對象都是可變的。

char[] value;

2)是否多線程安全

  String中的對象是不可變的,也就可以理解爲常量,顯然線程安全。

  AbstractStringBuilder是StringBuilder與StringBuffer的公共父類,定義了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。

  StringBuffer對方法加了同步鎖或者對調用的方法加了同步鎖,所以是線程安全的。看如下源碼:

public synchronized StringBuffer reverse() {

super.reverse();

return this;

}

 

public int indexOf(String str) {

return indexOf(str, 0); //存在 public synchronized int indexOf(String str, int fromIndex) 方法

}

  StringBuilder並沒有對方法進行加同步鎖,所以是非線程安全的。

3)StringBuilder與StringBuffer共同點

  StringBuilder與StringBuffer有公共父類AbstractStringBuilder(抽象類)。

抽象類與接口的其中一個區別是:抽象類中可以定義一些子類的公共方法,子類只需要增加新的功能,不需要重複寫已經存在的方法;而接口中只是對方法的申明和常量的定義。

  StringBuilder、StringBuffer的方法都會調用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer會在方法上加synchronized關鍵字,進行同步。

  最後,如果程序不是多線程的,那麼使用StringBuilder效率高於StringBuffer。

11. hashtable和hashmap的區別

hashmap

線程不安全

允許有null的鍵和值

效率高一點、

方法不是Synchronize的要提供外同步

有containsvalue和containsKey方法

HashMap 是Java1.2 引進的Map interface 的一個實現

HashMap是Hashtable的輕量級實現

hashtable

線程安全

不允許有null的鍵和值

效率稍低、

方法是是Synchronize的

有contains方法方法

、Hashtable 繼承於Dictionary 類

Hashtable 比HashMap 要舊

13 .異常的結構,運行時異常和非運行時異常,各舉個例子

 

通常,Java的異常(包括Exception和Error)分爲 可查的異常(checked exceptions)和不可查的異常(unchecked exceptions)。

可查異常(編譯器要求必須處置的異常): 正確的程序在運行中,很容易出現的、情理可容的異常狀況 。 可查異常雖然是異常狀況,但在一定程度上它的發生是可以預計的,而且一旦發生這種異常 狀況,就必須採取某種方式進行處理。除了RuntimeException及其子類以外,其他的Exception類及其子類都屬於可查異常。這種異常的特點是Java編譯器會檢查它,也就是說,當程序中可能出現這類異常,要麼用try-catch語句捕獲它,要麼用throws子句聲明拋出它,否則編譯不會通過。

不可查異常(編譯器不要求強制處置的異常):包括運行時異常(RuntimeException與其子類)和錯誤(Error)。

14. String a= “abc” String b = “abc” String c = new String(“abc”) String d = “ab” + “c” .他們之間用 == 比較的結果

System.out.println("a == b " + (a == b));//true

System.out.println("a == c " + (a == c));//false

System.out.println("a == d " + (a == d));//true

System.out.println("b == c " + (b == c));//false

System.out.println("b == d " + (b == d));//true

System.out.println("c == d " + (c == d));//false

對上面結果的解釋說明:

首先上面的比較過程是直接拿 == 來比較的,沒有用equal方法,那麼用 == 來比較的話,比較的是地址。並不是值。

可以看到,a,b,d,三個字符串直接用 == 比較,比較出來的結果都是true。是相等的。

意思就是說這三個變量在空間上都是指向同一個內存地址。這就涉及到一個字符串常量池的問題。

但是和c比較的時候,卻不相等,因爲,c是new出來的,記得當時學習的時候,new都是在堆內存裏面的,新開闢的空間。

所以,地址肯定就不相同。

15. String 類的常用方法

參考地址:https://www.cnblogs.com/crazyac/articles/2012791.html

16. Java 的引用類型有哪幾種

Java雖然有內存管理機制,但仍應該警惕內存泄露的問題。例如對象池、緩存中的過期對象都有可能引發內存泄露的問題。

從JDK1.2版本開始,加入了對象的幾種引用級別,從而使程序能夠更好的控制對象的生命週期,幫助開發者能夠更好的緩解和處理內存泄露的問題。

這幾種引用級別由高到低分別爲:強引用、軟引用、弱引用和虛引用。

強引用:類似Object a=new Object()這類,永遠不會被回收。

軟引用:SoftReference,當系統快要發生內存溢出異常時,將會把這些對象列入回收範圍進行二次回收,如果這次回收還是沒有足夠內存,則拋出內存溢出異常。

弱引用:比軟引用更弱,活不過下一次gc。無論當前內存是否足夠,下一次gc都會被回收掉。

虛引用:又叫幻引用,最弱,一個對象時候有虛引用的存在,不會對它的生存時間構成影響,唯一目的就是能在這對象被回收以後收到一個系統通知

在java.lang.ref包中提供了三個類:SoftReference類、WeakReference類和PhantomReference類,它們分別代表軟引用、弱引用和虛引用。ReferenceQueue類表示引用隊列,它可以和這三種引用類聯合使用,以便跟蹤Java虛擬機回收所引用的對象的活動。

17. 抽象類和接口的區別

抽象類是用來捕捉子類的通用特性的 。它不能被實例化,只能被用作子類的超類。抽象類是被用來創建繼承層級裏子類的模板。

接口是抽象方法的集合。如果一個類實現了某個接口,那麼它就繼承了這個接口的抽象方法。這就像契約模式,如果實現了這個接口,那麼就必須確保使用這些方法。接口只是一種形式,接口自身不能做任何事情。

參數

抽象類

接口

默認的方法實現

它可以有默認的方法實現

接口完全是抽象的。它根本不存在方法的實現

實現

子類使用extends關鍵字來繼承抽象類。如果子類不是抽象類的話,它需要提供抽象類中所有聲明的方法的實現。

子類使用關鍵字implements來實現接口。它需要提供接口中所有聲明的方法的實現

構造器

抽象類可以有構造器

接口不能有構造器

與正常Java類的區別

除了你不能實例化抽象類之外,它和普通Java類沒有任何區別

接口是完全不同的類型

訪問修飾符

抽象方法可以有public、protected和default這些修飾符

接口方法默認修飾符是public。你不可以使用其它修飾符。

main方法

抽象方法可以有main方法並且我們可以運行它

接口沒有main方法,因此我們不能運行它。

多繼承

抽象方法可以繼承一個類和實現多個接口

接口只可以繼承一個或多個其它接口

速度

它比接口速度要快

接口是稍微有點慢的,因爲它需要時間去尋找在類中實現的方法。

添加新方法

如果你往抽象類中添加新的方法,你可以給它提供默認的實現。因此你不需要改變你現在的代碼。

如果你往接口中添加方法,那麼你必須改變實現該接口的類。

什麼時候使用抽象類和接口:

如果你擁有一些方法並且想讓它們中的一些有默認實現,那麼使用抽象類吧。

如果你想實現多重繼承,那麼你必須使用接口。由於Java不支持多繼承,子類不能夠繼承多個類,但可以實現多個接口。因此你就可以使用接口來解決它。

如果基本功能在不斷改變,那麼就需要使用抽象類。如果不斷改變基本功能並且使用接口,那麼就需要改變所有實現了該接口的類。

18. java的基礎類型和字節大小。

在Java中一共有8種基本數據類型,其中有4種整型,2種浮點類型,1種用於表示Unicode編碼的字符單元的字符類型和1種用於表示真值的boolean類型。(一個字節等於8個bit)

1)整型

類型 存儲需求 bit數 取值範圍 備註

int 4字節 4*8

short 2字節 2*8 -32768~32767

long 8字節 8*8

byte 1字節 1*8 -128~127

2)浮點型

類型 存儲需求 bit數 取值範圍 備註

float 4字節 4*8 float類型的數值有一個後綴F(例如:3.14F)

double 8字節 8*8 沒有後綴F的浮點數值(如3.14)默認爲double類型

3)char類型

類型 存儲需求 bit數 取值範圍 備註

char 2字節 2*8

4)boolean類型

類型 存儲需求 bit數 取值範圍 備註

boolean 1字節 1*8 false、true

補充:Java有一個能夠表示任意精度的算書包,通常稱爲“大數值”(big number)。雖然被稱爲大數值,但它並不是一種Java類型,而是一個Java對象。

如果基本的整數和浮點數精度不能夠滿足需求,那麼可以使用java.math包中的兩個很有用的類:BigIntegerBigDecimal(Android SDK中也包含了java.math包以及這兩個類)這兩個類可以處理包含任意長度數字序列的數值。BigInteger類實現了任意精度的整數運算,BigDecimal實現了任意精度的浮點數運算。具體的用法可以參見Java API。

19. Hashtable,HashMap,ConcurrentHashMap 底層實現原理與線程安全問題(建議熟悉 jdk 源碼,才能從容應答)

關鍵點:

HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的情況下HashTable的效率非常低下。因爲當一個線程訪問HashTable的同步方法時,其他線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。如線程1使用put進行添加元素,線程2不但不能使用put方法添加元素,並且也不能使用get方法來獲取元素,所以競爭越激烈效率越低。

ConcurrentHashMap的鎖分段技術:ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap裏扮演鎖的角色,HashEntry則用於存儲鍵值對數據。一個ConcurrentHashMap裏包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構, 一個Segment裏包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素, 每個Segment守護者一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。

20. 如果不讓你用Java Jdk提供的工具,你自己實現一個Map,你怎麼做。說了好久,說了HashMap源代碼,如果我做,就會借鑑HashMap的原理,說了一通HashMap實現

數組加鏈表

21. Hash衝突怎麼辦?哪些解決散列衝突的方法?

開放地址法:

1)線性探測法:ThreadLocalMap

線性再散列法是形式最簡單的處理衝突的方法。插入元素時,如果發生衝突,算法會簡單的從該槽位置向後循環遍歷hash表,直到找到表中的下一個空槽,並將該元素放入該槽中(會導致相同hash值的元素挨在一起和其他hash值對應的槽被佔用)。查找元素時,首先散列值所指向的槽,如果沒有找到匹配,則繼續從該槽遍歷hash表,直到:(1)找到相應的元素;(2)找到一個空槽,指示查找的元素不存在,(所以不能隨便刪除元素);(3)整個hash表遍歷完畢(指示該元素不存在並且hash表是滿的)

用線性探測法處理衝突,思路清晰,算法簡單,但存在下列缺點:

① 處理溢出需另編程序。一般可另外設立一個溢出表,專門用來存放上述哈希表中放不下的記錄。此溢出表最簡單的結構是順序表,查找方法可用順序查找。

② 按上述算法建立起來的哈希表,刪除工作非常困難。如果將此元素刪除,查找的時會發現空槽,則會認爲要找的元素不存在。只能標上已被刪除的標記,否則,將會影響以後的查找。

③ 線性探測法很容易產生堆聚現象。所謂堆聚現象,就是存入哈希表的記錄在表中連成一片。按照線性探測法處理衝突,如果生成哈希地址的連續序列愈長 ( 即不同關鍵字值的哈希地址相鄰在一起愈長 ) ,則當新的記錄加入該表時,與這個序列發生衝突的可能性愈大。因此,哈希地址的較長連續序列比較短連續序列生長得快,這就意味着,一旦出現堆聚 ( 伴隨着衝突 ) ,就將引起進一步的堆聚。

2)線性補償探測法

線性補償探測法的基本思想是:將線性探測的步長從 1 改爲 Q ,即將上述算法中的

hash = (hash + 1) % m 改爲:hash = (hash + Q) % m = hash % m + Q % m,而且要求 Q 與 m 是互質的,以便能探測到哈希表中的所有單元。

【例】 PDP-11 小型計算機中的彙編程序所用的符合表,就採用此方法來解決衝突,所用表長 m = 1321 ,選用 Q = 25 。

3)僞隨機探測

隨機探測的基本思想是:將線性探測的步長從常數改爲隨機數,即令: hash = (hash + RN) % m ,其中 RN 是一個隨機數。在實際程序中應預先用隨機數發生器產生一個隨機序列,將此序列作爲依次探測的步長。這樣就能使不同的關鍵字具有不同的探測次序,從而可以避 免或減少堆聚。基於與線性探測法相同的理由,在線性補償探測法和隨機探測法中,刪除一個記錄後也要打上刪除標記。

拉鍊法 : hashmap

拉鍊法的優點

與開放定址法相比,拉鍊法有如下幾個優點:

①拉鍊法處理衝突簡單,且無堆積現象,即非同義詞決不會發生衝突,因此平均查找長度較短;

②由於拉鍊法中各鏈表上的結點空間是動態申請的,故它更適合於造表前無法確定表長的情況;

③開放定址法爲減少衝突,要求裝填因子α較小,故當結點規模較大時會浪費很多空間。而拉鍊法中可取α≥1,且結點較大時,拉鍊法中增加的指針域可忽略不計,因此節省空間;

④在用拉鍊法構造的散列表中,刪除結點的操作易於實現。只要簡單地刪去鏈表上相應的結點即可。

拉鍊法的缺點:拉鍊法的缺點是:指針需要額外的空間,故當結點規模較小時,開放定址法較爲節省空間,而若將節省的指針空間用來擴大散列表的規模,可使裝填因子變小,這又減少了開放定址法中的衝突,從而提高平均查找速度。

再散列(雙重散列,多重散列)

當發生衝突時,使用第二個、第三個、哈希函數計算地址,直到無衝突時。缺點:計算時間增加。

建立一個公共溢出區

假設哈希函數的值域爲[0,m-1],則設向量HashTable[0..m-1]爲基本表,另外設立存儲空間向量OverTable[0..v]用以存儲發生衝突的記錄。

22. HashMap衝突很厲害,最差性能,你會怎麼解決?從O(n)提升到log(n)咯,用二叉排序樹的思路說了一通

 

23. rehash

HashMap的內部實現機制時提到了兩個參數,DEFAULT_INITIAL_CAPACITY和DEFAULT_LOAD_FACTOR,DEFAULT_INITIAL_CAPACITY是table數組的容量,DEFAULT_LOAD_FACTOR則是爲了最大程度避免哈希衝突,提高HashMap效率而設置的一個影響因子,將其乘以DEFAULT_INITIAL_CAPACITY就得到了一個閾值threshold,當HashMap的容量達到threshold時就需要進行擴容,這個時候就要進行ReHash操作了,可以看到下面addEntry函數的實現,當size達到threshold時會調用resize函數進行擴容。

在擴容的過程中需要進行ReHash操作,而這是非常耗時的,在實際中應該儘量避免。

24. hashCode() 與 equals() 生成算法、方法怎麼重寫

兩個obj,如果equals()相等,hashCode()一定相等。

兩個obj,如果hashCode()相等,equals()不一定相等(Hash散列值有衝突的情況,雖然概率很低)。

 

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