Java 基礎知識(二)

一、Java基礎

1、重載(overload)和重寫(override)的區別

①、被覆蓋的方法不能是private,那樣就相當於在之類中新建了一個新的方法而已
②、覆蓋是指對父類方法的重寫,拋出的異常範圍小於父類,訪問權限應該不小於父類
③、重載是一個類中有多個相同名稱的方法,它們的參數類型、參數順序、參數個數不一樣(返回值不能作爲方法簽名)

Note:
a、上述的方法簽名:方法名和參數列表是在Java代碼層面的,在Class文件層面範圍會更大一些:還包括方法返回值和受查異常表

b、返回值不能作爲簽名原因:Java中調用函數並不需要強制賦值
如:int d(){…} 和 void d(){…}
調用的地方:d();此時就會無法區別是調用的哪一個方法。

2、接口和抽象類的區別

①、接口是完全的抽象類,所有的方法都是抽象的,而抽象類中允許有非抽象的方法
②、抽象類只能單繼承,接口可以多個實現
③、抽象類中可以有變量,接口中只能有static final的常量,且必須被初始化
④、接口中不能含有靜態代碼塊以及靜態方法,而抽象類可以有靜態代碼塊和靜態方法;
⑤、java8中可以使用default來修飾方法,此時方法可以有方法體

3、java常用的包

java.lang 、java.util 、 java.io 、 java.net 、 java.sql

二、Java中常見集合

1、常見的集合

Collection

所有集合框架的父接口,沒有具體實現,而是讓List、Set和Queue接口進行繼承在各自實現。

Set

特點:無序、不能重複,基於散列表實現
主要實現類:AbstractSet(實現Set)、HashSet(繼承AbstractSet)、LinkedHashSet(繼承HashSet)、TreeSet(繼承AbstractSet)、SortedSet(接口,繼承Set)

  • 1、HashSet通過HashMap(哈希表)實現,其實就是Map的key,非線程安全、集合元素可以爲null,無序
  • 2、LinkedHashSet是HashSet的子類,基於哈希表和鏈表實現
  • 3、TreeSet唯一實現了SortedSet的類,通過NavigableMap來實現(基於紅黑樹),其實就是Map的key,大小有序
  • 4、EnumSet專門爲枚舉類設計的集合(是Set實現類中性能最好的),內部以位向量的形式存儲,所以運行效率高、佔內存小。但是傳入的必須是指定的枚舉類的枚舉值(在構造器中傳入),加入的值不能爲空,有序。

    1、普通插入、刪除HashSet比LinkedHashSet更快
    2、遍歷LinkedHashSet會更快
    3、上面三個實現都是線程不安全的,解決辦法:Set set = Collections.synchronizedSet(set 對象)

List

特點:有序、可以重複,可以按索引訪問
主要實現類:ArrayList(繼承AbstractList,實現List)、LinkedList(繼承AbstractList,實現List)、Vector(繼承AbstractList,實現List)、Stack(繼承自Vector)等

  • ArrayList通過數組實現,查詢、更新快,刪除、插入慢,線程不安全,但是效率高
  • Vector通過數組實現,查詢、更新快,刪除、插入慢,線程安全,但是效率低
  • LinkedList通過鏈表實現,查詢慢,刪除、插入快,線程不安全,效率高

Map

特點:

  • 1、一個Map可以看成是多個Entry(key,value)的集合
  • 2、Map不能包含重複的key,但是可以包含相同的value。

常用實現類:HashMap、Hashtable、ConcurrentHashMap、LinkedHashMap和TreeMap

HashMap

使用紅黑樹實現,快速訪問,只能有一條記錄的key爲NULL,允許多條記錄的value爲NULL,線程不安全

Hashtable

HashMap的線程安全版,每次上鎖的時候會將整個表鎖住,會很大的影響速度,效率較低,且不允許key爲NULL

ConcurrentHashMap

相比Hashtable來說稍細粒度的鎖(鎖分段技術),將整個表分爲16個Segment,每個Segment相當於一個Hashtable,每次上鎖針對的是相應的segment。

