JAVA常見的技術(shù)面試問題(3)

24.HashMap和Hashtable的區(qū)別

HashMap和Hashtable的比較是Java面試中的常見問題,用來考驗(yàn)程序員是否能夠正確使用集合類以及是否可以隨機(jī)應(yīng)變使用多種思路解決問題。HashMap的工作原理、ArrayList與Vector的比較以及這個問題是有關(guān)Java 集合框架的最經(jīng)典的問題。Hashtable是個過時的集合類,存在于Java API中很久了。在Java 4中被重寫了,實(shí)現(xiàn)了Map接口,所以自此以后也成了Java集合框架中的一部分。Hashtable和HashMap在Java面試中相當(dāng)容易被問到,甚至成為了集合框架面試題中最常被考的問題,所以在參加任何Java面試之前,都不要忘了準(zhǔn)備這一題。

這篇文章中,我們不僅將會看到HashMap和Hashtable的區(qū)別,還將看到它們之間的相似之處。

HashMap和Hashtable的區(qū)別
HashMap和Hashtable都實(shí)現(xiàn)了Map接口,但決定用哪一個之前先要弄清楚它們之間的分別。主要的區(qū)別有:線程安全性,同步(synchronization),以及速度。

HashMap幾乎可以等價于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)。
HashMap是非synchronized,而Hashtable是synchronized,這意味著Hashtable是線程安全的,多個線程可以共享一個Hashtable;而如果沒有正確的同步的話,多個線程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴(kuò)展性更好。
另一個區(qū)別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以當(dāng)有其它線程改變了HashMap的結(jié)構(gòu)(增加或者移除元素),將會拋出ConcurrentModificationException,但迭代器本身的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這并不是一個一定發(fā)生的行為,要看JVM。這條同樣也是Enumeration和Iterator的區(qū)別。
由于Hashtable是線程安全的也是synchronized,所以在單線程環(huán)境下它比HashMap要慢。如果你不需要同步,只需要單一線程,那么使用HashMap性能要好過Hashtable。
HashMap不能保證隨著時間的推移Map中的元素次序是不變的。
要注意的一些重要術(shù)語:

  1. sychronized意味著在一次僅有一個線程能夠更改Hashtable。就是說任何線程要更新Hashtable時要首先獲得同步鎖,其它線程要等到同步鎖被釋放之后才能再次獲得同步鎖更新Hashtable。

  2. Fail-safe和iterator迭代器相關(guān)。如果某個集合對象創(chuàng)建了Iterator或者ListIterator,然后其它的線程試圖“結(jié)構(gòu)上”更改集合對象,將會拋出ConcurrentModificationException異常。但其它線程可以通過set()方法更改集合對象是允許的,因?yàn)檫@并沒有從“結(jié)構(gòu)上”更改集合。但是假如已經(jīng)從結(jié)構(gòu)上進(jìn)行了更改,再調(diào)用set()方法,將會拋出IllegalArgumentException異常。

  3. 結(jié)構(gòu)上的更改指的是刪除或者插入一個元素,這樣會影響到map的結(jié)構(gòu)。

我們能否讓HashMap同步?
HashMap可以通過下面的語句進(jìn)行同步:
Map m = Collections.synchronizeMap(hashMap);

結(jié)論
Hashtable和HashMap有幾個主要的不同:線程安全以及速度。僅在你需要完全的線程安全的時候使用Hashtable,而如果你使用Java 5或以上的話,請使用ConcurrentHashMap吧。

25.java中String、StringBuffer、StringBuilder的區(qū)別

java中String、StringBuffer、StringBuilder是編程中經(jīng)常使用的字符串類,他們之間的區(qū)別也是經(jīng)常在面試中會問到的問題。現(xiàn)在總結(jié)一下,看看他們的不同與相同。

1.可變與不可變

String類中使用字符數(shù)組保存字符串,如下就是,因?yàn)橛小癴inal”修飾符,所以可以知道string對象是不可變的。

private final char value[];

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

char[] value;

2.是否多線程安全

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

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

StringBuffer對方法加了同步鎖或者對調(diào)用的方法加了同步鎖,所以是線程安全的。
  StringBuilder并沒有對方法進(jìn)行加同步鎖,所以是非線程安全的

3.StringBuilder與StringBuffer共同點(diǎn)

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

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

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

最后,如果程序不是多線程的,那么使用StringBuilder效率高于StringBuffer。

26.Java中 List、Set、Map 之間的區(qū)別

List(列表)
??List的元素以線性方式存儲,可以存放重復(fù)對象,List主要有以下兩個實(shí)現(xiàn)類:

