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ù)語:
sychronized意味著在一次僅有一個線程能夠更改Hashtable。就是說任何線程要更新Hashtable時要首先獲得同步鎖,其它線程要等到同步鎖被釋放之后才能再次獲得同步鎖更新Hashtable。
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異常。
結(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個?
[
](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>

](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ù)器:
[
](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>

](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ù)器:
[
](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>

](javascript:void(0); "復(fù)制代碼")
上面只是在addCount()方法中加上了synchronized同步控制,就成為一個線程安全的計數(shù)器了。再執(zhí)行程序。
輸出結(jié)果:
10000000
10000000
10000000
10000000
10000000
10000000
10000000
10000000
10000000
10000000