LinkedHashMap

有HashMap的全部特性,會保存數據插入的順序,遍歷的時候會比HashMap慢,但是插入的時候應該比HashMap快

TreeMap

使用紅黑樹實現,根據鍵值大小排序,默認升序,也可以指定排序的比較器(同TreeSet),非線程安全,不允許key爲NULL

Iterator 和 Iterable

所有的Collection類,都繼承了Iterable接口,這個接口主要是提供for-each循環,在這個接口裏面封裝了iterator。

在Iterator中主要包含以下三種方法:

  • 1.hasNext()是否還有下一個元素。
  • 2.next()返回下一個元素。
  • 3.remove()刪除當前元素。

Iterator替換了原始的Enumeration類,主要是:

  • 1、允許在使用迭代器的過程中移除元素
  • 2、優化了方法名

2、List、Set、Map的初始大小、加載因子和擴容大小

ArrayList

初始大小是10,加載因子是1,之後每次增加當前容量的一半(oldCapacity + (oldCapacity >> 1)),比如當前大小是10,擴容之後是15

Vector

初始默認大小是10,加載因子是1,擴容爲原來的一倍(oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity)),如果給出了擴容大小capacityIncrement則每次擴容的大小就是capacityIncrement,沒有給出則capacityIncrement爲0,此時每次擴容就是增加一倍的大小,比如,原本10,擴容之後爲20

Hashtable

初始默認大小是11,加載因子是0.75,使用Entry數組實現的

HashSet 和 HashMap

兩個一樣的原則,初始大小是16,加載因子是0.75,擴容增量爲原來容量的1倍

由上可知,HashMap的大小應該爲2的冪,原因如下:

  • 1、tab[i = (n - 1) & hash],其中n是tab的大小,tab就是當前HashMap的大小,hash就是key對應的hash值,得到的i就是應該放置的位置。
  • 2、當n爲2的次冪,則就能保證n-1都是111111….的形式,此時&hash的值就很好就算了,這樣的話每一個hash值的位置都能利用上,而不會浪費,提高利用率。利用率高了,衝突也會少一些,查詢的效率更高。比如:如果n = 15,則n-1 = 14,對應的二進制爲1110, 此時無論hash的值爲多少,末尾的一位爲1的時候時候就會無法存放元素了,比如:0001,0011,0101,0111,1001,1011,1101這幾個位置的空間就會浪費了。

3、Comparable接口和Comparator接口

Comparable

是排序接口,實現了這個接口的類可以支持Arrays.sort()和Collections.sort()進行排序,具體比較兩個類的大小的方法是通過實現Comparable中的compareTo方法來實現的。

特點:簡單,但是,假設Person類要實現這個接口,則修改Person類中的代碼,如果比較的方法發生變化,也需要修改 Person中的compareTo方法。甚至,如果當前的比較對象不是Person了,則有需要在Peroson中刪掉這一段代碼。

Comparator

是一個比較器,是定義在Person外部的,實現這個比較器對Person這個類沒有任何影響。可以在實現的這個外部比較器中實現通用的比較方法,根據泛型傳入父類,就可以實現大範圍的比較了。

必須實現裏面的compare()方法。

4、集合中的快速失敗機制(fail-fast)

是什麼

在多線程環境下,JAVA的Collection不允許當一個線程對集合進行iterator迭代的時候,另一個線程去改變集合的結構。

原理

在所有的List和HashMap裏面(HashSet裏面是用的HashMap實現的,所以應該也存在這個機制),都維護了一個變量modCount來記錄當前集合被修改的次數。同時,這些類中都是實現了Iterator迭代器(注意,這個機制只會存在於迭代的過程中,因爲是在實現Iterator接口的時候拋出的異常)。

每次集合中涉及到修改集合內容的時候,如add()、remove()、clear()等操作,都會將modCount變量進行增加,表示被修改了。在Iterator的實現中,還維護了一個變量expectedModCount,這個變量的值一直都是等於modCount的值(ArrayList爲例):

