打卡知識圈

  • 多線程

    1、什麼是線程調度器(Thread Scheduler)和時間分片(Time Slicing) ?

           答:線程調度器是一個操作系統服務 ,它負責爲Runnable狀態的線程分配CPU時間。一旦我們創建一個線程並啓動它 ,它的執行便依賴於線程調度器的實現。時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間可以基於線程優先級或者線程等待的時間。線程調度並不受到Java虛擬機控制,所以由應用程序來控制它是更好的選擇(也就是說不要讓你的程序依賴於線程的優先級)。

     

    2、爲什麼Thread類的sleep()和yield()方法是靜態的 ?

       答:Thread類的sleep()和yield()方法將在當前正在執行的線程上運行。所以在其他處於等待狀態的線程上調用這些方法是沒有意義的。這就是爲什麼這些方法是靜態的。它們可以在當前正在執行的線程中工作,並避免程序員錯誤的認爲可以在其他非運行線程調用這些方法。

     

    3、volatile關鍵字在Java中有什麼作用 ?

       答:當我們使用volatile關鍵字去修飾變量的時候,所有線程都會直接讀取該變量並且不緩存它。這就確保了線程讀取到的變量是同內存中是一致的。

    volatile是防止指令重排序來保證可見性對於JVM層面是防止編譯器的重排序同時,對於有些cpu來說,他們會通過緩存鎖或者中線索來解決緩存可見性但是,目前很多cpu都做了優化,因爲緩存一致性MESI會帶來性能開銷,所以用到了storebuffer機制來做異步處理,而這種機制帶來了指令的亂序執行。從而導致可見性問題。那麼volatile會在cpu層面會增加內存屏障,來解決cpu的亂序執行帶來的可見性問題

    4、什麼是ThreadLocal?

      答: ThreadLocal用於創建線程的本地變量,我們知道一個對象的所有線程會共享它的全局變量,所以這些變量不是線程安全的,我們可以使用同步技術。但是當我們不想使用同步的時候,我們可以選擇ThreadLocal變量,每個線程都會擁有他們自己的Thread變量,它們可以使用get)\set()方法去獲取他們的默認值或者在線程內部改變他們的值。ThreadLocal實例通常是希望它們同線程狀態關聯起來是private static屬性。

    5、什麼是阻塞隊列?如何使用阻塞隊列來實現生產者消費者模型?

      答: java.util.concurrent.BlockingQueue的特性是:當隊列是空的時,從隊列中獲取或刪除元素的操作將會被阻塞, 或者當隊列是滿時,往隊列裏添加元素的操作會被阻塞。阻塞隊列不接受空值,當你嘗試向隊列中添加空值的時候 ,它會拋出NullPointerException。阻塞隊列的實現都是線程安全的,所有的查詢方法都是原子的並且使用了內部鎖或者其他形式的併發控制。BlockingQueue接口是java collections框架的一部分,它主要用於實現生產者消費者問題。

    6、什麼是Callable和Future?

    答: Java 5在concurrency包中引入了java.util.concurrent.callable接口,它和Runnable接口很相似,但它可以返回一個對象或者拋出一個異常。

    Callable接口使用泛型去定義它的返回類型。Executors類提供了一些有用的方法去在線程池中執行Callable內的任務。由於Callable任務是並行的,我們必須等待它返回的結果。java.util.concurrent.Future對象爲我們解決了這個問題。在線程池提交Callable任務後返回了一個Future對象,使用它我們可以知道Callable任務的狀態和得到Callable返回的執行結果。Future提供了get()方法讓我們可以等待Callable結束並獲取它的執行結果。

    Dubbo

    1、Dubbo 的使用場景有哪些?

       答:透明化的遠程方法調用:就像調用本地方法一樣調用遠程方法,只需簡單配置,沒有任何API侵入。

    軟負載均衡及容錯機制:可在內網替代F5等硬件負載均衡器,降低成本,減少單點。

    服務自動註冊與發現:不再需要寫死服務提供方地址,註冊中心基於接口名查詢服務提供者的IP地址,並且能夠平滑添加或刪除服務提供者。

    2、Dubbo 核心功能有哪些?

      答:Remoting:網絡通信框架,提供對多種NIO框架抽象封裝,包括“同步轉異步”和“請求-響應”模式的信息交換方式。

    Cluster:服務框架,提供基於接口方法的透明遠程過程調用,包括多協議支持,以及軟負載均衡,失敗容錯,地址路由,動態配置等集羣支持。

    Registry:服務註冊,基於註冊中心目錄服務,使服務消費方能動態的查找服務提供方,使地址透明,使服務提供方可以平滑增加或減少機器。

    3、Dubbo 核心組件有哪些?

      答:Provider:暴露服務的服務提供方

    Consumer:調用遠程服務消費方

    Registry:服務註冊與發現註冊中心

    Monitor:監控中心和訪問調用統計

    Container:服務運行容器

     

    4、Dubbo 服務器註冊與發現的流程?

       答:Provider(提供者)綁定指定端口並啓動服務。

    提供者連接註冊中心,並把本機 IP、端口、應用信息和提供服務信息發送至註冊中心存儲。

    Consumer(消費者),連接註冊中心 ,併發送應用信息、所求服務信息至註冊中心。

    註冊中心根據消費者所求服務信息匹配對應的提供者列表發送至 Consumer 應用緩存。

    Consumer 在發起遠程調用時基於緩存的消費者列表擇其一發起調用。

    Provider 狀態變更會實時通知註冊中心、再由註冊中心實時推送至 Consumer。

     

    5、Dubbo的集羣容錯方案有哪些?

      答:Failover Cluster:失敗自動切換,當出現失敗,重試其它服務器。通常用於讀操作,但重試會帶來更長延遲。

    Failfast Cluster:快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。

    Failsafe Cluster:失敗安全,出現異常時,直接忽略。通常用於寫入審計日誌等操作。

    Failback Cluster:失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於消息通知操作。

    Forking Cluster:並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks=”2″ 來設置最大並行數。

    Broadcast Cluster:廣播調用所有提供者,逐個調用,任意一臺報錯則報錯 。通常用於通知所有提供者更新緩存或日誌等本地資源信息。

    默認的容錯方案是 Failover Cluster。

     

    6、Dubbo集羣提供了哪些負載均衡策略?

       答:Random LoadBalance:隨機選取提供者策略,有利於動態調整提供者權重。截面碰撞率高,調用次數越多,分佈越均勻。

    RoundRobin LoadBalance:輪循選取提供者策略,平均分佈,但是存在請求累積的問題。

    LeastActive LoadBalance:最少活躍調用策略,解決慢提供者接收更少的請求。

    ConstantHash LoadBalance:一致性 Hash 策略,使相同參數請求總是發到同一提供者,一臺機器宕機,可以基於虛擬節點,分攤至其他提供者,避免引起提供者的劇烈變動。

    默認爲 Random 隨機調用。

     

     

    redis

    1、redis和memcache有什麼區別?

      答:redis是現在的企業使用最廣泛緩存技術,而在redis以前memcache是一些公司最常   用的緩存技術,它們比較相似,但有如下一些區別:

    (1)redis相對於memcache來說擁有更豐富的數據類型,可以適用更多複雜場景。

    (2)redis原生就是支持cluster集羣模式的,但memcache沒有原生的集羣模式,需要依靠客戶端來實現往集羣中分片寫入數據。

    (3)redis使用的是單核,memcache使用的是多核,所以redis在存儲小數據的時候性能比較高,memcache在存儲大一點的數據時候性能更好。

    (4)memcache在使用簡單的key-value存儲的時候內存利用率更高,但redis如果採用hash的結構來做存儲,內存使用率會較好。

    2、redis的單線程模型

    答:redis基於reactor模式開發了網絡事件處理器,這個處理器叫做文件事件處理器,它的組成結構爲4部分:多個套接字(socket)、IO多路複用程序、文件事件分派器、事件處理器。因爲這個文件處理器是單線程的,所以redis才叫單線程模型。

    在redis啓動初始化的時候,redis會將連接應答處理器跟AE_READABLE事件關聯起來,接着如果一個客戶端跟redis發起連接,此時會產生一個AE_READABLE事件,然後由連接應答處理器來處理跟客戶端建立連接,創建客戶端對應的套接字(socket),同時將這個socket的AE_READABLE事件跟命令請求處理器關聯起來。

    當客戶端向redis發起請求的時候,首先就會在socket產生一個AE_READABLE事件,然後由對應的命令請求處理器來處理。這個命令請求處理器就會從socket中讀取請求相關數據,然後進行執行和處理。

    接着redis這邊準備好了給客戶端的響應數據之後,就會將socket的AE_WRITABLE事件跟命令回覆處理器關聯起來,當客戶端這邊準備好讀取響應數據時,就會在socket上產生一個AE_WRITABLE事件,會由對應的命令回覆處理器來處理,就是將準備好的響應數據寫入socket,供客戶端來讀取。命令回覆處理器寫完之後,就會刪除這個socket的AE_WRITABLE事件和命令回覆處理器的關聯關係。

    3、爲什麼redis單線程模型的效率很高

       答:首先redis裏的數據是存儲在內存中,對redis裏的數據操作的時候實際上是純內存操作;其次,他的文件事件處理器的核心機制是非阻塞的IO多路複用程序;最重要的是單線程反而避免了多線程頻繁上下文切換帶來的損耗。

    4、redis的過期策略和內存淘汰機制

       答:在我們平常使用redis做緩存的時候,我們經常會給這個緩存設置一個過期時間,那麼大家都知道如果我們在查過了過期時間的key時是不會有數據的,那麼所有過期key數據已經被刪除了嗎?是如何刪除的?

    其實如果一個key過期了,但是數據不一定已經被刪除了,因爲redis採用的是定期刪除和惰性刪除。定期刪除是指redis默認會每隔100ms會隨機抽取一些設置了過期時間的key檢查是否過期了,如果過期了就刪除。那麼爲什麼不遍歷刪除所有的而是隨機抽取一些呢?是因爲可能redis中放置了大量的key,如果你每隔100ms都遍歷,那麼CPU肯定爆炸,redis也就GG了。那麼這樣的話,爲什麼去查過期的key的話會查不到?

    其實這就是redis的惰性刪除,在你去查key的時候,redis會檢查一下這個key是否設置了過期時間和是否已經過期了,如果是redis會刪除這個key,並且返回空。

    那麼這樣的話豈不是出大問題了,如果過期了又沒有去查這個key,垃圾數據大量堆積,把redis的內存耗盡了怎麼辦?

    其實當內存佔用過多的時候,此時會進行內存淘汰,redis提供瞭如下策略:

    (1)noeviction:當內存不足以容納新寫入數據時,新寫入數據會報錯。

    (2)allkeys-lru:當內存不足以容納新寫入數據時,會移除最近最少使用的key。

    (3)allkeys-random:當內存不足以容納新寫入數據時,會隨機移除某個key。

    (4)volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的key。

    (5)volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個key。

    (6)volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的key優先移除。

    以上幾個策略最常用的應該是allkeys-lru,其實這個也要根據業務場景去選擇。

    5、redis的併發競爭問題以及解決方案

      答:當併發的去set某個值時可能結果與我們期望不同,這時我們就要去採取一些措施來避免。首先可以使用分佈式鎖來保證,這一塊我在上面的雙寫一致性裏面提到過,此處就不在贅述了。其次我們可以採用redis的CAS事務。Redis使用WATCH命令實現事務的“檢查再設置”(CAS) 行爲。作爲WATCH命令的參數的鍵會受到Redis的監控,Redis能夠檢測到它們的變化。在執行EXEC命令之前,如果Redis檢測到至少有一個鍵被修改了,那麼整個事務便會中止運行,然後EXEC命令會返回一個Null值, 提醒用戶事務運行失敗。

    6、緩存雪崩和穿透

       笞:緩存雪崩是發生在高併發的情況下,如果redis宕機不可用時大量的請求涌入數據庫,導致數據庫崩潰以至於整個系統不可用,在這種情況下數據庫會直接無法重啓,因爲起來會被再次打掛。所以要想避免緩存雪崩可以考慮採用以下措施,首先儘量保證redis的高可用(可以通過主從+哨兵或者redis cluster來提供高可用),其次可以使用一些限流降級組件(例如hystrix) 來避免mysq|被打掛,最後如果不是分佈式系統可以考慮本地緩存一些數據。

    緩存穿透發生在一些惡意攻擊下,在有些惡意攻擊中會僞造請求來訪問服務器,由於僞造的錯誤的數據在redis中不能找到,所以請求直接打到了數據庫,導致了高併發直接把mysq|打掛,同緩存雪崩一-樣,在這種情況下即使重啓數據庫也不能解決問題。想避免這種問題可以考慮採用以下措施,首先對請求過來的參數做合法性校驗,例如用戶id不是負數;其次,可以考慮如果有參數頻繁的訪問數據庫而不能查詢到,可以在redis給它搞個空值,避免請求直接打在數據庫。

    database

    1:請簡潔描述 MySQL 中 InnoDB 支持的四種事務隔離級別名稱,以及逐級之間的區別?

    答:

    1)、讀未提交,一個事務可以讀取到其他事務未提交的數據,會出現髒讀的問題

    2)、讀已提交,一個事務不可以讀取到其他事務未提交的數據,但是可以讀取到其他事務已提交的數據,會出現不可重複讀的問題

    3)、可重複讀,在一個事務中,前後兩次讀取的數據是一致的,在InnoDB中通過間隙鎖解決了幻讀的問題

    4)、串行化,事務排隊執行,不存在併發的問題,併發效率最低。

    2. 在MySQL中ENUM的用法是什麼?

       ENUM是一個字符串對象,用於指定一組預定義的值,並可在創建表時使用。SQL語法如下:

      Create table size(name ENUM('Smail,Medium','L arge');

     3. CHAR和VARCHAR的區別?

        CHAR和VARCHAR類型在存儲和檢索方面有所不同。CHAR列長度固定爲創建表時聲明的長度,長度值範圍是1到255。當CHAR值被存儲時,它們被用空格填充到特定長度,檢索CHAR值時需刪除尾隨空格。

     4.列的字符串類型可以是什麼?字符串類型是:

    SET

    BLOB

    ENUM

    CHAR

    TEXT

    VARCHAR

    5. MySQL中使用什麼存儲引擎?

       MyISAM InnoDB MEMORY MERGE ARCHIVE

    6. TIMESTAMP在UPDATE CURRENT _TIMESTAMP數據類型上做什麼?

      創建表時TIMESTAMP列用Zero更新。只要表中的其他字段發生更改,UPDATE

      CURRENT_ TIMESTAMP修飾符就將時間戳字段更新爲當前時間。

    jvm

    一.Java 類加載過程?

    Java 類加載需要經歷一下 7 個過程:

    1. 加載

    加載是類加載的第一個過程,在這個階段,將完成一下三件事情:

    • 通過一個類的全限定名獲取該類的二進制流。

    • 將該二進制流中的靜態存儲結構轉化爲方法去運行時數據結構。

    • 在內存中生成該類的 Class 對象,作爲該類的數據訪問入口。

    2. 驗證

    驗證的目的是爲了確保 Class 文件的字節流中的信息不回危害到 虛擬機.在該階段主要完成以下四鍾驗證:

    • 文件格式驗證:驗證字節流是否符合 Class 文件的規範,如 主次版本號是否在當前虛擬機範圍內,常量池中的常量是否 有不被支持的類型.

    • 元數據驗證:對字節碼描述的信息進行語義分析,如這個類是否有父類,是否集成了不被繼承的類等。• 字節碼驗證:是整個驗證過程中最複雜的一個階段,通過驗證數據流和控制流的分析,確定程序語義是否正確,主要針對方法體的驗證。如:方法中的類型轉換是否正確,跳轉指令是否正確等。

    • 符號引用驗證:這個動作在後面的解析過程中發生,主要是爲了確保解析動作能正確執行。

    3. 準備

    準備階段是爲類的靜態變量分配內存並將其初始化爲默認值,這些內存都將在方法區中進行分配。準備階段不分配類中的實例變量的內存,實例變量將會在對象實例化時隨着對象一起分配在 Java 堆中。public static int value=123;//在準備階段 value 初始值爲 0 。在初始化階段纔會變爲 123 。

    4. 解析

    該階段主要完成符號引用到直接引用的轉換動作。解析動作並不一定在初始化動作完成之前,也有可能在初始化之後。

    5. 初始化

    初始化時類加載的最後一步,前面的類加載過程,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其餘動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java 程序代碼。

    6. 使用

    7. 卸載

    二.描述一下 JVM 加載 Class 文件的原理機制?

    Java 語言是一種具有動態性的解釋型語言,類(Class)只有被加載到 JVM 後才能運行。當運行指定程序時,JVM 會將編譯生成的 .class 文件按照需求和一定的規則加載到內存中,並組織成爲一個完整的 Java 應用程序。這個加載過程是由類加載器完成,具體來說,就是由ClassLoader 和它的子類來實現的。類加載器本身也是一個類,其實質是把類文件從硬盤讀取到內存中。類的加載方式分爲隱式加載和顯示加載。隱式加載指的是程序在使用 new 等方式創建對象時,會隱式地調用類的加載器把對應的類加載到 JVM 中。顯示加載指的是通過直接調用 class.forName()方法來把所需的類加載到 JVM 中。任何一個工程項目都是由許多類組成的,當程序啓動時,只把需要的類加載到 JVM 中,其他類只有被使用到的時候纔會被加載,採用這種方法一方面可以加快加載速度,另一方面可以節約程序運行時對內存的開銷。此外,在 Java 語言中,每個類或接口都對應一個 .class 文件,這些文件可以被看成是一個個可以被動態加載的單元,因此當只有部分類被修改時,只需要重新編譯變化的類即可,而不需要重新編譯所有文件,因此加快了編譯速度。在 Java 語言中,類的加載是動態的,它並不會一次性將所有類全部加載後再運行,而是保證程序運行的基礎類(例如基類)完全加載到 JVM 中,至於其他類,則在需要的時候才加載。類加載的主要步驟:

    • 裝載。根據查找路徑找到相應的 class 文件,然後導入。

    • 鏈接。鏈接又可分爲 3 個小步:

    • 檢查,檢查待加載的 class 文件的正確性。

    • 準備,給類中的靜態變量分配存儲空間。

    • 解析,將符號引用轉換爲直接引用(這一步可選)

    • 初始化。對靜態變量和靜態代碼塊執行初始化工作。

    三 Java 內存分配。

    • 寄存器:我們無法控制。

    • 靜態域:static 定義的靜態成員。

    • 常量池:編譯時被確定並保存在 .class 文件中的(final)常量值和一些文本修飾的符號引用(類和接口的全限定名,字段的名稱和描述符,方法和名稱和描述符)。

    • 非 RAM 存儲:硬盤等永久存儲空間。• 堆內存:new 創建的對象和數組,由 Java 虛擬機自動垃圾回收器管理,存取速度慢。

    • 棧內存:基本類型的變量和對象的引用變量(堆內存空間的訪問地址),速度快,可以共享,但是大小與生存期必須確定,缺乏靈活性。

    1. Java 堆的結構是什麼樣子的?什麼是堆中的永久代(Perm Gen

    space)?

    JVM 的堆是運行時數據區,所有類的實例和數組都是在堆上分配內存。它在 JVM 啓動的時候被創建。對象所佔的堆內存是由自動內存管理系統也就是垃圾收集器回收。堆內存是由存活和死亡的對象組成的。存活的對象是應用可以訪問的,不會被垃圾回收。死亡的對象是應用不可訪問尚且還沒有被垃圾收集器回收掉的對象。一直到垃圾收集器把這些 對象回收掉之前,他們會一直佔據堆內存空間。

    四.GC 是什麼? 爲什麼要有 GC?

       GC 是垃圾收集的意思(GabageCollection),內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會導致程序或系統的不穩定甚至崩潰,Java 提供的 GC 功能可以自動監測對象是否超過作用域從而達到自動回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操作方法。

    五. 簡述 Java 垃圾回收機制。

       在 Java 中,程序員是不需要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在 JVM 中,有一個垃圾回收線程,它是低優先級的,在正常情況下是不會執行的,只有在虛擬機空閒或者當前堆內存不足時,纔會觸發執行,掃面那些沒有被任何引用的對象,並將它們添加到要回收的集合中,進行回收。

    六. 如何判斷一個對象是否存活?(或者 GC 對象的判定方法)

    判斷一個對象是否存活有兩種方法:

    1. 引用計數法

       所謂引用計數法就是給每一個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器爲零時,說明此對象沒有被引用,也就是“死對象”,將會被垃圾回收. 引用計數法有一個缺陷就是無法解決循環引用問題,也就是說當對象 A 引用對象 B,對象 B 又引用者對象 A,那麼此時 A、B 對象的引用計數器都不爲零,也就造成無法完成垃圾回收,所以主流的虛擬機都沒有采用這種算法。

    2. 可達性算法(引用鏈法)該算法的思想是:從一個被稱爲 GC Roots 的對象開始向下搜索,

       如果一個對象到 GC Roots 沒有任何引用鏈相連時,則說明此對象不可用。在 Java 中可以作爲 GC Roots 的對象有以下幾種:

    • 虛擬機棧中引用的對象

    • 方法區類靜態屬性引用的對象

    • 方法區常量池引用的對象

    • 本地方法棧 JNI 引用的對象

    雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象比不一定會被回收。當一個對象不可達 GC Root時,這個對象並不會立馬被回收,而是出於一個死緩的階段,若要被真正的回收需要經歷兩次標記. 如果對象在可達性分析中沒有與 GC Root 的引用鏈,那麼此時就會被第一次標記並且進行一次篩選,篩選的條件是是否有必要執行finalize() 方法。當對象沒有覆蓋 finalize() 方法或者已被虛擬機調用過,那麼就認爲是沒必要的。 如果該對象有必要執行finalize() 方法,那麼這個對象將會放在一個稱爲 F-Queue 的對隊列中,虛擬機會觸發一個 Finalize() 線程去執行,此線程是低優先級的,並且虛擬機不會承諾一直等待它運行完,這是因爲如果finalize() 執行緩慢或者發生了死鎖,那麼就會造成 F-Queue 隊列一直等待,造成了內存回收系統的崩潰。GC 對處於 F-Queue 中的對象進行第二次被標記,這時,該對象將被移除” 即將回收”集合,等待回收。

    一丶垃圾回收的優點和原理。並考慮 2 種回收機制。

        Java語言中一個顯著的特點就是引入了垃圾回收機制,使 C++ 程序員最頭疼的內存管理的問題迎刃而解,它使得Java程序員在編寫程序的時候不再需要考慮內存管理。由於有個垃圾回收機制,Java中的對象不再有“作用域”的概念,只有對象的引用纔有"作用域"。垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作爲一個單獨的低級別的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清楚和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。回收機制有分代複製垃圾回收和標記垃圾回收,增量垃圾回收。

     

    二、垃圾回收器的基本原理是什麼?垃圾回收器可以馬上回收內存嗎?有什麼辦法主動通知虛擬機進行垃圾回收?

        對於GC來說,當程序員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。通常,GC採用有向圖的方式記錄和管理堆(heap) 中的所有對象。通過這種方式確定哪些對象是”可達的”,哪些對象是”不可達的”。當GC確定一些對象爲“不可達”時,GC就有責任回收這些內存空間。

    可以。程序員可以手動執行System.gc(),通知GC運行,但是Java語言規範並不保證GC一定會執行。

    三、Java中會存在內存泄漏嗎,請簡單描述

        所謂內存泄露就是指一個不再被程序使用的對象或變量-直被佔據在內存中。Java中有垃圾回收機制,它可以保證一對象不再被引用的時候,即對象變成了孤兒的時候,對象將自動被垃圾回收器從內存中清除掉。由於Java使用有向圖的方式進行垃圾回收管理,可以消除弓引|用循環的問題,例如有兩個對象,相互引用,只要它們和根進程不可達的,那麼GC也是可以回收它們的,例如下面的代碼可以看到這種情況的內存回收:

    import java.io.lOException;

    public class GarbageTest {

    /**

     * @param args

     * @throws IOException

    */

    public static void main(String[] args) throws IOException{

     // TODO Auto-generated method stub

    try {

    gcTest();

      }catch (lOException e) {

    //TODO-Auto-generated-catch-block

    e.printStackTrace();

    }

    System.out.println("has exited gcTest!");

    System.in.read();

    System.in.read();

    System.out.println("out begin gc!");

    for(int i=0;i<100;i++){

    System.gc();

    System.in.read();

    System.in.read();

    }}

    private static void gcTest() throws lOException{

    System.in.read();

    System.in.read();

    Person p1=new Person();

    System.in.read();

    System.in.read();

    Person p2 = new Person();

    p1.setMate(p2);

    p2.setMate(p1);

    System.out.println("before exit gctest!");

    System.in.read();

    System.in.read();

    System.gc();

    System.out.println("exit gctest!");

    }

    private static class Person

    {

    byte[] data = new byte[ 20000000];

    Person mate = null;

    public void setMate(Person other){

     mate = other;

    }}}

    Java中的內存泄露的情況:

    長生命週期的對象持有短生命週期對象的引用就很可能發生內存泄露,儘管短生命週期對象已經不再需要,但是因爲長生命週期對象持有它的引用而導致不能被回收,這就是Java中內存泄露的發生場景,通俗地說,就是程序員可能創建了一個對象,以後一直不再使用這個對象,這個對象卻一直被引用,即這個對象無用但是卻無法被垃圾回收器回收的,這就是java中可能出現內存泄露的情況,例如,緩存系統,我們加載了一個對象放在緩存中(例如放在一個全局map對象中),然後一直不再使用它,這個對象一直被緩存引用,但卻不再被使用。

    檢查Java中的內存泄露,一定要讓程序將各種分支情況都完整執行到程序結束,然後看某個對象是否被使用過,如果沒有,則才能判定這個對象屬於內存泄露。

    如果一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即使那個外部類實例對象不再被使用,但由於內部類持久外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會造成內存泄露。

     

    下面內容來自於網上(主要特點就是清空堆棧中的某個元素,並不是徹底把它從數組中拿掉,而是把存儲的總數減少,本人寫得可以比這個好,在拿掉某個元素時,順便也讓它從數組中消失,將那個元素所在的位置的值設置爲null 即可) :

    我實在想不到比那個堆棧更經典的例子了,以致於我還要引用別人

    的例子。

    下面的例子不是我想到的,是書上看到的,當然如果沒有在書上看到,可能過一段時間我自己也想的到,可是那時我說是我自己想到的也沒有人相信的。

    public class Stack {

    private Object[] elements=new Object[10];

    private int size = 0;

    public void push(Object e){

    ensureCapacity();

    elements[size++] = e;

    }

    public Object pop()(

    if( size == 0) throw new EmptyStackException();

    return elements[-- size];

    }

    private void ensureCapacity(){

    if(elements.length == size){

    Object[] oldElements = elements;

    elements = new Object[2 * elements.length+1];

    System.arraycopy(oldElements,0, elements, 0,size);

    }}}

    上面的原理應該很簡單,假如堆棧加了10個元素,然後全部彈出來,雖然堆棧是空的,沒有我們要的東西,但是這是個對象是無法回收的,這個才符合了內存泄露的兩個條件:無用,無法回收。但是就是存在這樣的東西也不一定會導致什麼樣的後果

    如果這個堆棧用的比較少,也就浪費了幾個K內存而已,反正我們的內存都上G了,哪裏會有什麼影響,再說這個東西很快就會被回收的,有什麼關係。下面看兩個例子。

    public class Bad{

    public static Stack s=Stack();static{

    s.push(new Object());

    s.pop();

     //這裏有一個對象發生內存泄露

    s.push(new Object());

     // 上面的對象可以被回收了,等於

    //是自愈了

    }}

    因爲是static,就一直存在到程序退出,但是我們也可以看到它有自愈功能,就是說如果你的Stack最多有100個對象,那麼最多也就只有100個對象無法被回收其實這個應該很容易理解,Stack內部持有100個引用,最壞的情況就是他們都是無用的,

    因爲我們一旦放新的進去,以前的引用自然消失!

    內存泄露的另外一種情況:當一個對象被存儲進HashSet集合中以後,就不能修改這個對象中的那些參與計算哈希值的字段了,否則,對象修改後的哈希值與最初存儲進HashSet集合中時的哈希值就不同了,在這種情況下,即使在contains方法使用該對象的當前引用作爲的參數去HashSet集合中檢索對象,也將返回找不到對象的結果,這也會導致無法從HashSet集合中單獨刪除當前對象,造成內存泄露。

    四、深拷貝和淺拷貝。簡單來講就是複製、克隆。

    Person p=new Person(“張三”);

        淺拷貝就是對對象中的數據成員進行簡單賦值,如果存在動態成員或者指針就會報錯。

        深拷貝就是對對象中存在的動態成員或指針重新開闢內存空間。

     五、System.gc() 和Runtime.gc() 會做什麼事情?

        這兩個方法用來提示JVM要進行垃圾回收。但是,立即開始還是延遲進行垃圾回收是取決於JVM的。

    六、finalize() 方法什麼時候被調用?析構函數(fnalization)的目的是什麼?

        垃圾回收器(garbage colector)決定回收某對象時,就會運行該對象的finalize()方法但是在Java中很不幸,如果內存總是充足的,那麼垃圾回收可能永遠不會進行,也就是說flalize()可能永遠不被執行,顯然指望它做收尾工作是靠不住的。

      那麼finalize()究竟是做什麼的呢?

    它最主要的用途是回收特殊渠道申請的內存。Java程序有垃圾回收器,所以一般情況下內存問題不用程序員操心。但有一種JNI (Java Native Interface)調用non-Java程序(C 或C++),fnalize() 的工作就是回收這部分的內存。

    1、finalize() 方法什麼時候被調用?析構函數 (finalization) 的

    目的是什麼?

    垃圾回收器(garbage colector)決定回收某對象時,就會運行該對象的 finalize() 方法 但是在 Java 中很不幸,如果內存總是充足的,那麼垃圾回收可能永遠不會進行,也就是說 filalize() 可能永遠不被執行,顯然指望它做收尾工作是靠不住的。 那麼finalize() 究竟是做什麼的呢? 它最主要的用途是回收特殊渠道申請的內存。Java 程序有垃圾回收器,所以一般情況下內存問題不用程序員操心。但有一種 JNI(Java Native Interface)調用non-Java 程序(C 或 C++), finalize() 的工作就是回收這部分的內存。

    2、 如果對象的引用被置爲 null,垃圾收集器是否會立即釋放對象佔

    用的內存?

    不會,在下一個垃圾回收週期中,這個對象將是可被回收的。

    3、 什麼是分佈式垃圾回收(DGC)?它是如何工作的?

    DGC 叫做分佈式垃圾回收。RMI 使用 DGC 來做自動垃圾回收。因爲 RMI 包含了跨虛擬機的遠程對象的引用,垃圾回收是很困難的。DGC 使用引用計數算法來給遠程對象提供自動內存管理。

    4、 串行(serial)收集器和吞吐量(throughput)收集器的區別是什麼?

       吞吐量收集器使用並行版本的新生代垃圾收集器,它用於中等規模和大規模數據的應用程序。 而串行收集器對大多數的小應用(在現代處理器上需要大概 100M 左右的內存)就足夠了。

    5、 在 Java 中,對象什麼時候可以被垃圾回收?

        當對象對當前使用這個對象的應用程序變得不可觸及的時候,這個對象就可以被回收了。

    6、 簡述 Java 內存分配與回收策率以及 Minor GC 和 Major

    GC。• 對象優先在堆的 Eden 區分配

    • 大對象直接進入老年代

    • 長期存活的對象將直接進入老年代當 Eden 區沒有足夠的空間進行分配時,虛擬機會執行一次Minor GC。Minor GC 通常發生在新生代的 Eden 區,在這個區的對象生存期短,往往發生 Gc 的頻率較高,回收速度比較快;Full GC/Major GC 發生在老年代,一般情況下,觸發老年代 GC的時候不會觸發 Minor GC,但是通過配置,可以在 Full GC 之前進行一次 Minor GC 這樣可以加快老年代的回收速度。

    7、 JVM 的永久代中會發生垃圾回收麼?

    垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。注:Java 8 中已經移除了永久代,新加了一個叫做元數據區的native 內存區。

    8、 Java 中垃圾收集的方法有哪些?

    標記 - 清除:這是垃圾收集算法中最基礎的,根據名字就可以知道,它的思想就是標記哪些要被回收的對象,然後統一回收。這種方法很簡單,但是會有兩個主要問題:

    1. 效率不高,標記和清除的效率都很低;

    2. 會產生大量不連續的內存碎片,導致以後程序在分配較大的對象時,由於沒有充足的連續內存而提前觸發一次 GC 動作。

    複製算法:爲了解決效率問題,複製算法將可用內存按容量劃分爲相等的兩部分,然後每次只使用其中的一塊,當一塊內存用完時,就將還存活的對象複製到第二塊內存上,然後一次性清楚完第一塊內存,再將第二塊上的對象複製到第一塊。但是這種方式,內存的代價太高,每次基本上都要浪費一般的內存。於是將該算法進行了改進,內存區域不再是按照 1:1 去劃分,而是將內存劃分爲 8:1:1 三部分,較大那份內存交 Eden 區,其餘是兩塊較小的內存區叫 Survior 區。每次都會優先使用 Eden 區,若 Eden 區滿,就將對象複製到第二塊內存區上,然後清除 Eden區,如果此時存活的對象太多,以至於 Survivor 不夠時,會將這些對象通過分配擔保機制複製到老年代中。(java 堆又分爲新生代和老年代)

    標記 - 整理:該算法主要是爲了解決標記 - 清除,產生大量內存碎片的問題;當對象存活率較高時,也解決了複製算法的效率問題。它的不同之處就是在清除對象的時候現將可回收對象移動到一端,然後清除掉端邊界以外的對象,這樣就不會產生內存碎片了。分代收集:現在的虛擬機垃圾收集大多采用這種方式,它根據對象的生存週期,將堆分爲新生代和老年代。在新生代中,由於對象生存期短,每次回收都會有大量對象死去,那麼這時就採用複製算法。老年代裏的對象存活率較高,沒有額外的空間進行分配擔保。

    9.什麼是類加載器?類加載器有哪些?

    實現通過類的權限定名獲取該類的二進制字節流的代碼塊叫做類加載器。

    主要有一下四種類加載器:

    ●啓動類加載器(Bootstrap ClassL oader)用來加載Java核心類庫,無法被Java程序直接引用。

    ●擴展類加載器(extensions class loader) :它用來加載Java的擴展庫。Java虛擬機的實現會提供一一個擴展庫目錄。該類加載器在此目錄裏面查找並加載Java類。

    ●系統類加載器(system class loader) :它根據Java應用的類路徑(CL ASSPATH)來加載Java類。一般來說,Java應用的類都是由它來完成加載的。可以通過ClassL oader.getSystemClassL oader()來獲取它。

    ●用戶自定義類加載器,通過繼承< java.lang.ClassL oader類的方式實現。

    10、類加載器雙親委派模型機制?

    當一個類收到了類加載請求時,不會自己先去加載這個類,而是將其委派給父類,由父類去加載,如果此時父類不能加載反饋給子類,由子類去完成類的加載。

    集合框架

    1. ArrayList 和 Vector 的區別。 
       這兩個類都實現了 List 接口(List 接口繼承了 Collection 接口),他們都是有序集合,即存儲在這兩個集合中的元素的位置都是有順序的,相當於一種動態的數組,我們以後可以按位置索引號取出某個元素,並且其中的數據是允許重複的,這是 HashSet 之類的集合的最大不同處,HashSet 之類的集合不可以按索引號去檢索其中的元素,也不允許有重複的元素(本來題目問的與 hashset 沒有任何關係,但爲了說清楚 ArrayList 與 Vector 的功能,我們使用對比方式,更有利於說明問題)。接着才說ArrayList 與 Vector 的區別,這主要包括兩個方面。 
    · 同步性: 
    Vector 是線程安全的,也就是說是它的方法之間是線程同步的,而 ArrayList 是線程不安全的,它的方法之間是線程不同步的。如果只有一個線程會訪問到集合,那最好是使用 ArrayList,因爲它不考慮線程安全,效率會高些;如果有多個線程會訪問到集合,那最好是使用 Vector,因爲不需要我們自己再去考慮和編寫線程安全的代碼。 
    備註:對於 Vector&ArrayList、Hashtable&HashMap,要記住線程安全的問題,記住 Vector 與 Hashtable 是舊的,是 java 一誕生就提供了的,它們是線程安全 
    的,ArrayList 與 HashMap 是 java2 時才提供的,它們是線程不安全的。所以,我們講課時先講老的。
    · 數據增長: 
    ArrayList 與 Vector 都有一個初始的容量大小,當存儲進它們裏面的元素的個數超過了容量時,就需要增加 ArrayList 與 Vector 的存儲空間,每次要增加存儲空間時,不是隻增加一個存儲單元,而是增加多個存儲單元,每次增加的存儲單元的個數在內存空間利用與程序效率之間要取得一定的平衡。Vector 默認增長爲原來兩倍,而ArrayList 的增長策略在文檔中沒有明確規定(從源代碼看到的是增長爲原來的 1.5 倍)。ArrayList 與 Vector 都可以設置初始的空間大小,Vector 還可以設置增長的空間大小,而 ArrayList 沒有提供設置增長空間的方法。總結:即 Vector 增長原來的一倍,ArrayList 增加原來的 0.5 倍。 
    2. 說說 ArrayList,Vector, LinkedList 的存儲性能和特性。 
    ArrayList 和 Vector 都是使用數組方式存儲數據,此數組元素數大於實際存儲的數 據以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及數組元素移動等內存操作,所以索引數據快而插入數據慢,Vector 由於使用了 synchronized 方法(線程安全)。通常性能上較 ArrayList 差,而 LinkedList 使用雙向鏈表實現存儲,按序號索引數據需要進行前向或後向遍歷,但是插入數據時只需要記錄本項的前後項即可,所以插入速度較快 。 
    ArrayList 在查找時速度快,LinkedList 在插入與刪除時更具優勢。 
    3. 快速失敗 (fail-fast) 和安全失敗 (fail-safe) 的區別是什麼?Iterator 的安全失敗是基於對底層集合做拷貝,因此,它不受源集合上修改的影響。 
    java.util 包下面的所有的集合類都是快速失敗的,而 java.util.concurrent 包下面的 
    所有的類都是安全失敗的。快速失敗的迭代器會拋出 
    ConcurrentModificationException 異常,而安全失敗的迭代器永遠不會拋出這樣的異常。

    4. hashmap的數據結構。

       在java編程語言中,最基本的結構就是兩種,一個是數組,另外一個是模擬指針(引用),所有的數據結構都可以用這兩個基本結構來構造的,hashmap 也不例外。Hashmap實際上是一個數組和鏈表的結合體(在數據結構中,一般稱之爲“鏈表散列“)

    5. HashMap的工作原理是什幺?

       Java中的HashMap是以鍵値対(key- -value) 的形式存儲元素的。HashMap需要一個hash函數,它使用hashCode()和equals()方法來向集合/從集合添加和檢索元素。當凋用put()方法的吋候,HashMap 會汁算key的hash値,然後把鍵值対存偖在集合中合透的索引上。如果key已経存在了value會被更新成新値。

    HashMap的一些重要的特性是它的容量(capacity),負載因子(load factor)和抗容板限(threshold resizing)。

    6. HashMap什麼時候進行擴容?

       當hashmap中的元素個數超過數組大小loadFactor時就會進行數組擴容,loadFactor的默認值爲0.75, 也就是說,默認情況下,數組大小爲16,那麼當hashmap中元素個數超過160.75=12的時候,就把數組的大小擴展爲216=32,即擴大一倍,然後重新計算每個元素在數組中的位置,而這是一個非常消耗性能的操作,所以如果我們已經預知hashmap中元素的個數,那麼預設元素的個數能夠有效

    的提高hashmap的性能。比如說,我們有1000個元素new HashMap(1000),但是理論上來講new HashMap(1024)更合適,不過上面annegu已經說過,即使是1000, hashmap也自動會將其設置爲1024。但是new HashMap(1024)還不是更合適的,因爲0.75*1000 < 1000,也就是說爲了讓0.75 * size > 1000,我們必須這樣new HashMap(2048)才最合適,既考慮了&的問題,也避免了resize的問題。

    7. List、Map、Set 三個接口,存取元素時,各有什麼特點?這樣的題屬於隨意發揮題:這樣的題比較考水平,兩個方面的水平:一是要真正明白這些內容,二是要有較強的總結和表述能力。如果你明白,但表述不清楚,在別人那裏則等同於不明白。首先,List 與 Set 具有相似性,它們都是單列元素的集合,所以,它們有一個功共同的父接口,叫 Collection。Set 裏面不允許有重複的元素,所謂重複,即不能有兩個相等(注意,不是僅僅是相同)的對象 ,即假設 Set 集合中有了一個 A 對象,現在我要向 Set 集合再存入一個 B 對象,但 B 對象與 A 對象 equals 相等,則 B 對象存儲不進去,所以,Set 集合的 add 方法有一個 boolean 的返回值,當集合中沒有某個元素,此時 add 方法可成功加入該元素時,則返回 true,當集合含有與某個元素 equals 相等的元素時,此時 add 方法無法加入該元素,返回結果爲 false。

    Set 取元素時,沒法說取第幾個,只能以 Iterator 接口取得所有的元素,再逐一遍歷各個元素。List 表示有先後順序的集合, 注意,不是那種按年齡、按大小、按價格之類的排序。

    當我們多次調用 add(Obj e) 方法時,每次加入的對象就像火車站買票有排隊順序一樣,按先來後到的順序排序。有時候,也可以插隊,即調用 add(int index,Obj e) 方法,就可以指定當前對象在集合中的存放位置。一個對象可以被反覆存儲進 List 中,

    每調用一次 add 方法,這個對象就**入進集合中一次,其實,並不是把這個對象本身存儲進了集合中,而是在集合中用一個索引變量指向這個對象,當這個對象被add 多次時,即相當於集合中有多個索引指向了這個對象,如圖 x 所示。List 除了可以以 Iterator 接口取得所有的元素,再逐一遍歷各個元素之外,還可以調用get(index i) 來明確說明取第幾個。Map 與 List 和 Set 不同,它是雙列的集合,其中有 put 方法,定義如下:

    put(obj key,obj value),每次存儲時,要存儲一對 key/value,不能存儲重複的key,這個重複的規則也是按 equals 比較相等。取則可以根據 key 獲得相應的value,即 get(Object key) 返回值爲 key 所對應的 value。另外,也可以獲得所有的 key 的結合,還可以獲得所有的 value 的結合,還可以獲得 key 和 value 組合成的 Map.Entry 對象的集合。

    List 以特定次序來持有元素,可有重複元素。Set 無法擁有重複元素, 內部排序。

    Map 保存 key-value 值,value 可多值。HashSet 按照 hashcode 值的某種運算方式進行存儲,而不是直接按 hashCode值的大小進行存儲。例如,"abc" ---> 78,"def" ---> 62,"xyz" ---> 65 在hashSet 中的存儲順序不是 62,65,78,這些問題感謝以前一個叫崔健的學員提出,最後通過查看源代碼給他解釋清楚,看本次培訓學員當中有多少能看懂源碼。 LinkedHashSet 按插入的順序存儲,那被存儲對象的 hashcode 方法還有什麼作用呢?學員想想! hashset 集合比較兩個對象是否相等,首先看 hashcode 方法是否相等,然後看 equals 方法是否相等。new 兩個 Student 插入到 HashSet 中,看HashSet 的 size,實現 hashcode 和 equals 方法後再看 size。同一個對象可以在 Vector 中加入多次。往集合裏面加元素,相當於集合裏用一根繩子連接到了目標對象。往 HashSet 中卻加不了多次的。

     8. Set裏的元素是不能重複的,那麼用什麼方法來區分重複與否呢?是用==還是equals()?它們有何區別?

          Set裏的元素是不能重複的,元素重複與否是使用equaIs()方法進行判斷的。

          equals()和==方法決定引用值是否指向同一對象equaIs()在類中被覆蓋,爲的是當兩個分離的對象的內容和類型相配的話,返回真值。

          9.兩個對象值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?

    對。如果對象要保存在HashSet或HashMap中,它們的equals相等,那麼,它們的hashcode值就必須相等。如果不是要保存在HashSet或HashMap,則與hashcode沒有什麼關係了,這時候hashcode不等是可以的,例如arrayL ist存儲的對象就不用實現hashcode,當然,我們沒有理由不實現,通常都會去實現的。

    10. heap和stack 有什麼區別。

       Java的內存分爲兩類,一類是棧內存,一類是堆內存。棧內存是指程序進入一個方法時,會爲這個方法單獨分配一塊私屬存儲空間, 用於存儲這個方法內部的局部變量,

    當這個方法結束時,分配給這個方法的棧會釋放,這個棧中的變量也將隨之釋放。

    堆是與棧作用不同的內存,一般用於存放不放在當前方法棧中的那些數據,例如,使用new創建的對象都放在堆裏,所以,它不會隨方法的結束而消失。方法中的局部變量使用fnal修飾後,放在堆中,而不是棧中。

     11. Java集合類框架的基本接口有哪些?

          集合類接口指定了一組叫做元素的對象。集合類接口的每一種具體的實現類都可以選擇以它自己的方式對元素進行保存和排序。有的集合類允許重複的鍵,有些不允許。

          Java集合類提供了一套設計良好的支持對一組對象進行操作的接口和類。Java集合類裏面最基本的接口有:

          Collection:代表一組對象, 每一個對象都是它的子元素

          Set:不包含重複元素的Collection。

          List:有順序的collection,並且可以包含重複元素。

          Map:可以把鍵(key)映射到值(value) 的對象,鍵不能重複。

    12. HashSet和TreeSet有什麼區別?

        HashSet是由一個hash表來實現的,因此,它的元素是無序的。add(),

    remove(),contains(),TreeSet是由一個樹形的結構來實現的,它裏面的元素是有序的。因此,add(),remove(), contains() 方法的時間複雜度是O(logn)。

    13. HashSet 的底層實現是什麼? 
    通過看源碼知道 HashSet 的實現是依賴於 HashMap 的,HashSet 的值都是存儲在 HashMap 中的。在 HashSet 的構造法中會初始化一個 HashMap 對象,HashSet 不允許值重複,因此,HashSet 的值是作爲 HashMap 的 key 存儲在HashMap 中的,當存儲的值已經存在時返回 false。 
    14. LinkedHashMap 的實現原理? 
    LinkedHashMap 也是基於HashMap 實現的,不同的是它定義了一個 Entry header,這個 header 不是放在 Table 裏,它是額外獨立出來的。LinkedHashMap 通過繼承 hashMap 中的 Entry,並添加兩個屬性 Entry before,after, 和 header 結合起來組成一個雙向鏈表,來實現按插入順序或訪問順序排序。LinkedHashMap 定義了排序模式 accessOrder,該屬性爲 boolean 型變量,對於訪問順序,爲 true;對於插入順序,則爲false。一般情況下,不必指定排序模式,其迭代順序即爲默認爲插入順序。 
    15. 爲什麼集合類沒有實現 Cloneable 和 Serializable 接口? 
        克隆 (cloning) 或者是序列化 (serialization) 的語義和含義是跟具體的實現相關的。 因此,應該 由集合類的具體實現來決定如何被克隆或者是序列化。 
    16. 什麼是迭代器 (Iterator)? 
        Iterator 接口提供了很多對集合元素進行迭代的方法。每一個集合類都包含了可以返回迭代 器實例的迭代方法。迭代器可以在迭代的過程中刪除底層集合的元素, 但是不可以直接調用集合的 remove(Object Obj) 刪除,可以通過迭代器的 remove() 方法刪除。 
    17. Iterator 和 ListIterator 的區別是什麼? 
    下面列出了他們的區別: 
    Iterator 可用來遍歷 Set 和 List 集合,但是 ListIterator 只能用來遍歷 List。 
    Iterator 對集合只能是前向遍歷,ListIterator 既可以前向也可以後向。 
    ListIterator 實現了 Iterator 接口,幷包含其他的功能,比如:增加元素,替換元素,獲取前一個和後一個元素的索引,等等。
    18. 數組 (Array) 和列表 (ArrayList) 有什麼區別?什麼時候應該使用 Array 而不是 ArrayList? 
    Array 可以包含基本類型和對象類型,ArrayList 只能包含對象型。 
    Array 大小是固定的,ArrayList 的大小是動態變化的。 
    ArrayList 處理固定大小的基本數據類型的時候,這種方式相對比較慢。

    19. Java 集合類框架的最佳實踐有哪些?

    · 假如元素的大小是固 定的,而且能事先知道,我們就應該用 Array 而不是ArrayList。

    有些集合類允許指定初始容量。因此,如果我們能估計出存儲的元素的數目,我們可以設置 初始容量來避免重新計算 hash 值或者是擴容。

    · 爲了類型安全,可讀性和健壯性的原因總是要使用泛型。同時,使用泛型還可以避免運行時的 ClassCastException。

    · 使用 JDK 提供的不變類 (immutable class) 作爲 Map 的鍵可以避免爲我們自己的類實現 hashCode()和 equals()方法。

    · 編程的時候接口優於實現。

    · 底層的集合實際上是空的情況下,返回長度是 0 的集合或者是數組,不要返回 null。

    20. Set 裏的元素是不能重複的,那麼用什麼方法來區分重複與否呢?是用 == 還是equals()?        它們有何區別?

    Set 裏的元素是不能重複的,那麼用 iterator() 方法來區分重複與否。equals() 是判讀兩個 Set 是否相等equals() 和 == 方法決定引用值是否指向同一對象 equals() 在類中被覆蓋,爲的是當兩個分離的對象的內容和類型相配的話,返回真值

    21. Comparable 和 Comparator 接口是幹什麼的?列出它們的區別。

    Java提供了只包含一個 compareTo() 方法的 Comparable 接口。這個方法可以給兩個對象排序。具體來說,它返回負數,0,正數來表明輸入對象小於,等於,大於已經存在的對象。

    Java 提供了包含 compare() 和 equals() 兩個方法的 Comparator 接口。 compare() 方法用來給兩個輸入參數排序,返回負數,0,正數表明第一個參數是小於,等於,大於第二個參數。equals() 方法需要一個對象作爲參數,它用來決定輸入參數是否和 comparator 相等。只有當輸入參數也是一個 comparator 並且輸入參數和當前 comparator 的排序結果是相同的時 候,這個方法才返回 true。

    22. Collection 和 Collections 的區別。

    collection 是集合類的上級接口, 繼承與它的接口主要是 set 和 list。

    collections 類是針對集合類的一個幫助類. 它提供一系列的靜態方法對各種集合的搜索, 排序, 線程安全化等操作。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章