1. hashmap hashtable
HashMap 是一個散列表架忌,它存儲的內(nèi)容是鍵值對(key-value)映射混聊。
HashMap 繼承于AbstractMap行贪,實現(xiàn)了Map牵咙、Cloneable芬为、java.io.Serializable接口萄金。
HashMap 的實現(xiàn)不是同步的,這意味著它不是線程安全的媚朦。它的key氧敢、value都可以為null。此外询张,HashMap中的映射不是有序的孙乖。
HashMap 的實例有兩個參數(shù)影響其性能:“初始容量” 和 “加載因子”。容量 是哈希表中桶的數(shù)量份氧,初始容量 只是哈希表在創(chuàng)建時的容量唯袄。加載因子 是哈希表在其容量自動增加之前可以達(dá)到多滿的一種尺度。當(dāng)哈希表中的條目數(shù)超出了加載因子與當(dāng)前容量的乘積時蜗帜,則要對該哈希表進(jìn)行 rehash 操作(即重建內(nèi)部數(shù)據(jù)結(jié)構(gòu))越妈,從而哈希表將具有大約兩倍的桶數(shù)。
hashmap底層就是一個數(shù)組钮糖,然后數(shù)組里每個元素裝了個鏈表梅掠。
這個數(shù)組元素稱為bucket桶
HashTable和HashMap區(qū)別
第一酌住,繼承不同。
public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
第二阎抒,Hashtable 中的方法是同步的酪我,而HashMap中的方法在缺省情況下是非同步的。在多線程并發(fā)的環(huán)境下且叁,可以直接使用Hashtable都哭,但是要使用HashMap的話就要自己增加同步處理了,對HashMap的同步處理可以使用Collections類提供的synchronizedMap靜態(tài)方法逞带,或者直接使用JDK 5.0之后提供的java.util.concurrent包里的ConcurrentHashMap類欺矫。
第三,Hashtable中展氓,key和value都不允許出現(xiàn)null值穆趴。在HashMap中,null可以作為鍵遇汞,這樣的鍵只有一個未妹;可以有一個或多個鍵所對應(yīng)的值為null。當(dāng)get()方法返回null值時空入,即可以表示 HashMap中沒有該鍵络它,也可以表示該鍵所對應(yīng)的值為null。因此歪赢,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵化戳,而應(yīng)該用containsKey()方法來判斷。
第四埋凯,兩個遍歷方式的內(nèi)部實現(xiàn)上不同点楼。
Hashtable、HashMap都使用了 Iterator递鹉。而由于歷史原因盟步,Hashtable還使用了Enumeration的方式 藏斩。
第五躏结,哈希值的使用不同,HashTable直接使用對象的hashCode狰域。而HashMap重新計算hash值( indexFor )媳拴。
第六,Hashtable和HashMap它們兩個內(nèi)部實現(xiàn)方式的數(shù)組的初始大小和擴(kuò)容的方式兆览。HashTable中hash數(shù)組默認(rèn)大小是11屈溉,增加的方式是 old*2+1。HashMap中hash數(shù)組的默認(rèn)大小是16抬探,而且一定是2的指數(shù)子巾。
2. 為什么用枚舉實現(xiàn)的單例是最好的方式
- 枚舉寫法簡單
public enum Singleton{
INSTANCE帆赢;
}
- 枚舉自己處理序列化
在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結(jié)果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據(jù)名字查找枚舉對象线梗。
3. jvm 內(nèi)存模型
- 程序計數(shù)器
每個線程有要有一個獨立的程序計數(shù)器椰于,記錄下一條要運行的指令。線程私有的內(nèi)存區(qū)域仪搔。如果執(zhí)行的是JAVA方法瘾婿,計數(shù)器記錄正在執(zhí)行的java字節(jié)碼地址,如果執(zhí)行的是native方法烤咧,則計數(shù)器為空偏陪。 - 虛擬機(jī)棧
線程私有的,與線程在同一時間創(chuàng)建煮嫌。管理JAVA方法執(zhí)行的內(nèi)存模型笛谦。 - 本地方法區(qū)
和虛擬機(jī)棧功能相似,但管理的不是JAVA方法立膛,是本地方法 - 方法區(qū)
線程共享的揪罕,用于存放被虛擬機(jī)加載的類的元數(shù)據(jù)信息:如常量、靜態(tài)變量宝泵、即時編譯器編譯后的代碼好啰。也稱為永久代。 - JAVA 堆
線程共享的儿奶,存放所有對象實例和數(shù)組框往。垃圾回收的主要區(qū)域〈成樱可以分為新生代和老年代(tenured)椰弊。
4. jdk 線程池
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 {
//線程池維護(hù)線程的最少數(shù)量
private static final int COREPOOLSIZE = 2;
//線程池維護(hù)線程的最大數(shù)量
private static final int MAXINUMPOOLSIZE = 5;
//線程池維護(hù)線程所允許的空閑時間
private static final long KEEPALIVETIME = 4;
//線程池維護(hù)線程所允許的空閑時間的單位
private static final TimeUnit UNIT = TimeUnit.SECONDS;
//線程池所使用的緩沖隊列,這里隊列大小為3
private static final BlockingQueue<Runnable> WORKQUEUE = new ArrayBlockingQueue<Runnable>(3);
//線程池對拒絕任務(wù)的處理策略:AbortPolicy為拋出異常;CallerRunsPolicy為重試添加當(dāng)前的任務(wù)瓤鼻,他會自動重復(fù)調(diào)用execute()方法秉版;DiscardOldestPolicy為拋棄舊的任務(wù),DiscardPolicy為拋棄當(dāng)前的任務(wù)
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);
//一個任務(wù)通過 execute(Runnable)方法被添加到線程池茬祷,任務(wù)就是一個 Runnable類型的對象清焕,任務(wù)的執(zhí)行方法就是 Runnable類型對象的run()方法
//處理任務(wù)的優(yōu)先級為:核心線程corePoolSize、任務(wù)隊列workQueue祭犯、最大線程maximumPoolSize秸妥,如果三者都滿了,使用handler處理被拒絕的任務(wù)
//設(shè)此時線程池中的數(shù)量為currentPoolSize,若currentPoolSize>corePoolSize,則創(chuàng)建新的線程執(zhí)行被添加的任務(wù),
//當(dāng)corePoolSize+workQueue>currentPoolSize>=corePoolSize,新增任務(wù)被放入緩沖隊列,
//當(dāng)maximumPoolSize>currentPoolSize>=corePoolSize+workQueue,建新線程來處理被添加的任務(wù),
//當(dāng)currentPoolSize>=maximumPoolSize,通過 handler所指定的策略來處理新添加的任務(wù)
//本例中可以同時可以被處理的任務(wù)最多為maximumPoolSize+WORKQUEUE=8個沃粗,其中最多5個在線程中正在處理粥惧,3個在緩沖隊列中等待被處理
//當(dāng)currentPoolSize>corePoolSize時,如果某線程空閑時間超過keepAliveTime最盅,線程將被終止突雪。這樣起惕,線程池可以動態(tài)的調(diào)整池中的線程數(shù)
threadPool.execute(new ThreadPoolTask(task));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
threadPool.shutdown();//關(guān)閉主線程,但線程池會繼續(xù)運行咏删,直到所有任務(wù)執(zhí)行完才會停止疤祭。若不調(diào)用該方法線程池會一直保持下去,以便隨時添加新的任務(wù)
}
}
package cn.gaialine.threadpool;
import java.io.Serializable;
/**
* 任務(wù)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 {
//模擬線程正在執(zhí)行任務(wù)
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依次最先執(zhí)行饵婆,這時候currentPoolSize=2達(dá)到了corePoolSize勺馆,task3、task4侨核、task5被送入緩沖隊列草穆,達(dá)到了workQueue最大值3,task6搓译、task7悲柱、task8開啟新的線程開始執(zhí)行,此時currentPoolSize=5達(dá)到了maximumPoolSize些己,task9豌鸡、task10根據(jù)AbortPolicy策略拋出異常,不再執(zhí)行task9和task10段标。10秒鐘后task1涯冠、task2....依次執(zhí)行完畢釋放線程,開始執(zhí)行隊列里的task3逼庞、task4蛇更、task5,最后task3赛糟、4派任、5執(zhí)行完畢,所有任務(wù)完成璧南。
JDK根據(jù)ThreadPoolExecutor配置好的線程池
// 固定工作線程數(shù)量的線程池
ExecutorService executorService1 = Executors.newFixedThreadPool(3);
// 一個可緩存的線程池
ExecutorService executorService2 = Executors.newCachedThreadPool();
// 單線程化的Executor
ExecutorService executorService3 = Executors.newSingleThreadExecutor();
// 支持定時的以及周期性的任務(wù)執(zhí)行
ExecutorService executorService4 = Executors.newScheduledThreadPool(3);
5. Java 內(nèi)存模型
內(nèi)存模型描述了程序中各個變量(實例域掌逛、靜態(tài)域和數(shù)組元素)之間的關(guān)系,以及在實際計算機(jī)系統(tǒng)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)司倚。
JVM中存在一個主存區(qū)(Main Memory或Java Heap Memory)豆混,Java中所有變量都是存在主存中的,對于所有線程進(jìn)行共享对湃,而每個線程又存在自己的工作內(nèi)存(Working Memory)崖叫,工作內(nèi)存中保存的是主存中某些變量的拷貝遗淳,線程對所有變量的操作并非發(fā)生在主存區(qū)拍柒,而是發(fā)生在工作內(nèi)存中,而線程之間是不能直接相互訪問屈暗,變量在程序中的傳遞拆讯,是依賴主存來完成的脂男。
JMM的最初目的,就是為了能夠支持多線程程序設(shè)計的种呐,每個線程可以認(rèn)為是和其他線程不同的CPU上運行宰翅,或者對于多處理器的機(jī)器而言,該模型需要實現(xiàn)的就是使得每一個線程就像運行在不同的機(jī)器爽室、不同的CPU或者本身就不同的線程上一樣汁讼。
6. volatile 與 synchronized
在Java中,為了保證多線程讀寫數(shù)據(jù)時保證數(shù)據(jù)的一致性,可以采用兩種方式:
- 同步
如用synchronized關(guān)鍵字,或者使用鎖對象. - volatile
使用volatile關(guān)鍵字
用一句話概括volatile,它能夠使變量在值發(fā)生改變時能盡快地讓其他線程知道.
volatile本質(zhì)是在告訴jvm當(dāng)前變量在寄存器中的值是不確定的,需要從主存中讀取,synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問該變量,其他線程被阻塞住.
volatile僅能使用在變量級別,synchronized則可以使用在變量,方法.
volatile僅能實現(xiàn)變量的修改可見性,但不具備原子特性,而synchronized則可以保證變量的修改可見性和原子性.
volatile不會造成線程的阻塞,而synchronized可能會造成線程的阻塞.
volatile標(biāo)記的變量不會被編譯器優(yōu)化,而synchronized標(biāo)記的變量可以被編譯器優(yōu)化.
7. 面試中遇到的問題
求一個無序數(shù)組的中位數(shù),白板寫code
因為時間有限阔墩,沒有多想嘿架,直接使用冒泡排序冒一半的數(shù)據(jù),另一半保持無序啸箫。如果有更好的方法請告訴我謝謝耸彪!
數(shù)據(jù)庫表的行轉(zhuǎn)列
使用case when就可以實現(xiàn)了,但是要注意需要對每個case when做max忘苛,以及最后的group by蝉娜,這樣可以去除null值。
使用map = new hashmap 比 hashmap = new hashmap 的好處
一時間沒想明白扎唾,因為覺得不會有人寫成hashmap = new hashmap召川,就答了一下實現(xiàn)依賴抽象,方便復(fù)用和修改胸遇,減少棧存儲之類亂七八糟的東西扮宠。。狐榔。如果有更好的答案也請告訴我謝謝??
== 和 equals 的區(qū)別
Object類中的equals方法和“==”是一樣的坛增,沒有區(qū)別,而String類薄腻,Integer類等等一些類收捣,是重寫了equals方法,才使得equals和“==不同”庵楷,所以罢艾,當(dāng)自己創(chuàng)建類時,自動繼承了Object的equals方法尽纽,要想實現(xiàn)不同的等于比較咐蚯,必須重寫equals方法。
"=="比"equal"運行速度快,因為"=="只是比較引用.
hashcode 和 equals 的具體實現(xiàn)方式
這真是個高頻問題弄贿,從大四找實習(xí)面試到研究生找工作面試到兩次跳槽一共幾十場技術(shù)面試幾乎全都問到了春锋。唯一一次沒問到的一次是因為我面的是算法工程師的職位,那次都在面機(jī)器學(xué)習(xí)的算法和實現(xiàn)……
默認(rèn) equals 方法直接調(diào)用了 ==
public boolean equals(Object obj) {
return (this == obj);
}
String 改寫了 equals
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中的方法差凹。默認(rèn)情況下期奔,Object中的hashCode() 返回對象的32位jvm內(nèi)存地址侧馅。也就是說如果對象不重寫該方法,則返回相應(yīng)對象的32為JVM內(nèi)存地址呐萌。
String類源碼中重寫的hashCode方法如下:
public int hashCode() {
int h = hash; //Default to 0 ### String類中的私有變量馁痴,
if (h == 0 && value.length > 0) { //private final char value[]; ### Sting類中保存的字符串內(nèi)容的的數(shù)組
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
總結(jié):
(1)綁定。當(dāng)equals方法被重寫時肺孤,通常有必要重寫 hashCode 方法罗晕,以維護(hù) hashCode 方法的常規(guī)協(xié)定,該協(xié)定聲明相等對象必須具有相等的哈希碼赠堵。
(2)綁定原因攀例。Hashtable實現(xiàn)一個哈希表,為了成功地在哈希表中存儲和檢索對象顾腊,用作鍵的對象必須實現(xiàn) hashCode 方法和 equals 方法粤铭。同(1),必須保證equals相等的對象杂靶,hashCode 也相等梆惯。因為哈希表通過hashCode檢索對象。
(3)默認(rèn)吗垮。
==默認(rèn)比較對象在JVM中的地址垛吗。
hashCode 默認(rèn)返回對象在JVM中的存儲地址。
equal比較對象烁登,默認(rèn)也是比較對象在JVM中的地址怯屉,同==
reference:
http://blog.csdn.net/sdyy321/article/details/7407685
http://blog.csdn.net/fanaticism1/article/details/9966163
http://www.cnblogs.com/dingyingsi/p/3760447.html
http://www.cnblogs.com/xudong-bupt/p/3960177.html