搬運(yùn)來源: https://github.com/jujunchen/Java-interview-question
1.Java 基礎(chǔ)
[TOC]
《Java編程思想》、《瘋狂Java:突破程序員基本功的16課.修訂版》,這里省略了一些非?;A(chǔ)的知識(shí)點(diǎn)
請(qǐng)你解釋為什么會(huì)出現(xiàn) 4.0 - 3.6=0.40000001 這種現(xiàn)象
二進(jìn)制小數(shù)無法精確表達(dá)十進(jìn)制小數(shù),計(jì)算機(jī)在計(jì)算十進(jìn)制小數(shù)的過程中要先轉(zhuǎn)換為二進(jìn)制進(jìn)行計(jì)算,這個(gè)過程中出現(xiàn)了誤差
請(qǐng)你講講一個(gè)十進(jìn)制的數(shù)在內(nèi)存中是怎么存的?
以二進(jìn)制補(bǔ)碼形式存儲(chǔ),最高位是符號(hào)位,正數(shù)的補(bǔ)碼是它的原碼,負(fù)數(shù)的補(bǔ)碼是它的反碼加1,在求反碼時(shí)符號(hào)位不變,其他取反,1表示負(fù)數(shù),0正數(shù)
接口和抽象類的區(qū)別是什么 ?
接口中的所有方法隱含的都是抽象的,而抽象類則可以同時(shí)包含抽象和非抽象方法
類可以實(shí)現(xiàn)很多個(gè)接口,但只能繼承一個(gè)抽象類
類可以不實(shí)現(xiàn)抽象類和接口聲明的所有方法,但這種情況下,該類必須聲明成抽象的
接口中的成員函數(shù)默認(rèn)是public的。抽象類的成員函數(shù)可以是private,protected或者是public。
JDK8后,接口中可以包含default方法,抽象類中不可以
面向?qū)ο箝_發(fā)的六個(gè)基本原則,在項(xiàng)目中用過哪些原則
-
六個(gè)基本原則
-
單一原則
一個(gè)類只做它該做的事情(高內(nèi)聚)。在面向?qū)ο笾?,如果只讓一個(gè)類完成它該做的事,而不涉及與它無關(guān)的領(lǐng)域就是踐行了高內(nèi)聚的原則,這個(gè)類就只有單一原則
-
開閉原則
軟件實(shí)體應(yīng)當(dāng)對(duì)擴(kuò)展開發(fā),對(duì)修改關(guān)閉,要做到開閉有兩個(gè)要點(diǎn):
- 抽象是關(guān)鍵,一個(gè)系統(tǒng)中如果沒有抽象類或接口系統(tǒng)就沒有擴(kuò)展點(diǎn)
- 封裝可變性,將系統(tǒng)中的各種可變因素封裝到一個(gè)繼承結(jié)構(gòu)中,如果多個(gè)可變因素混雜在一起,系統(tǒng)將變的復(fù)雜而繁亂
-
里氏替換原則
任何時(shí)候都可以用子類型替換掉父類型,子類一定是增加父類的能力而不是減少父類的能力
-
依賴倒置原則
面向接口編程。高層模塊不應(yīng)該依賴底層模塊,兩者都應(yīng)該依賴其抽象,盡可能使用抽象類型而不用具體類型,因?yàn)槌橄箢愋涂梢员凰娜魏我粋€(gè)子類型所替代。
-
接口隔離原則
類間的依賴關(guān)系應(yīng)該建立在最小的接口上,不能大而全,接口表示能力,一個(gè)接口只應(yīng)該描述一種能力,接口也應(yīng)該高度內(nèi)聚
合成聚成復(fù)用原則
has a 關(guān)聯(lián);use a;-
迪米特法則
由叫最少知識(shí)原則,一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象有盡可能少的了解
-
-
根據(jù)自己的項(xiàng)目來說
詳細(xì)的可以看這里:https://www.cnblogs.com/qifengshi/p/5709594.html
關(guān)于網(wǎng)絡(luò)方面的問題,面試官應(yīng)該只會(huì)問一題,不會(huì)多問
HTTP請(qǐng)求的GET與POST方式的區(qū)別
- GET在瀏覽器回退是無害的,而POST會(huì)再次提交請(qǐng)求
- GET請(qǐng)求會(huì)被瀏覽器主動(dòng)cache,而POST不會(huì),除非手動(dòng)設(shè)置
- GET請(qǐng)求只能進(jìn)行URL編碼,而POST支持多種編碼
- GET請(qǐng)求參數(shù)會(huì)被完整保留在瀏覽器歷史記錄中,而POST中的參數(shù)不會(huì)被保留
- GET請(qǐng)求在URL中傳送參數(shù)是有大小限制的,不能大于2KB,而POST可以說沒有
- GET只接受ASCII字符,而POST沒有限制
- GET參數(shù)直接暴露在URL上,而POST將數(shù)據(jù)放在request body中
TCP 三次握手,四次揮手
可見該文:https://blog.csdn.net/qq_38950316/article/details/81087809
TCP 和 UDP區(qū)別
-
區(qū)別:
UDP是無連接的,即發(fā)送數(shù)據(jù)之前不需要建立連接
UDP使用盡最大努力交付,即不保證可靠交付,同時(shí)也不使用擁塞控制
UDP是面向報(bào)文的,沒有擁塞控制,適合多媒體通信要求
UDP支持一對(duì)一,一對(duì)多,多對(duì)一和多對(duì)多的交互通信
UDP首部開銷小,只有8個(gè)字節(jié)
TCP是面向連接的運(yùn)輸層協(xié)議
TCP只能一對(duì)一連接
TCP提供可靠的交付服務(wù),提供全雙工通信
TCP 面向字節(jié)流,頭部最低20個(gè)字節(jié)
從輸入網(wǎng)址到獲取頁面的過程
- 查詢DNS, 獲取域名對(duì)應(yīng)的IP地址
- 瀏覽器搜索自身的DNS緩存
- 搜索操作系統(tǒng)的DNS緩存
- 讀取本地的HOST文件
- 發(fā)起一個(gè)DNS系統(tǒng)調(diào)用(寬帶運(yùn)營服務(wù)器查看本身緩存,運(yùn)營服務(wù)器發(fā)起一個(gè)迭代DNS解析請(qǐng)求)
- 瀏覽器獲得域名對(duì)應(yīng)的IP地址后,發(fā)起HTTP三次握手
- TCP/IP建立連接后,瀏覽器可以向服務(wù)器發(fā)送HTTP請(qǐng)求了
- 服務(wù)器接收到請(qǐng)求后,根據(jù)路徑參數(shù),經(jīng)過后端處理將頁面返回給瀏覽器
- 瀏覽器渲染頁面,和外部資源,最終將完整的頁面呈現(xiàn)給用戶
Session與Cookie區(qū)別
Session:
服務(wù)器端會(huì)為每個(gè)訪問服務(wù)端的請(qǐng)求分配一個(gè)會(huì)話Session,其數(shù)據(jù)存儲(chǔ)在服務(wù)器端,不依賴瀏覽器端環(huán)境,因此高效安全
Cookie:
數(shù)據(jù)已文件形式存在用戶瀏覽器端,用戶可以通過瀏覽器禁用Cookie,用戶可以對(duì)Cookie進(jìn)行查看,修改,和刪除
列出自己常用的JDK包
常用的包:
java.lang 包裝類,線程等都在該包
java.match 有BigDecimal 精確數(shù)字類型
java.util 并發(fā),集合等都在該包內(nèi)
equals與==的區(qū)別
-
equals 比較兩個(gè)實(shí)體值是否相同,可以被覆蓋,但需要遵循幾個(gè)約定:
自反性:對(duì)于任何非null的引用值x, x.equals(x)必須返回true
對(duì)稱性:對(duì)于任何非null的引用值x和y,當(dāng)y.equals(x)返回true時(shí),x.equlas(y)必須返回true
傳遞性:對(duì)于任何非null的引用值x、y、z,如果x.equals(y)返回true,并且y.equals(x)也返回true,那么x.equals(z)也必須返回true
一致性:對(duì)于任何非null的引用值x和y,只要比較對(duì)象中的所有信息沒有被修改,多次調(diào)用equals一致返回true,或者false
== 比較兩個(gè)實(shí)體的引用地址是否相等,不能覆蓋,如果引用地址相等,那認(rèn)為兩個(gè)實(shí)體為同一個(gè)實(shí)體
int a = 1;
Integer b = new Integer(1);
Integer c = new Integer(1);
Integer d = Integer.valueOf(1);
Long e = new Long(1);
Long f = Long.valueOf(1);
assert a == b;
assert b != c;
assert b != d;
assert a == d;
assert a == e;
assert a == f;
assert b.equals(c);
assert !b.equals(e);
hashCode和equals方法的區(qū)別與聯(lián)系
對(duì)于覆蓋了equals方法的類中,同樣也要覆蓋hashCode方法。這是JDK規(guī)定的結(jié)果。
比較兩個(gè)對(duì)象是否相同,hashCode比equals效率更高,所以優(yōu)先會(huì)根據(jù)hashCode來比較,但如果不重寫hashCode,原本兩個(gè)對(duì)象可以認(rèn)為是相等,但由于hashCode默認(rèn)返回表示對(duì)象地址的整數(shù),必然不相等,所以需要重寫hashCode。
由于hashCode有個(gè)問題,可能兩個(gè)不同的對(duì)象會(huì)有相同的hashCode,這樣還需要通過equals來比較
比如HashMap中,計(jì)算key的索引位置,會(huì)用到key.hashCode,在確定是否為同一個(gè)元素時(shí)通過equals比較
什么是Java序列化和反序列化,如何實(shí)現(xiàn)Java序列化?或者請(qǐng)解釋Serializable 接口的作用
序列化是一種用來處理對(duì)象流的機(jī)制,也就是將對(duì)象的內(nèi)容轉(zhuǎn)化成二進(jìn)制流,可以將對(duì)象持久化或者網(wǎng)絡(luò)傳輸
反序列化是將二進(jìn)制流還原為對(duì)象的過程
實(shí)現(xiàn)Java序列化,通過實(shí)現(xiàn)Serializable即可
Object類中常見的方法,為什么wait notify會(huì)放在Object里邊?
因?yàn)镴ava提供的鎖是對(duì)象級(jí)的,每個(gè)對(duì)象都有對(duì)象頭,用來存儲(chǔ)鎖
解釋一下extends 和super泛型限定符
<? extends Fruit> 稱為 上界限定符,list只能get,不能add(除了add null值),通常用于讀
<? super Apple>稱為 下界限定符,list只能add,不能get(只能用Object接收),通過用于寫
請(qǐng)列舉你所知道的Object類的方法并簡要說明
Object()默認(rèn)構(gòu)造方法
clone()創(chuàng)建并返回此對(duì)象的一個(gè)副本
equals(Object obj) 當(dāng)前對(duì)象是否與obj對(duì)象相同
finalize()當(dāng)垃圾收集器確定該對(duì)象可以回收時(shí),由垃圾收集器調(diào)用此方法
getClass返回一個(gè)對(duì)象的運(yùn)行時(shí)類
hashCode()返回該對(duì)象的哈希碼值
notify()喚醒此對(duì)象監(jiān)視器上等待的單個(gè)線程
notifyAll()喚醒在此對(duì)象監(jiān)視器上等待的所有線程
toString()返回該對(duì)象的字符串表示
wait()使當(dāng)前線程等待,直到其他線程調(diào)用此對(duì)象的notify()或者notifyAll()方法
wait(long timeout)導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法,或者超過指定的時(shí)間量。wait(long timeout, int nanos) 導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法,或者其他某個(gè)線程中斷當(dāng)前線程,或者已超過某個(gè)實(shí)際時(shí)間量。
創(chuàng)建線程的方式
- 繼承Thread類創(chuàng)建線程,并重寫run方法,調(diào)用實(shí)例對(duì)象的start()方法啟動(dòng)線程
- 實(shí)現(xiàn)Runnable接口,并實(shí)現(xiàn)run方法,將實(shí)現(xiàn)Runnable的類傳入Thread構(gòu)造函數(shù)中,并調(diào)用Thread實(shí)例對(duì)象的start方法啟動(dòng)線程
- 實(shí)現(xiàn)Callable接口,并實(shí)現(xiàn)call方法,創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例,使用FutureTask包裝Callable對(duì)象,使用FutureTask對(duì)象傳入Thread中,調(diào)用start方法啟動(dòng)線程,使用FutureTask對(duì)象的get方法獲取線程的返回值
ArrayList 與 LinkedList 區(qū)別
ArrayList 是一種順序存儲(chǔ)的線性表,底層使用數(shù)組實(shí)現(xiàn)
LinkedList是一種鏈?zhǔn)酱鎯?chǔ)的線性表,本質(zhì)是一個(gè)雙向鏈表,實(shí)現(xiàn)了List、Deque接口,可以當(dāng)成雙向鏈表、隊(duì)列、棧使用
自定義注解
-
聲明注解的保留期限類型
@Retention(RetentionPolicy.RUNTIME)表示該注解可以在運(yùn)行期保留
保留期限類型:java.lang.annotation.Retention
SOURCE: 注解信息僅保留在目標(biāo)類源代碼文件中,對(duì)應(yīng)的字節(jié)碼文件不會(huì)保留
CLASS: 注解信息存在于源代碼、字節(jié)碼文件中,但運(yùn)行期JVM不能獲得該注解信息
RUNTIME: 注解信息存在于源代碼、字節(jié)碼文件、運(yùn)行期JVM中,能夠通過反射機(jī)制獲取注解類信息
-
聲明注解可以使用的目標(biāo)類型
@Target(ElementType.METHOD) 表示這個(gè)注解只能在方法上使用
目標(biāo)類型:java.lang.annotation.ElementType
TYPE: 類、接口、注解類、Enum
FIELD: 類成員變量或常量
METHOD: 方法
PARAMETER: 參數(shù)
CONSTRUCTOR: 構(gòu)造器
LOCAL_VARIABLE: 局部變量
ANNOTATION_TYPE: 注解
PACKAGE: 包
使用@interface 修飾類
-
聲明注解成員
成員無入?yún)ⅰ⒉荒軖伋霎惓#?/p>
可以通過default成員指定默認(rèn)值
成員類型只能使用基本數(shù)據(jù)類型、String、Class、enums、注解類型,及上述類型的數(shù)組類型。如ForumService value()是非法的
如果注解只有一個(gè)成員,則成員名必須取名為value(),再使用時(shí)可以忽略成員名和賦值號(hào),如果注解類擁有多個(gè)成員時(shí),
對(duì)value成員賦值,可以省略value和賦值號(hào),如果是多個(gè)成員賦值,必須使用賦值號(hào)
ArrayList擴(kuò)容機(jī)制是怎么樣的? 詳細(xì)說一下。
在往ArrayList add元素的時(shí)候,如果ArrayList 已有元素?cái)?shù)量+1 大于 ArrayList 存儲(chǔ)元素的總長度,就會(huì)觸發(fā)擴(kuò)容。
首先ArrayList會(huì)計(jì)算新數(shù)組的長度,長度為老數(shù)組的0.5倍,如果新數(shù)組長度還是小于插入元素需要的最小長度,那么新數(shù)組長度賦值為最小長度,如果超過ArrayList允許的最大長度Integer.MAX_VALUE(),那么新數(shù)組長度為Integer.MAX_VALUE,否則為Integer.MAX_VALUE - 8(為什么要-8?Why the maximum array size of ArrayList is Integer.MAX_VALUE - 8?)
最后將原數(shù)組元素拷貝到新數(shù)組進(jìn)行擴(kuò)容
HashMap 1.7 和 1.8 的區(qū)別
1.7,在發(fā)生hash沖突的時(shí)候,數(shù)據(jù)結(jié)構(gòu)只有鏈表;
-
1.8,數(shù)據(jù)結(jié)構(gòu)有鏈表和紅黑樹,使用紅黑樹是為了能夠提高查詢效率。在鏈表長度達(dá)到7時(shí)(bingCount >= TREEIFY_THRESHOLD - 1),并且hash tab[]數(shù)組長度大于等于64時(shí),將鏈表轉(zhuǎn)換成紅黑樹,如果數(shù)組長度小于64,只是對(duì)數(shù)組進(jìn)行擴(kuò)容
HashMap中的key可以是任何對(duì)象或數(shù)據(jù)類型嗎
- 可以是null,但不能是可變對(duì)象,如果是可變對(duì)象,對(duì)象中的屬性改變,則對(duì)象的HashCode也相應(yīng)改變,導(dǎo)致下次無法查找到已存在Map中的數(shù)據(jù)
- 如果要可變對(duì)象當(dāng)著鍵,必須保證其HashCode在成員屬性改變的時(shí)候保持不變
HashMap 初始容量 計(jì)算方法
如果在new HashMap的時(shí)候,沒有指定初始initialCapacity,則初始initialCapacity為16,負(fù)載因子為0.75,下次擴(kuò)容閾值為 16*0.75=12
這個(gè)初始容量 不一定等于初始化完成后底層數(shù)組實(shí)際的容量,因?yàn)榇嬖陂撝档挠?jì)算,方法如下;也不是初始容量是多少開始就能存多少個(gè)元素,因?yàn)榇嬖谪?fù)載因子,在底層數(shù)組還沒滿的時(shí)候就會(huì)進(jìn)行擴(kuò)容
閾值計(jì)算方法為:
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
該方法計(jì)算大于等于輸入?yún)?shù)并最接近參數(shù)的2的整數(shù)次冪的數(shù),如10,返回16
cap -1 ,-1是為了在計(jì)算的時(shí)候能得到大于等于輸入?yún)?shù)的值
在HashMap 進(jìn)行put方法的時(shí)候,如果判斷已有的元素大于 閾值就會(huì)觸發(fā)擴(kuò)容計(jì)算,擴(kuò)容步驟
final Node<K,V>[] resize() {
//原table數(shù)組賦值
Node<K,V>[] oldTab = table;
//如果原數(shù)組為null,那么原數(shù)組長度為0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//賦值閾值
int oldThr = threshold;
//newCap 新數(shù)組長度
//newThr 下次擴(kuò)容的閾值
int newCap, newThr = 0;
// 1. 如果原數(shù)組長度大于0
if (oldCap > 0) {
//如果大于最大長度1 << 30 = 1073741824,那么閾值賦值為Integer.MAX_VALUE后直接返回
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 2. 如果原數(shù)組長度的2倍小于最大長度,并且原數(shù)組長度大于默認(rèn)長度16,那么新閾值為原閾值的2倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 3. 如果原數(shù)組長度等于0,但原閾值大于0,那么新的數(shù)組長度賦值為原閾值大小
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// 4. 如果原數(shù)組長度為0,閾值為0,那么新數(shù)組長度,新閾值都初始化為默認(rèn)值
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 5.如果新的閾值等于0
if (newThr == 0) {
//計(jì)算臨時(shí)閾值
float ft = (float)newCap * loadFactor;
//新數(shù)組長度小于最大長度,臨時(shí)閾值也小于最大長度,新閾值為臨時(shí)閾值,否則是Integer.MAX_VALUE
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//計(jì)算出來的新閾值賦值給對(duì)象的閾值
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//用新計(jì)算的數(shù)組長度新建一個(gè)Node數(shù)組,并賦值給對(duì)象的table
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//后面是copy數(shù)組和鏈表數(shù)據(jù)邏輯
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
總結(jié):
如下3種情況,例子需要自己調(diào)式,主要關(guān)注數(shù)組長度(OldCap,newCap)新老變化,擴(kuò)容閾值(oldThr,newThr)新老變化
//①
Map<String, String> map = new HashMap<>();
map.put("1", "1");
//②
Map<String, String> map1 = new HashMap<>(2);
map1.put("2", "2");
//③
Map<String, String> map2 = new HashMap<>(2, 0.5f);
map2.put("3", "3");
① 沒有設(shè)置initialCapacity,也沒有設(shè)置負(fù)載因子,第一次put的時(shí)候會(huì)觸發(fā)擴(kuò)容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
第一次的時(shí)候,數(shù)組長度為默認(rèn)值16,閾值為16*0.75=12,走的代碼4邏輯,等到數(shù)組長度超過閾值12后,觸發(fā)第二次擴(kuò)容,此時(shí)table數(shù)組,和threshold都不為0,即oldTab、oldCap、oldThr都不為0,先走代碼1,如果oldCap長度的2倍沒有超過最大容量,并且oldCap 長度大于等于 默認(rèn)容量16,那么下次擴(kuò)容的閾值 變?yōu)閛ldThr大小的兩倍即 12 * 2 = 24,newThr = 24,newCap=32
② 設(shè)置了initialCapacity,沒有設(shè)置負(fù)載因子,此時(shí)hashMap使用默認(rèn)負(fù)載因子0.75,本實(shí)例設(shè)置的初始容量為2,通過計(jì)算閾值為2,第一次put的時(shí)候由于還沒初始化table數(shù)組,因此觸發(fā)第一次擴(kuò)容。此時(shí)oldCap為0,oldThr為2,走代碼3,確定這次擴(kuò)容的新數(shù)組大小為2,此時(shí)還沒有確定newThr 下次擴(kuò)容的大小,于是進(jìn)入代碼5 確定newThr為 2 * 0.75 = 1.5 取整 1 ,及下次擴(kuò)容閾值為1。當(dāng)數(shù)組已有元素大于閾值及1時(shí),觸發(fā)第二次擴(kuò)容,此時(shí)oldCap為1,oldThr為1,走代碼1newCap = oldCap << 1 結(jié)果為 4 小于最大容量, 但oldCap 小于hashMap默認(rèn)大小16,結(jié)果為false,跳出判斷,此時(shí)由于newThr等于0,進(jìn)入代碼5,確定newThr為 4 * 0.75 = 3,下次擴(kuò)容閾值為3
③ 設(shè)置了initialCapacity=2,負(fù)載因子為0.5,通過tableSizeFor計(jì)算閾值為2,第一次put的時(shí)候,進(jìn)行擴(kuò)容,此時(shí)oldCap為2,oldThr為2,進(jìn)入代碼1,同實(shí)例②,newCap = oldCap << 1 結(jié)果為 4 小于最大容量, 但oldCap 小于hashMap默認(rèn)大小16,結(jié)果為false,跳出判斷,進(jìn)入代碼5,確定newThr為 4 * 0.5 = 2,下次擴(kuò)容閾值為2
面試回答要素
- 回答什么情況下會(huì)第一擴(kuò)容,舉例說明,新數(shù)組大小,閾值大小
- 以后什么情況下會(huì)再次擴(kuò)容,這次是怎么計(jì)算新數(shù)組大小,及閾值大小的
HashMap、ConcurrentHashMap初始化閾值為什么要是8,才轉(zhuǎn)為紅黑樹?
- 當(dāng)初始閾值為8時(shí),鏈表的長度達(dá)到8的概率變的很小,如果再大概率減小的并不明顯
- 樹結(jié)構(gòu)查找的時(shí)間復(fù)雜度是O(log(n)),而鏈表的時(shí)間復(fù)雜度是O(n),當(dāng)閾值為8時(shí),long8 = 3,相比鏈表更快,但樹結(jié)構(gòu)比鏈表占用的空間更多,所以這是一種時(shí)間和空間的平衡
手寫HashMap
要點(diǎn):
- 底層是數(shù)組結(jié)構(gòu)
- hash沖突的時(shí)候,轉(zhuǎn)換為鏈表
- 考慮擴(kuò)容處理
HashMap在并發(fā)下會(huì)產(chǎn)生什么問題?有什么替代方案?(HashTable, ConcurrentHashMap)。它們兩者的實(shí)現(xiàn)原理。
HashMap并發(fā)下產(chǎn)生問題:由于在發(fā)生hash沖突,插入鏈表的時(shí)候,多線程會(huì)造成環(huán)鏈,再get的時(shí)候變成死循環(huán),Map.size()不準(zhǔn)確,數(shù)據(jù)丟失
https://www.iteye.com/blog/hwl-sz-1897468HashTable: 通過synchronized來修飾,效率低,多線程put的時(shí)候,只能有一個(gè)線程成功,其他線程都處于阻塞狀態(tài)
-
ConcurrentHashMap:
1.7 采用鎖分段技術(shù)提高并發(fā)訪問率
1.8 數(shù)據(jù)依舊是分段存儲(chǔ),但鎖采用了synchronized,內(nèi)部采用Node數(shù)組+鏈表+紅黑樹的結(jié)構(gòu)存儲(chǔ),當(dāng)單個(gè)鏈表存儲(chǔ)數(shù)量達(dá)到紅黑樹閾值8時(shí)(此時(shí)鏈表已有元素7),并且數(shù)組長度大于64時(shí),存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為紅黑樹來存儲(chǔ),否則只進(jìn)行數(shù)組的擴(kuò)容
HashMap 為什么不用平衡樹,而用紅黑樹
紅黑樹也是一種平衡樹,但不是嚴(yán)格平衡,平衡樹是左右子樹高度差不超過1,紅黑樹可以是2倍
紅黑樹在插入、刪除的時(shí)候旋轉(zhuǎn)的概率比平衡樹低很多,效率比平衡樹高
查找時(shí)間復(fù)雜度都維持在O(logN)
關(guān)于HashMap的其他文章:https://blog.csdn.net/login_sonata/article/details/76598675
常見異常分為哪兩種(Exception,Error),區(qū)別是什么,了解受檢異常和非受檢異常嗎
Exception,Error有共同的父類Throwable
Error: 表示程序發(fā)生錯(cuò)誤,是程序無法處理的,不可恢復(fù)的,如OutOfMemoryError
Exception: 表示程序可處理的異常,又分為CheckedException(受檢異常)、UncheckedException(非受檢異常),受檢異常發(fā)生在編譯期,必須要使用try...catch 或者 throws捕獲或者拋出異常,否則編譯不通過;非受檢異常發(fā)生在運(yùn)行期,具有不確定性,主要由程序的邏輯問題引起的,在程序設(shè)計(jì)的時(shí)候要認(rèn)真考慮,盡量處理異常
實(shí)現(xiàn)一個(gè)LRU
可以直接使用LinkedHashMap實(shí)現(xiàn)
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private int initialCapacity;
public LRUCache(int initialCapacity) {
//true表示按照訪問順序排序
super(initialCapacity, 0.75f, true);
this.initialCapacity = initialCapacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
if (size() > initialCapacity) {
return true;
}
return false;
}
}
用過流沒有,流怎么實(shí)現(xiàn)
Stream流是Java8中引入的新特性,Stream有幾個(gè)特點(diǎn):
不存數(shù)據(jù),都是通過管道將源數(shù)據(jù)元素傳遞給操作;
對(duì)Stream的任何修改都不會(huì)修改數(shù)據(jù)源,都是新產(chǎn)生一個(gè)流
流的很多操作如filter、map都是延遲執(zhí)行的,只有到終點(diǎn)才會(huì)將操作順序執(zhí)行
對(duì)于無限流可以通過“短路”操作訪問到有限元素后就返回
流的元素只訪問一次,如果需要重新訪問,需要重新生成一個(gè)新的流
Stream中BaseStream規(guī)定了流的基本接口,在Stream中使用Stage來描述一個(gè)完整的操作,將具有先后順序的各個(gè)Stage連一起,就構(gòu)成了整個(gè)流水線。
AbstractPipeline是流水線的核心,定義了三個(gè)AbstractPipeline類型的變量:sourceStage(源階段)、previousStage(上游pipeline,上一階段),nexStage(下一階段)
ReferencePipeline 繼承了AbstractPipeline
Head、StatefulOp、StatelessOp繼承了ReferencePipeline,分別表示源,無狀態(tài)操作,有狀態(tài)操作

比如Collection.stream()方法得到Head也就是stage0,緊接著調(diào)用一系列的中間操作,不斷產(chǎn)生新的stage。這些stage對(duì)象以雙向鏈表的形式組織在一起,構(gòu)成整個(gè)流水線。
由于每個(gè)Stage都記錄了前一個(gè)Stage和本次的操作以及回調(diào)函數(shù),依靠這種結(jié)構(gòu)就建立起對(duì)數(shù)據(jù)源的所有操作。