int expectedModCount = ArrayList.this.modCount;

在每次進行next()時,會調用checkForComodification()檢查當前的modCount和expectedModCount的值是否相等,如果不相等就會拋出ConcurrentModificationException異常。

出現的原因:
假設A線程開始迭代遍歷list,此時的modCount爲N,expectedModCount的值也爲N,然後此時另一個線程刪除了一個元素,此時modCount的值變爲了N + 1,但是在A線程中expectedModCount已經是N了,然後當A線程進行next的時候,檢測發現modCount = N + 1,但是expectedModCount = N,此時就會出發上述的異常。

解決辦法

  • 在所有要進行修改集合數據的方法上加上synchronized關鍵字
  • 用concurrent包下的CopyOnWriteArrayList代替ArrayList

CopyOnWriteArrayList能解決的原因

寫時複製的原則,在要對集合進行修改操作的時候,先將集合進行復制一份出來,對複製出來的副本進行修改的操作,修改完了之後再將原來數組的引用指向新的數組。

三、高併發-JUC包

1、線程和進程的區別

  • 進程是操作系統進行資源分配和調度的基本單位,線程是能獨立運行的基本單位
  • 一個程序至少一個進程,一個進程至少一個線程
  • 由於進程之間是獨立的,所以每個進程有獨立的內存空間,所以在進行進程之間的切換的時候的時間開銷很大,相比之下,同一個進程中的線程之間是共享父進程的內存空間的,在進行線程切換的時間開銷相比較小
  • 進程開銷大,但是由於空間獨立,所以利於資源的管理和保護。線程雖然開銷不大,但是同一個進程的多個線程之間是可以共享內存的,所以不利於資源的管理和保護。
  • 線程是屬於進程的,一個進程內部可以創建多個線程進行併發的執行

2、單線程和多線程

單線程:一個進程中只有一個線程
多線程:一個進程中有多個線程

區別:

  • 單個線程的運行過程順序的執行一個指令流;多線程就是運行程序運行的時候併發的執行多個指令流
  • 多線程的執行在邏輯上是併發的,由於cpu在一個時間段只能執行一個線程,所以,每個線程都會佔用一段CPU的時間片,然後CPU就輪流這在多個線程之間進行切換,由於這個時間片很短,就會感覺是同時執行的
  • 單線程不存在線程的切換問題,但是多線程之間存在線程切換的問題,線程的切換是有時間開銷的,所以對於單核CPU來說,兩個線程的併發執行會比一個線程連續執行兩次的時間開銷大,執行的速度會變慢,但是用戶的響應時間減少了
  • 對於多核的CPU來說的話,多個線程會映射到多個CPU的核心上進行運行,這個是可以提高運行速度的

3、實現多線程按照順序進行運行

問題:讓十個線程按照順序打印0,1,2,3,4,5,6,7,8,9
思路:

  • 1、首先創建是個線程,每個線程需要傳入一個線程的id號,用來表示這個線程應該執行的指令是什麼(或者說應該按照什麼順序執行)
  • 2、然後按照給定的順序讓線程進行啓動
  • 3、在每個線程裏,判斷當前要執行的指令是不是這個線程id對應的指令,是的話就執行,不是的就進入等待狀體,等待應該執行這條指令的的行程執行完之後喚醒所有等待的線程
  • 4、在執行的代碼塊中,一個是執行命令(此處就爲打印輸出當前的指令),執行完之後需要喚醒所有等待的線程(notifyAll),但是分析一下,如果當前等待的線程爲0,再調用notifyAll,會出現IllegalMonitorStateException,所以,需要再維護一個全局變量來表示當前處於等待狀態的線程的數量,如果這個數量的值大於0,就調用notifyAll()方法

4、產生死鎖的原因和解決方法

分析產生死鎖的原因:

  • 系統的資源不足,導致有線程沒能獲得到相應的資源
  • 線程之間的順序不合理,存在互相阻塞的情況
  • 資源的分配不合理,比如說某個線程需要運行很長的時間卻被分配了很長的CPU時間

