日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

Java集合必会14问(精选面试题整理)

發(fā)布時(shí)間:2025/3/21 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java集合必会14问(精选面试题整理) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?

前言:把這段時(shí)間復(fù)習(xí)的關(guān)于集合類的東西整理出來,特別是HashMap相關(guān)的一些東西,之前都沒有很注意1.7 ->> 1.8的變化問題,但后來發(fā)現(xiàn)這其實(shí)變化挺大的,而且很多整理的面試資料都沒有更新(包括我之前整理的...)

1)說說常見的集合有哪些吧?

答:Map接口和Collection接口是所有集合框架的父接口:

  • Collection接口的子接口包括:Set接口和List接口
  • Map接口的實(shí)現(xiàn)類主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
  • Set接口的實(shí)現(xiàn)類主要有:HashSet、TreeSet、LinkedHashSet等
  • List接口的實(shí)現(xiàn)類主要有:ArrayList、LinkedList、Stack以及Vector等

  • 2)HashMap與HashTable的區(qū)別?

    答:

  • HashMap沒有考慮同步,是線程不安全的;Hashtable使用了synchronized關(guān)鍵字,是線程安全的;

  • HashMap允許K/V都為null;后者K/V都不允許為null;

  • HashMap繼承自AbstractMap類;而Hashtable繼承自Dictionary類;


  • 3)HashMap的put方法的具體流程?

    圖引用自:https://blog.csdn.net/u011240877/article/details/53358305

    答:下面先來分析一下源碼

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;// 1.如果table為空或者長(zhǎng)度為0,即沒有元素,那么使用resize()方法擴(kuò)容if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// 2.計(jì)算插入存儲(chǔ)的數(shù)組索引i,此處計(jì)算方法同 1.7 中的indexFor()方法// 如果數(shù)組為空,即不存在Hash沖突,則直接插入數(shù)組if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);// 3.插入時(shí),如果發(fā)生Hash沖突,則依次往下判斷else {HashMap.Node<K,V> e; K k;// a.判斷table[i]的元素的key是否與需要插入的key一樣,若相同則直接用新的value覆蓋掉舊的value// 判斷原則equals() - 所以需要當(dāng)key的對(duì)象重寫該方法if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// b.繼續(xù)判斷:需要插入的數(shù)據(jù)結(jié)構(gòu)是紅黑樹還是鏈表// 如果是紅黑樹,則直接在樹中插入 or 更新鍵值對(duì)else if (p instanceof HashMap.TreeNode)e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);// 如果是鏈表,則在鏈表中插入 or 更新鍵值對(duì)else {// i .遍歷table[i],判斷key是否已存在:采用equals對(duì)比當(dāng)前遍歷結(jié)點(diǎn)的key與需要插入數(shù)據(jù)的key// 如果存在相同的,則直接覆蓋// ii.遍歷完畢后任務(wù)發(fā)現(xiàn)上述情況,則直接在鏈表尾部插入數(shù)據(jù)// 插入完成后判斷鏈表長(zhǎng)度是否 > 8:若是,則把鏈表轉(zhuǎn)換成紅黑樹for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 對(duì)于i 情況的后續(xù)操作:發(fā)現(xiàn)key已存在,直接用新value覆蓋舊value&返回舊valueif (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;// 插入成功后,判斷實(shí)際存在的鍵值對(duì)數(shù)量size > 最大容量// 如果大于則進(jìn)行擴(kuò)容if (++size > threshold)resize();// 插入成功時(shí)會(huì)調(diào)用的方法(默認(rèn)實(shí)現(xiàn)為空)afterNodeInsertion(evict);return null; }

    圖片簡(jiǎn)單總結(jié)為:

    ?


    4)HashMap的擴(kuò)容操作是怎么實(shí)現(xiàn)的?

    答:通過分析源碼我們知道了HashMap通過resize()方法進(jìn)行擴(kuò)容或者初始化的操作,下面是對(duì)源碼進(jìn)行的一些簡(jiǎn)單分析:

    /*** 該函數(shù)有2中使用情況:1.初始化哈希表;2.當(dāng)前數(shù)組容量過小,需要擴(kuò)容*/ final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;// 擴(kuò)容前的數(shù)組(當(dāng)前數(shù)組)int oldCap = (oldTab == null) ? 0 : oldTab.length;// 擴(kuò)容前的數(shù)組容量(數(shù)組長(zhǎng)度)int oldThr = threshold;// 擴(kuò)容前數(shù)組的閾值int newCap, newThr = 0;if (oldCap > 0) {// 針對(duì)情況2:若擴(kuò)容前的數(shù)組容量超過最大值,則不再擴(kuò)容if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}// 針對(duì)情況2:若沒有超過最大值,就擴(kuò)容為原來的2倍(左移1位)else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}// 針對(duì)情況1:初始化哈希表(采用指定或者使用默認(rèn)值的方式)else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else { // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 計(jì)算新的resize上限if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold = newThr;@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;if (oldTab != null) {// 把每一個(gè)bucket都移動(dòng)到新的bucket中去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 orderNode<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;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.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; }

    5)HashMap是怎么解決哈希沖突的?

    參考資料:https://juejin.im/post/5ab99afff265da23a2291dee

    答:在解決這個(gè)問題之前,我們首先需要知道什么是哈希沖突,而在了解哈希沖突之前我們還要知道什么是哈希才行;

    什么是哈希?

    Hash,一般翻譯為“散列”,也有直接音譯為“哈希”的,這就是把任意長(zhǎng)度的輸入通過散列算法,變換成固定長(zhǎng)度的輸出,該輸出就是散列值(哈希值);這種轉(zhuǎn)換是一種壓縮映射,也就是,散列值的空間通常遠(yuǎn)小于輸入的空間,不同的輸入可能會(huì)散列成相同的輸出,所以不可能從散列值來唯一的確定輸入值。簡(jiǎn)單的說就是一種將任意長(zhǎng)度的消息壓縮到某一固定長(zhǎng)度的消息摘要的函數(shù)。

    所有散列函數(shù)都有如下一個(gè)基本特性:根據(jù)同一散列函數(shù)計(jì)算出的散列值如果不同,那么輸入值肯定也不同。但是,根據(jù)同一散列函數(shù)計(jì)算出的散列值如果相同,輸入值不一定相同。

    什么是哈希沖突?

    當(dāng)兩個(gè)不同的輸入值,根據(jù)同一散列函數(shù)計(jì)算出相同的散列值的現(xiàn)象,我們就把它叫做碰撞(哈希碰撞)。

    HashMap的數(shù)據(jù)結(jié)構(gòu)

    在Java中,保存數(shù)據(jù)有兩種比較簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu):數(shù)組和鏈表。數(shù)組的特點(diǎn)是:尋址容易,插入和刪除困難;鏈表的特點(diǎn)是:尋址困難,但插入和刪除容易;所以我們將數(shù)組和鏈表結(jié)合在一起,發(fā)揮兩者各自的優(yōu)勢(shì),使用一種叫做鏈地址法的方式可以解決哈希沖突:

    ?

    這樣我們就可以將擁有相同哈希值的對(duì)象組織成一個(gè)鏈表放在hash值所對(duì)應(yīng)的bucket下,但相比于hashCode返回的int類型,我們HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要遠(yuǎn)小于int類型的范圍,所以我們?nèi)绻皇菃渭兊挠胔ashCode取余來獲取對(duì)應(yīng)的bucket這將會(huì)大大增加哈希碰撞的概率,并且最壞情況下還會(huì)將HashMap變成一個(gè)單鏈表,所以我們還需要對(duì)hashCode作一定的優(yōu)化

    hash()函數(shù)

    上面提到的問題,主要是因?yàn)槿绻褂胔ashCode取余,那么相當(dāng)于參與運(yùn)算的只有hashCode的低位,高位是沒有起到任何作用的,所以我們的思路就是讓hashCode取值出的高位也參與運(yùn)算,進(jìn)一步降低hash碰撞的概率,使得數(shù)據(jù)分布更平均,我們把這樣的操作稱為擾動(dòng),在JDK 1.8中的hash()函數(shù)如下:

    static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 與自己右移16位進(jìn)行異或運(yùn)算(高低位異或) }

    這比在JDK 1.7中,更為簡(jiǎn)潔,相比在1.7中的4次位運(yùn)算,5次異或運(yùn)算(9次擾動(dòng)),在1.8中,只進(jìn)行了1次位運(yùn)算和1次異或運(yùn)算(2次擾動(dòng));

    JDK1.8新增紅黑樹

    ?

    通過上面的鏈地址法(使用散列表)擾動(dòng)函數(shù)我們成功讓我們的數(shù)據(jù)分布更平均,哈希碰撞減少,但是當(dāng)我們的HashMap中存在大量數(shù)據(jù)時(shí),加入我們某個(gè)bucket下對(duì)應(yīng)的鏈表有n個(gè)元素,那么遍歷時(shí)間復(fù)雜度就為O(n),為了針對(duì)這個(gè)問題,JDK1.8在HashMap中新增了紅黑樹的數(shù)據(jù)結(jié)構(gòu),進(jìn)一步使得遍歷復(fù)雜度降低至O(logn);

    總結(jié)

    簡(jiǎn)單總結(jié)一下HashMap是使用了哪些方法來有效解決哈希沖突的:

    1. 使用鏈地址法(使用散列表)來鏈接擁有相同hash值的數(shù)據(jù);
    2. 使用2次擾動(dòng)函數(shù)(hash函數(shù))來降低哈希沖突的概率,使得數(shù)據(jù)分布更平均;
    3. 引入紅黑樹進(jìn)一步降低遍歷的時(shí)間復(fù)雜度,使得遍歷更快;


    6)HashMap為什么不直接使用hashCode()處理后的哈希值直接作為table的下標(biāo)?

    答:hashCode()方法返回的是int整數(shù)類型,其范圍為-(2 ^ 31)~(2 ^ 31 - 1),約有40億個(gè)映射空間,而HashMap的容量范圍是在16(初始化默認(rèn)值)~2 ^ 30,HashMap通常情況下是取不到最大值的,并且設(shè)備上也難以提供這么多的存儲(chǔ)空間,從而導(dǎo)致通過hashCode()計(jì)算出的哈希值可能不在數(shù)組大小范圍內(nèi),進(jìn)而無法匹配存儲(chǔ)位置;

    面試官:那怎么解決呢?

    答:

  • HashMap自己實(shí)現(xiàn)了自己的hash()方法,通過兩次擾動(dòng)使得它自己的哈希值高低位自行進(jìn)行異或運(yùn)算,降低哈希碰撞概率也使得數(shù)據(jù)分布更平均;

  • 在保證數(shù)組長(zhǎng)度為2的冪次方的時(shí)候,使用hash()運(yùn)算之后的值與運(yùn)算(&)(數(shù)組長(zhǎng)度 - 1)來獲取數(shù)組下標(biāo)的方式進(jìn)行存儲(chǔ),這樣一來是比取余操作更加有效率,二來也是因?yàn)橹挥挟?dāng)數(shù)組長(zhǎng)度為2的冪次方時(shí),h&(length-1)才等價(jià)于h%length,三來解決了“哈希值與數(shù)組大小范圍不匹配”的問題;

  • 面試官:為什么數(shù)組長(zhǎng)度要保證為2的冪次方呢?

    答:

  • 只有當(dāng)數(shù)組長(zhǎng)度為2的冪次方時(shí),h&(length-1)才等價(jià)于h%length,即實(shí)現(xiàn)了key的定位,2的冪次方也可以減少?zèng)_突次數(shù),提高HashMap的查詢效率;

  • 如果 length 為 2 的次冪 則 length-1 轉(zhuǎn)化為二進(jìn)制必定是 11111……的形式,在于 h 的二進(jìn)制與操作效率會(huì)非常的快,而且空間不浪費(fèi);如果 length 不是 2 的次冪,比如 length 為 15,則 length - 1 為 14,對(duì)應(yīng)的二進(jìn)制為 1110,在于 h 與操作,最后一位都為 0 ,而 0001,0011,0101,1001,1011,0111,1101 這幾個(gè)位置永遠(yuǎn)都不能存放元素了,空間浪費(fèi)相當(dāng)大,更糟的是這種情況中,數(shù)組可以使用的位置比數(shù)組長(zhǎng)度小了很多,這意味著進(jìn)一步增加了碰撞的幾率,減慢了查詢的效率!這樣就會(huì)造成空間的浪費(fèi)。

  • 面試官:那為什么是兩次擾動(dòng)呢?

    答:這樣就是加大哈希值低位的隨機(jī)性,使得分布更均勻,從而提高對(duì)應(yīng)數(shù)組存儲(chǔ)下標(biāo)位置的隨機(jī)性&均勻性,最終減少Hash沖突,兩次就夠了,已經(jīng)達(dá)到了高位低位同時(shí)參與運(yùn)算的目的;


    7)HashMap在JDK1.7和JDK1.8中有哪些不同?

    答:

    不同JDK 1.7JDK 1.8
    存儲(chǔ)結(jié)構(gòu)數(shù)組 + 鏈表數(shù)組 + 鏈表 + 紅黑樹
    初始化方式單獨(dú)函數(shù):inflateTable()直接集成到了擴(kuò)容函數(shù)resize()中
    hash值計(jì)算方式擾動(dòng)處理 = 9次擾動(dòng) = 4次位運(yùn)算 + 5次異或運(yùn)算擾動(dòng)處理 = 2次擾動(dòng) = 1次位運(yùn)算 + 1次異或運(yùn)算
    存放數(shù)據(jù)的規(guī)則無沖突時(shí),存放數(shù)組;沖突時(shí),存放鏈表無沖突時(shí),存放數(shù)組;沖突 & 鏈表長(zhǎng)度 < 8:存放單鏈表;沖突 & 鏈表長(zhǎng)度 > 8:樹化并存放紅黑樹
    插入數(shù)據(jù)方式頭插法(先講原位置的數(shù)據(jù)移到后1位,再插入數(shù)據(jù)到該位置)尾插法(直接插入到鏈表尾部/紅黑樹)
    擴(kuò)容后存儲(chǔ)位置的計(jì)算方式全部按照原來方法進(jìn)行計(jì)算(即hashCode ->> 擾動(dòng)函數(shù) ->> (h&length-1))按照擴(kuò)容后的規(guī)律計(jì)算(即擴(kuò)容后的位置=原位置 or 原位置 + 舊容量)

    8)為什么HashMap中String、Integer這樣的包裝類適合作為K?

    答:String、Integer等包裝類的特性能夠保證Hash值的不可更改性和計(jì)算準(zhǔn)確性,能夠有效的減少Hash碰撞的幾率

  • 都是final類型,即不可變性,保證key的不可更改性,不會(huì)存在獲取hash值不同的情況
  • 內(nèi)部已重寫了equals()、hashCode()等方法,遵守了HashMap內(nèi)部的規(guī)范(不清楚可以去上面看看putValue的過程),不容易出現(xiàn)Hash值計(jì)算錯(cuò)誤的情況;
  • 面試官:如果我想要讓自己的Object作為K應(yīng)該怎么辦呢?

    答:重寫hashCode()和equals()方法

  • 重寫hashCode()是因?yàn)樾枰?jì)算存儲(chǔ)數(shù)據(jù)的存儲(chǔ)位置,需要注意不要試圖從散列碼計(jì)算中排除掉一個(gè)對(duì)象的關(guān)鍵部分來提高性能,這樣雖然能更快但可能會(huì)導(dǎo)致更多的Hash碰撞;

  • 重寫equals()方法,需要遵守自反性、對(duì)稱性、傳遞性、一致性以及對(duì)于任何非null的引用值x,x.equals(null)必須返回false的這幾個(gè)特性,目的是為了保證key在哈希表中的唯一性


  • 9)ConcurrentHashMap和Hashtable的區(qū)別?

    答:ConcurrentHashMap 結(jié)合了 HashMap 和 HashTable 二者的優(yōu)勢(shì)。HashMap 沒有考慮同步,HashTable 考慮了同步的問題。但是 HashTable 在每次同步執(zhí)行時(shí)都要鎖住整個(gè)結(jié)構(gòu)。 ConcurrentHashMap 鎖的方式是稍微細(xì)粒度的。

    面試官:ConcurrentHashMap的具體實(shí)現(xiàn)知道嗎?

    參考資料:http://www.importnew.com/23610.html

    答:在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式進(jìn)行實(shí)現(xiàn),結(jié)構(gòu)如下:

    ?

  • 該類包含兩個(gè)靜態(tài)內(nèi)部類 HashEntry 和 Segment ;前者用來封裝映射表的鍵值對(duì),后者用來充當(dāng)鎖的角色;

  • Segment 是一種可重入的鎖 ReentrantLock,每個(gè) Segment 守護(hù)一個(gè)HashEntry 數(shù)組里得元素,當(dāng)對(duì) HashEntry 數(shù)組的數(shù)據(jù)進(jìn)行修改時(shí),必須首先獲得對(duì)應(yīng)的 Segment 鎖。

  • JDK1.8中,放棄了Segment臃腫的設(shè)計(jì),取而代之的是采用Node + CAS + Synchronized來保證并發(fā)安全進(jìn)行實(shí)現(xiàn),結(jié)構(gòu)如下:

    ?

    插入元素過程(建議去看看源碼):

  • 如果相應(yīng)位置的Node還沒有初始化,則調(diào)用CAS插入相應(yīng)的數(shù)據(jù);
  • else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))break; // no lock when adding to empty bin }
  • 如果相應(yīng)位置的Node不為空,且當(dāng)前該節(jié)點(diǎn)不處于移動(dòng)狀態(tài),則對(duì)該節(jié)點(diǎn)加synchronized鎖,如果該節(jié)點(diǎn)的hash不小于0,則遍歷鏈表更新節(jié)點(diǎn)或插入新節(jié)點(diǎn);
  • if (fh >= 0) {binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key, value, null);break;}} }
  • 如果該節(jié)點(diǎn)是TreeBin類型的節(jié)點(diǎn),說明是紅黑樹結(jié)構(gòu),則通過putTreeVal方法往紅黑樹中插入節(jié)點(diǎn);如果binCount不為0,說明put操作對(duì)數(shù)據(jù)產(chǎn)生了影響,如果當(dāng)前鏈表的個(gè)數(shù)達(dá)到8個(gè),則通過treeifyBin方法轉(zhuǎn)化為紅黑樹,如果oldVal不為空,說明是一次更新操作,沒有對(duì)元素個(gè)數(shù)產(chǎn)生影響,則直接返回舊值;

  • 如果插入的是一個(gè)新節(jié)點(diǎn),則執(zhí)行addCount()方法嘗試更新元素個(gè)數(shù)baseCount;


  • 10)Java集合的快速失敗機(jī)制 “fail-fast”?

    答:

    是java集合的一種錯(cuò)誤檢測(cè)機(jī)制,當(dāng)多個(gè)線程對(duì)集合進(jìn)行結(jié)構(gòu)上的改變的操作時(shí),有可能會(huì)產(chǎn)生 fail-fast 機(jī)制。

    例如:假設(shè)存在兩個(gè)線程(線程1、線程2),線程1通過Iterator在遍歷集合A中的元素,在某個(gè)時(shí)候線程2修改了集合A的結(jié)構(gòu)(是結(jié)構(gòu)上面的修改,而不是簡(jiǎn)單的修改集合元素的內(nèi)容),那么這個(gè)時(shí)候程序就會(huì)拋出 ConcurrentModificationException 異常,從而產(chǎn)生fail-fast機(jī)制。

    原因:迭代器在遍歷時(shí)直接訪問集合中的內(nèi)容,并且在遍歷過程中使用一個(gè) modCount 變量。集合在被遍歷期間如果內(nèi)容發(fā)生變化,就會(huì)改變modCount的值。每當(dāng)?shù)魇褂胔ashNext()/next()遍歷下一個(gè)元素之前,都會(huì)檢測(cè)modCount變量是否為expectedmodCount值,是的話就返回遍歷;否則拋出異常,終止遍歷。

    解決辦法:

    1. 在遍歷過程中,所有涉及到改變modCount值得地方全部加上synchronized。

    2. 使用CopyOnWriteArrayList來替換ArrayList


    11)ArrayList 和 Vector 的區(qū)別?

    答:

    這兩個(gè)類都實(shí)現(xiàn)了 List 接口(List 接口繼承了 Collection 接口),他們都是有序集合,即存儲(chǔ)在這兩個(gè)集合中的元素位置都是有順序的,相當(dāng)于一種動(dòng)態(tài)的數(shù)組,我們以后可以按位置索引來取出某個(gè)元素,并且其中的數(shù)據(jù)是允許重復(fù)的,這是與 HashSet 之類的集合的最大不同處,HashSet 之類的集合不可以按索引號(hào)去檢索其中的元素,也不允許有重復(fù)的元素。

    ArrayList 與 Vector 的區(qū)別主要包括兩個(gè)方面:

  • 同步性:
    Vector 是線程安全的,也就是說它的方法之間是線程同步(加了synchronized 關(guān)鍵字)的,而 ArrayList 是線程不安全的,它的方法之間是線程不同步的。如果只有一個(gè)線程會(huì)訪問到集合,那最好是使用 ArrayList,因?yàn)樗豢紤]線程安全的問題,所以效率會(huì)高一些;如果有多個(gè)線程會(huì)訪問到集合,那最好是使用 Vector,因?yàn)椴恍枰覀冏约涸偃タ紤]和編寫線程安全的代碼。

  • 數(shù)據(jù)增長(zhǎng):
    ArrayList 與 Vector 都有一個(gè)初始的容量大小,當(dāng)存儲(chǔ)進(jìn)它們里面的元素的個(gè)人超過了容量時(shí),就需要增加 ArrayList 和 Vector 的存儲(chǔ)空間,每次要增加存儲(chǔ)空間時(shí),不是只增加一個(gè)存儲(chǔ)單元,而是增加多個(gè)存儲(chǔ)單元,每次增加的存儲(chǔ)單元的個(gè)數(shù)在內(nèi)存空間利用與程序效率之間要去的一定的平衡。Vector 在數(shù)據(jù)滿時(shí)(加載因子1)增長(zhǎng)為原來的兩倍(擴(kuò)容增量:原容量的 2 倍),而 ArrayList 在數(shù)據(jù)量達(dá)到容量的一半時(shí)(加載因子 0.5)增長(zhǎng)為原容量的 (0.5 倍 + 1) 個(gè)空間。


  • 12)ArrayList和LinkedList的區(qū)別?

    答:

  • LinkedList 實(shí)現(xiàn)了 List 和 Deque 接口,一般稱為雙向鏈表;ArrayList 實(shí)現(xiàn)了 List 接口,動(dòng)態(tài)數(shù)組;
  • LinkedList 在插入和刪除數(shù)據(jù)時(shí)效率更高,ArrayList 在查找某個(gè) index 的數(shù)據(jù)時(shí)效率更高;
  • LinkedList 比 ArrayList 需要更多的內(nèi)存;
  • 面試官:Array 和 ArrayList 有什么區(qū)別?什么時(shí)候該應(yīng) Array 而不是 ArrayList 呢?

    答:它們的區(qū)別是:

  • Array 可以包含基本類型和對(duì)象類型,ArrayList 只能包含對(duì)象類型。
  • Array 大小是固定的,ArrayList 的大小是動(dòng)態(tài)變化的。
  • ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。
  • 對(duì)于基本類型數(shù)據(jù),集合使用自動(dòng)裝箱來減少編碼工作量。但是,當(dāng)處理固定大小的基本數(shù)據(jù)類型的時(shí)候,這種方式相對(duì)比較慢。


    13)HashSet是如何保證數(shù)據(jù)不可重復(fù)的?

    答:HashSet的底層其實(shí)就是HashMap,只不過我們HashSet是實(shí)現(xiàn)了Set接口并且把數(shù)據(jù)作為K值,而V值一直使用一個(gè)相同的虛值來保存,我們可以看到源碼:

    public boolean add(E e) {return map.put(e, PRESENT)==null;// 調(diào)用HashMap的put方法,PRESENT是一個(gè)至始至終都相同的虛值 }

    由于HashMap的K值本身就不允許重復(fù),并且在HashMap中如果K/V相同時(shí),會(huì)用新的V覆蓋掉舊的V,然后返回舊的V,那么在HashSet中執(zhí)行這一句話始終會(huì)返回一個(gè)false,導(dǎo)致插入失敗,這樣就保證了數(shù)據(jù)的不可重復(fù)性;


    14)BlockingQueue是什么?

    答:

    Java.util.concurrent.BlockingQueue是一個(gè)隊(duì)列,在進(jìn)行檢索或移除一個(gè)元素的時(shí)候,它會(huì)等待隊(duì)列變?yōu)榉强?#xff1b;當(dāng)在添加一個(gè)元素時(shí),它會(huì)等待隊(duì)列中的可用空間。BlockingQueue接口是Java集合框架的一部分,主要用于實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式。我們不需要擔(dān)心等待生產(chǎn)者有可用的空間,或消費(fèi)者有可用的對(duì)象,因?yàn)樗荚贐lockingQueue的實(shí)現(xiàn)類中被處理了。Java提供了集中BlockingQueue的實(shí)現(xiàn),比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。


    歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處!
    簡(jiǎn)書ID:@我沒有三顆心臟
    github:wmyskxz

    《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

    總結(jié)

    以上是生活随笔為你收集整理的Java集合必会14问(精选面试题整理)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。