搬運來源: https://github.com/jujunchen/Java-interview-question
1.Java 基礎(chǔ)
[TOC]
《Java編程思想》篡诽、《瘋狂Java:突破程序員基本功的16課.修訂版》,這里省略了一些非逞缕基礎(chǔ)的知識點
請你解釋為什么會出現(xiàn) 4.0 - 3.6=0.40000001 這種現(xiàn)象
二進制小數(shù)無法精確表達十進制小數(shù),計算機在計算十進制小數(shù)的過程中要先轉(zhuǎn)換為二進制進行計算杈女,這個過程中出現(xiàn)了誤差
請你講講一個十進制的數(shù)在內(nèi)存中是怎么存的朱浴?
以二進制補碼形式存儲吊圾,最高位是符號位,正數(shù)的補碼是它的原碼翰蠢,負數(shù)的補碼是它的反碼加1项乒,在求反碼時符號位不變,其他取反梁沧,1表示負數(shù)檀何,0正數(shù)
接口和抽象類的區(qū)別是什么 ?
接口中的所有方法隱含的都是抽象的廷支,而抽象類則可以同時包含抽象和非抽象方法
類可以實現(xiàn)很多個接口频鉴,但只能繼承一個抽象類
類可以不實現(xiàn)抽象類和接口聲明的所有方法,但這種情況下恋拍,該類必須聲明成抽象的
接口中的成員函數(shù)默認是public的垛孔。抽象類的成員函數(shù)可以是private,protected或者是public施敢。
JDK8后周荐,接口中可以包含default方法,抽象類中不可以
面向?qū)ο箝_發(fā)的六個基本原則悯姊,在項目中用過哪些原則
-
六個基本原則
-
單一原則
一個類只做它該做的事情(高內(nèi)聚)羡藐。在面向?qū)ο笾校绻蛔屢粋€類完成它該做的事悯许,而不涉及與它無關(guān)的領(lǐng)域就是踐行了高內(nèi)聚的原則仆嗦,這個類就只有單一原則
-
開閉原則
軟件實體應(yīng)當對擴展開發(fā),對修改關(guān)閉先壕,要做到開閉有兩個要點:
- 抽象是關(guān)鍵瘩扼,一個系統(tǒng)中如果沒有抽象類或接口系統(tǒng)就沒有擴展點
- 封裝可變性,將系統(tǒng)中的各種可變因素封裝到一個繼承結(jié)構(gòu)中垃僚,如果多個可變因素混雜在一起集绰,系統(tǒng)將變的復(fù)雜而繁亂
-
里氏替換原則
任何時候都可以用子類型替換掉父類型,子類一定是增加父類的能力而不是減少父類的能力
-
依賴倒置原則
面向接口編程谆棺。高層模塊不應(yīng)該依賴底層模塊栽燕,兩者都應(yīng)該依賴其抽象,盡可能使用抽象類型而不用具體類型改淑,因為抽象類型可以被它的任何一個子類型所替代碍岔。
-
接口隔離原則
類間的依賴關(guān)系應(yīng)該建立在最小的接口上,不能大而全朵夏,接口表示能力蔼啦,一個接口只應(yīng)該描述一種能力,接口也應(yīng)該高度內(nèi)聚
合成聚成復(fù)用原則
has a 關(guān)聯(lián)仰猖;use a;-
迪米特法則
由叫最少知識原則捏肢,一個對象應(yīng)該對其他對象有盡可能少的了解
-
-
根據(jù)自己的項目來說
關(guān)于網(wǎng)絡(luò)方面的問題奈籽,面試官應(yīng)該只會問一題,不會多問
HTTP請求的GET與POST方式的區(qū)別
- GET在瀏覽器回退是無害的鸵赫,而POST會再次提交請求
- GET請求會被瀏覽器主動cache,而POST不會衣屏,除非手動設(shè)置
- GET請求只能進行URL編碼,而POST支持多種編碼
- GET請求參數(shù)會被完整保留在瀏覽器歷史記錄中奉瘤,而POST中的參數(shù)不會被保留
- GET請求在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使用盡最大努力交付卖局,即不保證可靠交付,同時也不使用擁塞控制
UDP是面向報文的双霍,沒有擁塞控制砚偶,適合多媒體通信要求
UDP支持一對一,一對多洒闸,多對一和多對多的交互通信
UDP首部開銷小染坯,只有8個字節(jié)
TCP是面向連接的運輸層協(xié)議
TCP只能一對一連接
TCP提供可靠的交付服務(wù),提供全雙工通信
TCP 面向字節(jié)流丘逸,頭部最低20個字節(jié)
從輸入網(wǎng)址到獲取頁面的過程
- 查詢DNS单鹿, 獲取域名對應(yīng)的IP地址
- 瀏覽器搜索自身的DNS緩存
- 搜索操作系統(tǒng)的DNS緩存
- 讀取本地的HOST文件
- 發(fā)起一個DNS系統(tǒng)調(diào)用(寬帶運營服務(wù)器查看本身緩存,運營服務(wù)器發(fā)起一個迭代DNS解析請求)
- 瀏覽器獲得域名對應(yīng)的IP地址后深纲,發(fā)起HTTP三次握手
- TCP/IP建立連接后仲锄,瀏覽器可以向服務(wù)器發(fā)送HTTP請求了
- 服務(wù)器接收到請求后,根據(jù)路徑參數(shù)湃鹊,經(jīng)過后端處理將頁面返回給瀏覽器
- 瀏覽器渲染頁面儒喊,和外部資源,最終將完整的頁面呈現(xiàn)給用戶
Session與Cookie區(qū)別
Session:
服務(wù)器端會為每個訪問服務(wù)端的請求分配一個會話Session币呵,其數(shù)據(jù)存儲在服務(wù)器端怀愧,不依賴瀏覽器端環(huán)境,因此高效安全
Cookie:
數(shù)據(jù)已文件形式存在用戶瀏覽器端余赢,用戶可以通過瀏覽器禁用Cookie芯义,用戶可以對Cookie進行查看,修改没佑,和刪除
列出自己常用的JDK包
常用的包:
java.lang 包裝類毕贼,線程等都在該包
java.match 有BigDecimal 精確數(shù)字類型
java.util 并發(fā),集合等都在該包內(nèi)
equals與==的區(qū)別
-
equals 比較兩個實體值是否相同蛤奢,可以被覆蓋鬼癣,但需要遵循幾個約定:
自反性:對于任何非null的引用值x, x.equals(x)必須返回true
對稱性:對于任何非null的引用值x和y陶贼,當y.equals(x)返回true時,x.equlas(y)必須返回true
傳遞性:對于任何非null的引用值x待秃、y拜秧、z,如果x.equals(y)返回true章郁,并且y.equals(x)也返回true枉氮,那么x.equals(z)也必須返回true
一致性:對于任何非null的引用值x和y,只要比較對象中的所有信息沒有被修改暖庄,多次調(diào)用equals一致返回true聊替,或者false
== 比較兩個實體的引用地址是否相等,不能覆蓋培廓,如果引用地址相等惹悄,那認為兩個實體為同一個實體
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)系
對于覆蓋了equals方法的類中,同樣也要覆蓋hashCode方法肩钠。這是JDK規(guī)定的結(jié)果泣港。
比較兩個對象是否相同,hashCode比equals效率更高价匠,所以優(yōu)先會根據(jù)hashCode來比較当纱,但如果不重寫hashCode,原本兩個對象可以認為是相等踩窖,但由于hashCode默認返回表示對象地址的整數(shù)坡氯,必然不相等,所以需要重寫hashCode毙石。
由于hashCode有個問題廉沮,可能兩個不同的對象會有相同的hashCode,這樣還需要通過equals來比較
比如HashMap中徐矩,計算key的索引位置滞时,會用到key.hashCode,在確定是否為同一個元素時通過equals比較
什么是Java序列化和反序列化滤灯,如何實現(xiàn)Java序列化坪稽?或者請解釋Serializable 接口的作用
序列化是一種用來處理對象流的機制,也就是將對象的內(nèi)容轉(zhuǎn)化成二進制流鳞骤,可以將對象持久化或者網(wǎng)絡(luò)傳輸
反序列化是將二進制流還原為對象的過程
實現(xiàn)Java序列化窒百,通過實現(xiàn)Serializable即可
Object類中常見的方法,為什么wait notify會放在Object里邊豫尽?
因為Java提供的鎖是對象級的篙梢,每個對象都有對象頭,用來存儲鎖
解釋一下extends 和super泛型限定符
<? extends Fruit> 稱為 上界限定符美旧,list只能get渤滞,不能add(除了add null值),通常用于讀
<? super Apple>稱為 下界限定符贬墩,list只能add,不能get(只能用Object接收),通過用于寫
請列舉你所知道的Object類的方法并簡要說明
Object()默認構(gòu)造方法
clone()創(chuàng)建并返回此對象的一個副本
equals(Object obj) 當前對象是否與obj對象相同
finalize()當垃圾收集器確定該對象可以回收時,由垃圾收集器調(diào)用此方法
getClass返回一個對象的運行時類
hashCode()返回該對象的哈希碼值
notify()喚醒此對象監(jiān)視器上等待的單個線程
notifyAll()喚醒在此對象監(jiān)視器上等待的所有線程
toString()返回該對象的字符串表示
wait()使當前線程等待妄呕,直到其他線程調(diào)用此對象的notify()或者notifyAll()方法
wait(long timeout)導(dǎo)致當前的線程等待陶舞,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量绪励。wait(long timeout, int nanos) 導(dǎo)致當前的線程等待肿孵,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程疏魏,或者已超過某個實際時間量停做。
創(chuàng)建線程的方式
- 繼承Thread類創(chuàng)建線程,并重寫run方法蠢护,調(diào)用實例對象的start()方法啟動線程
- 實現(xiàn)Runnable接口雅宾,并實現(xiàn)run方法,將實現(xiàn)Runnable的類傳入Thread構(gòu)造函數(shù)中葵硕,并調(diào)用Thread實例對象的start方法啟動線程
- 實現(xiàn)Callable接口,并實現(xiàn)call方法贯吓,創(chuàng)建Callable實現(xiàn)類的實例懈凹,使用FutureTask包裝Callable對象,使用FutureTask對象傳入Thread中悄谐,調(diào)用start方法啟動線程介评,使用FutureTask對象的get方法獲取線程的返回值
ArrayList 與 LinkedList 區(qū)別
ArrayList 是一種順序存儲的線性表,底層使用數(shù)組實現(xiàn)
LinkedList是一種鏈式存儲的線性表爬舰,本質(zhì)是一個雙向鏈表们陆,實現(xiàn)了List、Deque接口情屹,可以當成雙向鏈表坪仇、隊列、棧使用
自定義注解
-
聲明注解的保留期限類型
@Retention(RetentionPolicy.RUNTIME)表示該注解可以在運行期保留
保留期限類型:java.lang.annotation.Retention
SOURCE: 注解信息僅保留在目標類源代碼文件中垃你,對應(yīng)的字節(jié)碼文件不會保留
CLASS: 注解信息存在于源代碼椅文、字節(jié)碼文件中,但運行期JVM不能獲得該注解信息
RUNTIME: 注解信息存在于源代碼惜颇、字節(jié)碼文件皆刺、運行期JVM中,能夠通過反射機制獲取注解類信息
-
聲明注解可以使用的目標類型
@Target(ElementType.METHOD) 表示這個注解只能在方法上使用
目標類型:java.lang.annotation.ElementType
TYPE: 類凌摄、接口羡蛾、注解類、Enum
FIELD: 類成員變量或常量
METHOD: 方法
PARAMETER: 參數(shù)
CONSTRUCTOR: 構(gòu)造器
LOCAL_VARIABLE: 局部變量
ANNOTATION_TYPE: 注解
PACKAGE: 包
使用@interface 修飾類
-
聲明注解成員
成員無入?yún)⑾强鳌⒉荒軖伋霎惓#?/p>
可以通過default成員指定默認值
成員類型只能使用基本數(shù)據(jù)類型痴怨、String煎殷、Class、enums腿箩、注解類型豪直,及上述類型的數(shù)組類型。如ForumService value()是非法的
如果注解只有一個成員珠移,則成員名必須取名為value()弓乙,再使用時可以忽略成員名和賦值號,如果注解類擁有多個成員時钧惧,
對value成員賦值暇韧,可以省略value和賦值號,如果是多個成員賦值浓瞪,必須使用賦值號
ArrayList擴容機制是怎么樣的懈玻? 詳細說一下。
在往ArrayList add元素的時候乾颁,如果ArrayList 已有元素數(shù)量+1 大于 ArrayList 存儲元素的總長度涂乌,就會觸發(fā)擴容。
首先ArrayList會計算新數(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ù)組進行擴容
HashMap 1.7 和 1.8 的區(qū)別
1.7吭狡,在發(fā)生hash沖突的時候尖殃,數(shù)據(jù)結(jié)構(gòu)只有鏈表;
-
1.8划煮,數(shù)據(jù)結(jié)構(gòu)有鏈表和紅黑樹送丰,使用紅黑樹是為了能夠提高查詢效率。在鏈表長度達到7時(bingCount >= TREEIFY_THRESHOLD - 1)般此,并且hash tab[]數(shù)組長度大于等于64時蚪战,將鏈表轉(zhuǎn)換成紅黑樹,如果數(shù)組長度小于64铐懊,只是對數(shù)組進行擴容
HashMap中的key可以是任何對象或數(shù)據(jù)類型嗎
- 可以是null邀桑,但不能是可變對象,如果是可變對象科乎,對象中的屬性改變壁畸,則對象的HashCode也相應(yīng)改變,導(dǎo)致下次無法查找到已存在Map中的數(shù)據(jù)
- 如果要可變對象當著鍵,必須保證其HashCode在成員屬性改變的時候保持不變
HashMap 初始容量 計算方法
如果在new HashMap的時候捏萍,沒有指定初始initialCapacity太抓,則初始initialCapacity為16,負載因子為0.75令杈,下次擴容閾值為 16*0.75=12
這個初始容量 不一定等于初始化完成后底層數(shù)組實際的容量走敌,因為存在閾值的計算,方法如下逗噩;也不是初始容量是多少開始就能存多少個元素掉丽,因為存在負載因子,在底層數(shù)組還沒滿的時候就會進行擴容
閾值計算方法為:
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;
}
該方法計算大于等于輸入?yún)?shù)并最接近參數(shù)的2的整數(shù)次冪的數(shù)异雁,如10捶障,返回16
cap -1 ,-1是為了在計算的時候能得到大于等于輸入?yún)?shù)的值
在HashMap 進行put方法的時候纲刀,如果判斷已有的元素大于 閾值就會觸發(fā)擴容計算项炼,擴容步驟
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 下次擴容的閾值
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ù)組長度大于默認長度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ù)組長度,新閾值都初始化為默認值
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 5.如果新的閾值等于0
if (newThr == 0) {
//計算臨時閾值
float ft = (float)newCap * loadFactor;
//新數(shù)組長度小于最大長度扼菠,臨時閾值也小于最大長度摄杂,新閾值為臨時閾值,否則是Integer.MAX_VALUE
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//計算出來的新閾值賦值給對象的閾值
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//用新計算的數(shù)組長度新建一個Node數(shù)組循榆,并賦值給對象的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)新老變化秧饮,擴容閾值(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è)置負載因子,第一次put的時候會觸發(fā)擴容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
第一次的時候盗尸,數(shù)組長度為默認值16柑船,閾值為16*0.75=12,走的代碼4
邏輯泼各,等到數(shù)組長度超過閾值12后鞍时,觸發(fā)第二次擴容,此時table數(shù)組,和threshold都不為0逆巍,即oldTab及塘、oldCap、oldThr都不為0锐极,先走代碼1
笙僚,如果oldCap長度的2倍沒有超過最大容量,并且oldCap 長度大于等于 默認容量16灵再,那么下次擴容的閾值 變?yōu)閛ldThr大小的兩倍即 12 * 2 = 24肋层,newThr = 24,newCap=32
② 設(shè)置了initialCapacity檬嘀,沒有設(shè)置負載因子槽驶,此時hashMap使用默認負載因子0.75,本實例設(shè)置的初始容量為2鸳兽,通過計算閾值為2掂铐,第一次put的時候由于還沒初始化table數(shù)組,因此觸發(fā)第一次擴容揍异。此時oldCap為0全陨,oldThr為2,走代碼3
衷掷,確定這次擴容的新數(shù)組大小為2辱姨,此時還沒有確定newThr 下次擴容的大小,于是進入代碼5
確定newThr為 2 * 0.75 = 1.5 取整 1 戚嗅,及下次擴容閾值為1雨涛。當數(shù)組已有元素大于閾值及1時,觸發(fā)第二次擴容懦胞,此時oldCap為1替久,oldThr為1,走代碼1
newCap = oldCap << 1 結(jié)果為 4 小于最大容量躏尉, 但oldCap 小于hashMap默認大小16蚯根,結(jié)果為false,跳出判斷胀糜,此時由于newThr等于0颅拦,進入代碼5
,確定newThr為 4 * 0.75 = 3教藻,下次擴容閾值為3
③ 設(shè)置了initialCapacity=2距帅,負載因子為0.5,通過tableSizeFor計算閾值為2怖竭,第一次put的時候锥债,進行擴容,此時oldCap為2,oldThr為2哮肚,進入代碼1
登夫,同實例②,newCap = oldCap << 1 結(jié)果為 4 小于最大容量允趟, 但oldCap 小于hashMap默認大小16恼策,結(jié)果為false,跳出判斷潮剪,進入代碼5
涣楷,確定newThr為 4 * 0.5 = 2,下次擴容閾值為2
面試回答要素
- 回答什么情況下會第一擴容,舉例說明抗碰,新數(shù)組大小狮斗,閾值大小
- 以后什么情況下會再次擴容,這次是怎么計算新數(shù)組大小弧蝇,及閾值大小的
HashMap碳褒、ConcurrentHashMap初始化閾值為什么要是8,才轉(zhuǎn)為紅黑樹看疗?
- 當初始閾值為8時沙峻,鏈表的長度達到8的概率變的很小,如果再大概率減小的并不明顯
- 樹結(jié)構(gòu)查找的時間復(fù)雜度是O(log(n))两芳,而鏈表的時間復(fù)雜度是O(n)摔寨,當閾值為8時,long8 = 3怖辆,相比鏈表更快是复,但樹結(jié)構(gòu)比鏈表占用的空間更多,所以這是一種時間和空間的平衡
手寫HashMap
要點:
- 底層是數(shù)組結(jié)構(gòu)
- hash沖突的時候竖螃,轉(zhuǎn)換為鏈表
- 考慮擴容處理
HashMap在并發(fā)下會產(chǎn)生什么問題佑笋?有什么替代方案?(HashTable, ConcurrentHashMap)。它們兩者的實現(xiàn)原理斑鼻。
HashMap并發(fā)下產(chǎn)生問題:由于在發(fā)生hash沖突,插入鏈表的時候猎荠,多線程會造成環(huán)鏈坚弱,再get的時候變成死循環(huán),Map.size()不準確关摇,數(shù)據(jù)丟失
https://www.iteye.com/blog/hwl-sz-1897468HashTable: 通過synchronized來修飾荒叶,效率低,多線程put的時候输虱,只能有一個線程成功些楣,其他線程都處于阻塞狀態(tài)
-
ConcurrentHashMap:
1.7 采用鎖分段技術(shù)提高并發(fā)訪問率
1.8 數(shù)據(jù)依舊是分段存儲,但鎖采用了synchronized,內(nèi)部采用Node數(shù)組+鏈表+紅黑樹的結(jié)構(gòu)存儲愁茁,當單個鏈表存儲數(shù)量達到紅黑樹閾值8時(此時鏈表已有元素7)蚕钦,并且數(shù)組長度大于64時,存儲結(jié)構(gòu)轉(zhuǎn)換為紅黑樹來存儲鹅很,否則只進行數(shù)組的擴容
HashMap 為什么不用平衡樹嘶居,而用紅黑樹
紅黑樹也是一種平衡樹,但不是嚴格平衡促煮,平衡樹是左右子樹高度差不超過1邮屁,紅黑樹可以是2倍
紅黑樹在插入、刪除的時候旋轉(zhuǎn)的概率比平衡樹低很多菠齿,效率比平衡樹高
查找時間復(fù)雜度都維持在O(logN)
關(guān)于HashMap的其他文章:https://blog.csdn.net/login_sonata/article/details/76598675
常見異常分為哪兩種(Exception,Error)佑吝,區(qū)別是什么,了解受檢異常和非受檢異常嗎
Exception,Error有共同的父類Throwable
Error: 表示程序發(fā)生錯誤绳匀,是程序無法處理的芋忿,不可恢復(fù)的,如OutOfMemoryError
Exception: 表示程序可處理的異常襟士,又分為CheckedException(受檢異常)盗飒、UncheckedException(非受檢異常),受檢異常發(fā)生在編譯期陋桂,必須要使用try...catch 或者 throws捕獲或者拋出異常逆趣,否則編譯不通過;非受檢異常發(fā)生在運行期嗜历,具有不確定性宣渗,主要由程序的邏輯問題引起的,在程序設(shè)計的時候要認真考慮梨州,盡量處理異常
實現(xiàn)一個LRU
可以直接使用LinkedHashMap實現(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;
}
}
用過流沒有痕囱,流怎么實現(xiàn)
Stream流是Java8中引入的新特性,Stream有幾個特點:
不存數(shù)據(jù)暴匠,都是通過管道將源數(shù)據(jù)元素傳遞給操作鞍恢;
對Stream的任何修改都不會修改數(shù)據(jù)源,都是新產(chǎn)生一個流
流的很多操作如filter每窖、map都是延遲執(zhí)行的帮掉,只有到終點才會將操作順序執(zhí)行
對于無限流可以通過“短路”操作訪問到有限元素后就返回
流的元素只訪問一次,如果需要重新訪問窒典,需要重新生成一個新的流
Stream中BaseStream規(guī)定了流的基本接口蟆炊,在Stream中使用Stage來描述一個完整的操作,將具有先后順序的各個Stage連一起瀑志,就構(gòu)成了整個流水線涩搓。
AbstractPipeline是流水線的核心污秆,定義了三個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對象以雙向鏈表的形式組織在一起掖蛤,構(gòu)成整個流水線。
由于每個Stage都記錄了前一個Stage和本次的操作以及回調(diào)函數(shù)井厌,依靠這種結(jié)構(gòu)就建立起對數(shù)據(jù)源的所有操作蚓庭。