根據上面的原因,分析的到下面的產生死鎖的必要條件:

  • 互斥條件:如果不存在併發,這不會後死鎖,換句話說,正是由於併發的存在,爲了資源能正確的被訪問,所以很多時候限制了一個資源同時只能被一個線程使用
  • 請求保持:當一個線程A因爲要請求某個資源被阻塞了,此時這個線程A所佔用的資源也會保持不放,這就會是的那些需要請求A線程所佔有的資源的線程就會一直被阻塞
  • 不剝奪條件:對於線程已經獲得的資源,在未使用完之前不被剝奪
  • 循環等待條件:若干個線程之間形成了頭尾相連的循環等待

以上必要條件:發生死鎖必須上面是個條件都同時滿足,任何一個不滿足則不會發生死鎖(因爲只有有一個不滿足就有方法結束阻塞,釋放資源)

解決的方法:結合上面的原因來分析解決的辦法

1、死鎖的預防(保證系統不進入死鎖狀態,排除死鎖的靜態策略)

  • 讓某一些資源不允許被同時訪問
  • 合理的安排系統資源,避免線程永久佔據資源(即打破不可剝奪條件)
  • 要防止線程進入等待狀態下還佔用資源(請求保持條件)
  • 指定線程獲取鎖的順序(即獲取資源的順序):比如規定線程必須先獲取小號資源才能申請大號資源等

2、死鎖的避免(排除死鎖的動態策略)
首先,是不限制線程對資源的申請,然後,再對線程申請資源的時候加以檢查,判斷當前線程申請的這個資源是不是安全的。

銀行家算法:尋找安全序列

銀行家算法的優點:限制條件少了,資源的利用率提高了

銀行家算法的缺點:
1、要求客戶(即線程數)保持不變,這個在多道程序系統中很難實現
2、銀行家算法能保證所有的線程在有限的時間內得到系統的資源,但是對於實時要求的客戶來說卻不能滿足
3、需要尋找一個安全序列,增加系統的開銷

3、死鎖的檢測與恢復
上述的預防和避免實際上都不能很好的排除死鎖。提供一種檢測和解脫死鎖的方式:能發現死鎖並且從死鎖狀態中恢復出來。

5、sleep(n)、wait()、wait(n)

sleep(n)

睡眠n毫秒,通過Thread.sleep(n)方法調用,睡眠的時候不釋放鎖。

wait()

在wait方法處停止運行,等待notify或者中斷爲止。在進行wait()之前,必須獲得Object對象級(wait是Object的方法,又因爲Object是所有的父類,即其子類的對象即可)的鎖,所以必須和synchronized一起使用。在使用wait之後會釋放當前獲得的對象鎖。

Note:wait()是指無限制等待直到notify/notifyAll或者中斷,wait(n)在時間n毫秒之後會自動喚醒

與wait相對應的就是notify了,兩個一起構成了等待/通知機制來實現線程之間的通信:

notify()

也必須獲得Object對象級的鎖。調用成功後會隨機選擇一個處於等待狀態中的線程,使其或者該對象的對象鎖。

notifyAll()

就是對所有的線程都進行notify操作

注意:notify之後,當前線程不會立馬釋放當前獲得的對象鎖,所以被喚醒的線程也不會立馬被喚醒獲得對象鎖,而是要等到notify所在的同步方法或者同步塊執行完後纔會釋放鎖。

所以:可知wait方法會立即釋放鎖,notify不會立即釋放鎖

6、對線程池的瞭解

JUC下的ThreadPoolExecutor類

實現

繼承自AbstractExecutorService -> 實現自ExecutorService接口 -> 繼承自Executor

主要方法

execute() : 在ThreadPoolExecutor具體實現,傳入一個實現了Runnable的對象,此時會將該對象放到工作隊列中

submit() : 跟execute差不多,就是運行一個線程,但是submit使用Future能返回線程執行的結果