ArrayList : 長度可變的數(shù)組,可以對元素進(jìn)行隨機(jī)的訪問,向ArrayList中插入與刪除元素的速度慢。 JDK8 中ArrayList擴(kuò)容的實(shí)現(xiàn)是通過grow()方法里使用語句newCapacity = oldCapacity + (oldCapacity >> 1)(即1.5倍擴(kuò)容)計算容量,然后調(diào)用Arrays.copyof()方法進(jìn)行對原數(shù)組進(jìn)行復(fù)制。
LinkedList: 采用鏈表數(shù)據(jù)結(jié)構(gòu),插入和刪除速度快,但訪問速度慢。
Set(集合)
??Set中的對象不按特定(HashCode)的方式排序,并且沒有重復(fù)對象,Set主要有以下兩個實(shí)現(xiàn)類:

HashSet: HashSet按照哈希算法來存取集合中的對象,存取速度比較快。當(dāng)HashSet中的元素個數(shù)超過數(shù)組大小*loadFactor(默認(rèn)值為0.75)時,就會進(jìn)行近似兩倍擴(kuò)容(newCapacity = (oldCapacity << 1) + 1)。
TreeSet :TreeSet實(shí)現(xiàn)了SortedSet接口,能夠?qū)现械膶ο筮M(jìn)行排序。
Map(映射)
??Map是一種把鍵對象和值對象映射的集合,它的每一個元素都包含一個鍵對象和值對象。 Map主要有以下兩個實(shí)現(xiàn)類:

HashMap:HashMap基于散列表實(shí)現(xiàn),其插入和查詢<K,V>的開銷是固定的,可以通過構(gòu)造器設(shè)置容量和負(fù)載因子來調(diào)整容器的性能。
LinkedHashMap:類似于HashMap,但是迭代遍歷它時,取得<K,V>的順序是其插入次序,或者是最近最少使用(LRU)的次序。
TreeMap:TreeMap基于紅黑樹實(shí)現(xiàn)。查看<K,V>時,它們會被排序。TreeMap是唯一的帶有subMap()方法的Map,subMap()可以返回一個子樹。
HashMap
??底層實(shí)現(xiàn):HashMap底層整體結(jié)構(gòu)是一個數(shù)組,數(shù)組中的每個元素又是一個鏈表。每次添加一個對象(put)時會產(chǎn)生一個鏈表對象(Object類型),Map中的每個Entry就是數(shù)組中的一個元素(Map.Entry就是一個<Key,Value>),它具有由當(dāng)前元素指向下一個元素的引用,這就構(gòu)成了鏈表。
??存儲原理:當(dāng)向HsahMap中添加元素的時候,先根據(jù)HashCode重新計算Key的Hash值,得到數(shù)組下標(biāo),如果數(shù)組該位置已經(jīng)存在其他元素,那么這個位置的元素將會以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾,如果數(shù)組該位置元素不存在,那么就直接將該元素放到此數(shù)組中的該位置。
??去重原理:不同的Key算到數(shù)組下標(biāo)相同的幾率很小,新建一個<K,V>放入到HashMap的時候,首先會計算Key的數(shù)組下標(biāo),如果數(shù)組該位置已經(jīng)存在其他元素,則比較兩個Key,若相同則覆蓋寫入,若不同則形成鏈表。
??讀取原理:從HashMap中讀?。╣et)元素時,首先計算Key的HashCode,找到數(shù)組下標(biāo),然后在對應(yīng)位置的鏈表中找到需要的元素。
??擴(kuò)容機(jī)制:當(dāng)HashMap中的元素個數(shù)超過數(shù)組大小*loadFactor(默認(rèn)值為0.75)時,就會進(jìn)行2倍擴(kuò)容(oldThr << 1)。

26.Java中ArrayList和LinkedList區(qū)別

ArrayList和LinkedList的大致區(qū)別如下:
1.ArrayList是實(shí)現(xiàn)了基于動態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu),LinkedList基于鏈表的數(shù)據(jù)結(jié)構(gòu)。
2.對于隨機(jī)訪問get和set,ArrayList覺得優(yōu)于LinkedList,因?yàn)長inkedList要移動指針。
3.對于新增和刪除操作add和remove,LinedList比較占優(yōu)勢,因?yàn)锳rrayList要移動數(shù)據(jù)。

ArrayList內(nèi)部是使用可増長數(shù)組實(shí)現(xiàn)的,所以是用get和set方法是花費(fèi)常數(shù)時間的,但是如果插入元素和刪除元素,除非插入和刪除的位置都在表末尾,否則代碼開銷會很大,因?yàn)槔锩嫘枰獢?shù)組的移動。
LinkedList是使用雙鏈表實(shí)現(xiàn)的,所以get會非常消耗資源,除非位置離頭部很近。但是插入和刪除元素花費(fèi)常數(shù)時間。