shutsown() :
關閉線程池之後,線程池的狀態變爲SHUTDOWN,線程池不在接收新的線程,等待運行中的線程結束

shutdownNow():
關閉線程池之後,線程池變爲STOP狀態,會不在接收新線程,同時嘗試終止其他正在運行的線程

主要參數

corePoolSize : 核不大於最大線程池大小的核心線程池的大小
maxinumPoolSze : 允許存在的最大的線程池的大小
keepAliveTime : 默認情況下,當線程池中線程的數量大於corePoolSize的時候就會起作用,此時會將超過keepAliveTime沒有任務執行的線程進行關閉,直到不大於corePoolSize
workQueue : 是一個阻塞的工作隊列(BlockingQueue)

Note :
1、當運行的線程的數量少於corePoolSize的大小,則Executor始終會優先選擇進行添加線程,而不是進行排隊
2、當運行的線程的數量等於或者多餘corePoolSize的大小,則Executor會首選將請求加入隊列,而不是創建新的線程
3、若在將請求添加到等待隊列中,發現隊列已經滿了,則會選擇新建線程,如果此時運行的線程的數量已經達到了maxinumPoolSize的大小,則會拒絕該請求。

任務緩存隊列及排隊策略

ArrayBlockingQueue :
基於數組的先進先出隊列,該隊列創建時必須指定大小

LinkedBlockingQueue :
基於鏈表的鏈表的先進先出隊列,創建時如果沒有指定大小這默認爲Integer.MAX_VALUE

SynchronousQueue :
比較特殊,不會保存提交過來的任務,而是直接新建一個線程來執行該任務。

任務拒絕的策略

AbortPolicy : 丟棄任務並拋出RejectedExecutionException,也是默認的策略

DiscardPolicy : 丟棄任務但是不拋出異常

DiscardOldestPolicy : 丟棄當前等待隊列最前面的任務,然後再嘗試重新執行這個任務

動態設置線程池的容量參數

setCorePoolSize();
setMaxinumPoolSize();

總結 ThreadPoolExecutor的執行過程

1、首先將實現了Runnable接口的任務通過execute()進行提交,也可以使用submit()方法提交。
2、如果當前線程池中的線程的數量小於corePoolSize,則會首選創建新的線程來執行所提交的任務
3、如果當前線程池中的線程的數量大於或者等於corePoolSize,則會首選將提交的任務放到任務隊列中進行排隊等候執行(上述有隊列的排隊策略)
4、如果無法加入任務隊列或者隊列已經滿了,則將新建一個新的線程來執行執行,如果此時線程池中的線程的數量已經達到最大值maxPoolSize,此時會拒絕這個任務(上述有拒絕的策略)

7、ThreadLocal (線程局部變量)

ThreadLocal維護一個變量的時候,爲每一個使用該變量的線程維護了一個獨立的變量副本,這樣每一個線程就可以獨立的改變其副本中的變量,這樣就不會影響到其他線程的變量副本。

特性:線程之間的變量是相互隔離的。(在ThreadLocal中,每次獲取變量的值的時候,會出現獲取第一個值的時候是返回的NULL,此時可以通過繼承ThreadLocal類,並重寫方法initialValue(是protected的訪問權限)來返回初值)

ThreadLocal的和實現原理

1、每個線程的內部都會維護一個ThreadLocalMap的對象,裏面就包含了若干的Entry(即key-value鍵值對),每個entry的key就是當前線程中所使用的那個TreadLocal實例,其value就是當前線程的屬性
2、所以每個Entry就當前對象所使用的ThreadLocal實例和當前對象所特有的屬性的一個對應關係
3、ThreadLocalMap中對key的引用是弱引用,對value的引用是強引用

以set一個值到ThreadLocal中爲例的過程

1、首先通過Thread.getCurrentThread()來獲取當前使用這個ThreadLocal的線程
2、由於在Thread中維護了一個ThreadLocal.ThreadLocalMap threadLocals,所以可以用1中獲取到的線程來獲取這個線程的threadLocals屬性
3、判斷,如果當前的threadLocals爲null表示還沒有初始化,這創建這個ThreadLocalMap對象,如果不爲Null,則通過threadLocals.set(key,value)的形式賦值,這個key就是當前線程所使用的ThreadLocal的實例,value就是當前線程的屬性值

其他的知識點

1、ThreadLocal的的值是存放在哪兒的?

分析:首先,知道棧上存放的是線程私有的變量,每個線程都有一段自己私有的線程空間;而堆內存的變量是對所有線程都可見的,也就是堆內存中的變量是可以被所有其他線程訪問的。

ThreadLocal的值是線程私有的,那這個值是存放在棧中麼?
不是。ThreadLocal也是被其創建的類所持有,所以也是在堆上,但是其實現的方式達到了線程私有的目的,比如:每個線程自己本身是有一個ThreadLocalsMap變量的,這個是每個線程所私有的。這個Map中存放的就是以一個ThreadLocal實例爲key,相應的屬性值爲value的Entry對象。在每次通過ThreadLocal進行變量值的設置和獲取變量值的時候,首先是拿到當前是哪一個線程,然後得到這個線程裏面的私有變量threadLocalsMap,然後以當前這個ThreadLocal實例作爲key來獲取當前這個線程中的變量的值。

2、ThreadLocal是否會導致內存泄露

分析:由於有線程池的存在,所以,在一些線程執行完任務之後,爲了提高線程的複用率,執行完了任務的線程會依然還在線程池中。此時當這個線程又去執行其他的任務的時候,該線程之前所持有的由ThreadLocal所維持的一些變量依然還存在於這個線程中,所以此時會導致線程泄露。

但是,實際上是不會泄露!
原因:通過源碼分析,看到在ThreadLocal中的ThreadLocalMap中的每一條記錄Entry的key的引用是弱引用,其值是強引用class Entry extends WeakReference

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

可以看出來,子進程的inheritableThreadLocals的屬性直接就是來自其父線程的。

所以,此時使用IheritableThreadLocal可以實現在子線程中訪問父線程的ThreadLocal變量。

8、sychronized關鍵字

底層實現:
1、底層維護了一個monitorenter監視器,相當於一個信號量,當有一個線程進入的時候monitorenter就會加一,當線程出去的時候就會減一。
2、所以,在線程想要進入一段同步塊或者同步方法的時候,先判斷當前的monitorenter是否爲0,是則可以訪問,不是則不可以訪問

9、volatile 關鍵字

兩個作用:(參考書籍《深入理解Java虛擬機》 12.3.3)
1、可見性
2、禁止JVM進行指令重排

10、Atomic+<基本數據類型>

如:AtomicInteger、AtomicLong等

作用:能以原子的方式讓基本數據類型就行自增自減等操作

四、Java中的IO

幾種IO模型,最開始的BIO,到NIO,再到AIO

BIO

又分爲兩種:傳統的BIO模型、僞異步IO模型

傳統的BIO模型

由Socket進行連接的創立,雙方進行同步阻塞通信,通常由一個獨立的Acceptor線程負責監聽Client端的連接,每接收到一個client的連接就會創建一個新的線程進行處理。

問題:當多個客戶端連接之後,創建了多個線程,系統的性能就會急劇下降。

僞異步IO

爲了改進1:1的線程模型,改用將創建的線程交由線程池來進行管理,實現M個線程處理N個客戶端的模型,但是實際上實現的時候還是BIO的方式。

實現的方式,就是在BIO的模型的基礎上,將處理消息的線程交由ExecutorService來處理即可:

Socket socket = server.accept();
executorService.execute(new Serverhandler(socket));
//其中ServerHandler就是處理連接的線程

問題:當併發量比較大(大於線程池最大線程數量)時,線程的讀取速度有比較慢的時候,其他的客戶端連接就會一直被阻塞。

NIO

官方稱爲New IO,在民間也稱爲Non-block IO即非阻塞IO。