27. JAVA中的線程安全與非線程安全

轉(zhuǎn)自http://blog.csdn.net/xiao__gui/article/details/8934832

線程安全就是多線程訪問時,采用了加鎖機(jī)制,當(dāng)一個線程訪問該類的某個數(shù)據(jù)時,進(jìn)行保護(hù),其他線程不能進(jìn)行訪問直到該線程讀取完,其他線程才可使用。不會出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染。

線程不安全就是不提供數(shù)據(jù)訪問保護(hù),有可能出現(xiàn)多個線程先后更改數(shù)據(jù)造成所得到的數(shù)據(jù)是臟數(shù)據(jù)

ArrayList和Vector有什么區(qū)別?HashMap和HashTable有什么區(qū)別?StringBuilder和StringBuffer有什么區(qū)別?這些都是Java面試中常見的基礎(chǔ)問題。面對這樣的問題,回答是:ArrayList是非線程安全的,Vector是線程安全的;HashMap是非線程安全的,HashTable是線程安全的;StringBuilder是非線程安全的,StringBuffer是線程安全的。因?yàn)檫@是昨晚剛背的《Java面試題大全》上面寫的。此時如果繼續(xù)問:什么是線程安全?線程安全和非線程安全有什么區(qū)別?分別在什么情況下使用?這樣一連串的問題,一口老血就噴出來了…

非線程安全的現(xiàn)象模擬

這里就使用ArrayList和Vector二者來說明。

下面的代碼,在主線程中new了一個非線程安全的ArrayList,然后開1000個線程分別向這個ArrayList里面添加元素,每個線程添加100個元素,等所有線程執(zhí)行完成后,這個ArrayList的size應(yīng)該是多少?應(yīng)該是100000個?

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; overflow-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">public class Main
{ public static void main(String[] args)
{ // 進(jìn)行10次測試
for(int i = 0; i < 10; i++)
{
test();
}
} public static void test()
{ // 用來測試的List
List<Object> list = new ArrayList<Object>(); // 線程數(shù)量(1000)
int threadCount = 1000; // 用來讓主線程等待threadCount個子線程執(zhí)行完畢
CountDownLatch countDownLatch = new CountDownLatch(threadCount); // 啟動threadCount個子線程
for(int i = 0; i < threadCount; i++)
{
Thread thread = new Thread(new MyThread(list, countDownLatch));
thread.start();
} try { // 主線程等待所有子線程執(zhí)行完成,再向下執(zhí)行
countDownLatch.await();
} catch (InterruptedException e)
{
e.printStackTrace();
} // List的size
System.out.println(list.size());
}
} class MyThread implements Runnable
{ private List<Object> list; private CountDownLatch countDownLatch; public MyThread(List<Object> list, CountDownLatch countDownLatch)
{ this.list = list; this.countDownLatch = countDownLatch;
} public void run()
{ // 每個線程向List中添加100個元素
for(int i = 0; i < 100; i++)
{
list.add(new Object());
} // 完成一個子線程
countDownLatch.countDown();
}
}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

上面進(jìn)行了10次測試(為什么要測試10次?因?yàn)榉蔷€程安全并不是每次都會導(dǎo)致問題)。
輸出結(jié)果:

99946

100000

100000

100000

99998

99959

100000

99975

100000

99996

上面的輸出結(jié)果發(fā)現(xiàn),并不是每次測試結(jié)果都是100000,有好幾次測試最后ArrayList的size小于100000,甚至?xí)r不時會拋出個IndexOutOfBoundsException異常。(如果沒有這個現(xiàn)象可以多試幾次)
這就是非線程安全帶來的問題了。上面的代碼如果用于生產(chǎn)環(huán)境,就會有隱患就會有BUG了。

再用線程安全的Vector來進(jìn)行測試,上面代碼改變一處,test()方法中
List<Object> list = new ArrayList<Object>();
改為
List<Object> list = new Vector<Object>();

再運(yùn)行程序。

輸出結(jié)果:

100000

100000

100000

100000

100000

100000

100000

100000

100000

100000

再多跑幾次,發(fā)現(xiàn)都是100000,沒有任何問題。因?yàn)閂ector是線程安全的,在多線程操作同一個Vector對象時,不會有任何問題。

再換成LinkedList試試,同樣還會出現(xiàn)ArrayList類似的問題,因?yàn)長inkedList也是非線程安全的。

二者如何取舍

非線程安全是指多線程操作同一個對象可能會出現(xiàn)問題。而線程安全則是多線程操作同一個對象不會有問題。

線程安全必須要使用很多synchronized關(guān)鍵字來同步控制,所以必然會導(dǎo)致性能的降低。