提供了相對於BIO的兩種套接字:ServerChannel和ServerSocketChannel,這兩種套接字都支持阻塞和非阻塞兩種模式。

Buffer

實際上是一個數組,在NIO的庫中,所有數據都是用緩衝區處理的,比如讀數據是從緩衝區中讀,寫數據是寫到緩衝區。

具體的有:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer,都實現了Buffer的接口。

Channel

想水管一樣,相比於流來說,支持雙向操作,是全雙工的,可以寫、讀、同時讀寫。

主要分爲:
SelectableChannel:用於網絡讀寫
FileChannel:用於文件的讀寫(只能在阻塞模式下進行)

ServerSocketChannel和SocketChannel都是SelectableChannel的子類。

多路複用器Selector

用來管理Channel,Selector會不斷的輪詢註冊在其上的Channel,如果某個Channel上面發生了讀或者寫事件,那這個Channel就算處於就緒的狀態,就會唄Selector輪詢出來,然後通過SelectionKey可以獲取當前就緒的Channel的集合,然後進行後面的操作。

AIO

發起非阻塞的IO操作,操作完了之後進行通知。

完成通知的任務:Future和Callback的方式

Future

即提交的每一個IO操作都返回一個Future,然後可以對返回的Future進行檢查:Future result = channel.read(buf),然後使用result.get()來返回讀取到的數據的字節數,並進行相關的判斷。

Callback

爲每一次IO操作指定一個CompletionHandler,在這個接口裏面實現兩個方法:
void completed(V result, A attachment):當操作完成之後被調用,result表示操作結果,attachment表示提交操作請求時的參數

void failed(Throwable exc, A attachment):當操作失敗調用,exc 參數表示失敗原因。attachment 參數同上。

五、Java 8相關

HashMap 增加了紅黑樹的實現

紅黑樹:

特點

1、根節點是黑色
2、所有的葉子結點是黑色(這個葉子結點是指最下面的空節點)
3、如果當前節點是紅節點,則它的孩子節點是黑色的節點
4、從一個節點出發,到該節點的任意子孫路徑上包含的相同數目的黑節點的數量
5、所有的節點不是黑色就是紅色

插入數據

每一次新插入的數據都是紅色的,因爲插入數據之後會導致樹不滿足了紅黑樹的特點,於是就需要進行樹的調整。

case1:若當前節點的父節點是紅色,而且當前節點的叔叔節點也是紅色

操作:
1、先將當前節點的父節點和叔叔節點都設置爲黑色
2、然後將祖父節點設置爲紅色
3、將當前節點移到祖父節點上,繼續上面的判斷
4、直到根節點,如果處理完之後根節點是紅色,則將根節點設置爲黑色,否則結束

case2:若當前節點的父親節點是紅色,叔叔節點爲黑色,且祖父節點、父節點、當前節點處在同一條斜線上

操作:
1、將當前節點的父節點當做當前節點
2、以當前節點(即新插入節點的父節點)進行右旋轉,並且將當前節點(新插入節點的之前的父節點)和當前節點的和旋轉之前的父節點的顏色互換一下

case3:若當前節點的父親節點是紅色,叔叔節點爲黑色,且祖父節點、父節點、當前節點不處在同一條斜線上

操作:
1、先將新插入的節點進行左旋轉
2、然後根據case2的樣子處理

(有待補充)

接口中可以實現方法default

比如Map集合的接口中就有default方法

新增加了Lambda表達式

函數式編程

Exmaples:

(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

函數式接口

是隻包含一個抽象方法聲明的接口。每個Lambda表達式都可以隱式的賦值給函數式接口,如:

Runnable r = () -> System.out.println("hello world");

Consumer<Integer>  c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

可以使用@FunctionalInterface註釋來表示一個接口是函數式接口,這樣聲明一個接口之後,該接口之中就只能有一個抽象方法了。

新增加了java.time包

1、包含了所有的關於日期、時間、時區、持續時間和時鐘操作的類
2、這些類都是不可變的線程安全的

增加了很多時間的基本操作。

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