所以在使用的時候,如果是多個線程操作同一個對象,那么使用線程安全的Vector;否則,就使用效率更高的ArrayList。

非線程安全!=不安全

有人在使用過程中有一個不正確的觀點(diǎn):我的程序是多線程的,不能使用ArrayList要使用Vector,這樣才安全。

非線程安全并不是多線程環(huán)境下就不能使用。注意我上面有說到:多線程操作同一個對象。注意是同一個對象。比如最上面那個模擬,就是在主線程中new的一個ArrayList然后多個線程操作同一個ArrayList對象。

如果是每個線程中new一個ArrayList,而這個ArrayList只在這一個線程中使用,那么肯定是沒問題的。

線程安全的實(shí)現(xiàn)

線程安全是通過線程同步控制來實(shí)現(xiàn)的,也就是synchronized關(guān)鍵字。

在這里,我用代碼分別實(shí)現(xiàn)了一個非線程安全的計數(shù)器和線程安全的計數(shù)器Counter,并對他們分別進(jìn)行了多線程測試。

非線程安全的計數(shù)器:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; overflow-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">public class Main
{ public static void main(String[] args)
{ // 進(jìn)行10次測試
for(int i = 0; i < 10; i++)
{
test();
}
} public static void test()
{ // 計數(shù)器
Counter counter = new Counter(); // 線程數(shù)量(1000)
int threadCount = 1000; // 用來讓主線程等待threadCount個子線程執(zhí)行完畢
CountDownLatch countDownLatch = new CountDownLatch(threadCount); // 啟動threadCount個子線程
for(int i = 0; i < threadCount; i++)
{
Thread thread = new Thread(new MyThread(counter, countDownLatch));
thread.start();
} try { // 主線程等待所有子線程執(zhí)行完成,再向下執(zhí)行
countDownLatch.await();
} catch (InterruptedException e)
{
e.printStackTrace();
} // 計數(shù)器的值
System.out.println(counter.getCount());
}
} class MyThread implements Runnable
{ private Counter counter; private CountDownLatch countDownLatch; public MyThread(Counter counter, CountDownLatch countDownLatch)
{ this.counter = counter; this.countDownLatch = countDownLatch;
} public void run()
{ // 每個線程向Counter中進(jìn)行10000次累加
for(int i = 0; i < 10000; i++)
{
counter.addCount();
} // 完成一個子線程
countDownLatch.countDown();
}
} class Counter
{ private int count = 0; public int getCount()
{ return count;
} public void addCount()
{
count++;
}
}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

上面的測試代碼中,開啟1000個線程,每個線程對計數(shù)器進(jìn)行10000次累加,最終輸出結(jié)果應(yīng)該是10000000。

但是上面代碼中的Counter未進(jìn)行同步控制,所以非線程安全。

輸出結(jié)果:

9963727

9973178

9999577

9987650

9988734

9988665

9987820

9990847

9992305

9972233

稍加修改,把Counter改成線程安全的計數(shù)器:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px 0px 0px 22px; white-space: pre-wrap; overflow-wrap: break-word; font-size: 12px !important; font-family: "Courier New" !important;">class Counter
{ private int count = 0; public int getCount()
{ return count;
} public synchronized void addCount()
{
count++;
}
}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

上面只是在addCount()方法中加上了synchronized同步控制,就成為一個線程安全的計數(shù)器了。再執(zhí)行程序。

輸出結(jié)果:

10000000

10000000

10000000

10000000

10000000

10000000

10000000

10000000

10000000

10000000

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在一個方法內(nèi)部定義的變量都存儲在棧中,當(dāng)這個函數(shù)運(yùn)行結(jié)束后,其對應(yīng)的棧就會被回收,此時,在其方法體中定義的變量將不...
    Y了個J閱讀 4,585評論 1 14
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨(dú)云閱讀 7,275評論 0 62
  • 早上07:00收到教官給我《世界上最疼我的那個人走了》的點(diǎn)評:青姐,這是到目前為止,我覺得你寫得最完整,最好的一篇...
    丙由甲桂花兒閱讀 297評論 0 0
  • 語文: 今天學(xué)習(xí)的整體認(rèn)讀音節(jié)yi wu各寫一行,每行的后四個分別加上一二三四聲。書中a yi ~wu gui各寫...
    瑞睿家閱讀 137評論 0 0
  • 1 臨出發(fā)去玩的時候,先生要求帶上電腦。我不是特別樂意,多重啊,這么麻煩,丟了更麻煩;需要打字的話,帶個藍(lán)牙鍵盤不...
    紫苑閱讀 110評論 0 0

友情鏈接更多精彩內(nèi)容