一蜂挪、重要數(shù)據(jù)結(jié)構(gòu)和JVM的改動
1.HashMap的改動
HashMap維護了一個Entry數(shù)組,put(K key,V value)元素到HashMap中時通過key的hash碼計算其在數(shù)組中的索引位置,若索引位置上已有元素形成哈希碰撞.
jdk1.8以前:哈希碰撞之后,在碰撞位置將會形成一個鏈表,新加入的元素將放置于表頭位置(明顯缺點:當碰撞元素過多,鏈表過長,遍歷鏈表查找元素的速度就較慢)
jdk1.7關鍵代碼:
public V put(K key, V value) {
? ? if (table == EMPTY_TABLE) {
? ? ? ? inflateTable(threshold);
? ? }
? ? //若key為null,則特殊處理一下(放到數(shù)組索引為0的位置)
? ? if (key == null)
? ? ? ? return putForNullKey(value);
? ? //計算key在數(shù)組中對應的索引值
? ? int hash = hash(key);
? ? int i = indexFor(hash, table.length);
? ? //遍歷索引位置上的鏈表,若找到匹配key,則將新值賦給該節(jié)點,并返回舊值
? ? for (Entry e = table[i]; e != null; e = e.next) {
? ? ? ? Object k;
? ? ? ? if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
? ? ? ? ? ? ? ?V oldValue = e.value;
? ? ? ? ? ? ? ?e.value = value;
? ? ? ? ? ? ? ?e.recordAccess(this);
? ? ? ? ? ? ? ?return oldValue;
? ? ? ? }
? ? }
? ? //若沒有匹配的已存在的key,則在索引位置對應的鏈表上新增節(jié)點,next指向原鏈表的表頭元素
? ? modCount++;
? ? addEntry(hash, key, value, i);
? ? return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
? ? if ((size >= threshold) && (null != table[bucketIndex])) {
? ? ? ? resize(2 * table.length);
? ? ? ? hash = (null != key) ? hash(key) : 0;
????????bucketIndex = indexFor(hash, table.length);
????}
????createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
????Entry e = table[bucketIndex];
????table[bucketIndex] = new Entry<>(hash, key, value, e);
????size++;
}
jdk1.8:(a)新加入的元素將置于鏈表的尾部;(b)當鏈表元素達到8個時,此鏈表將轉(zhuǎn)換為紅黑樹 ,優(yōu)點:HashMap的查詢效率在jdk1.8中得到了大大的提升
jdk1.8關鍵代碼:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
????Node[] tab; Node p; int n, i;
????//若map中的tab數(shù)組為空,則先初始化數(shù)組
????if ((tab = table) == null || (n = tab.length) == 0)
????????n = (tab = resize()).length;
????//若tab數(shù)組中,當前元素hash值對應的索引位置上為null,則直接將元素置于該位置
????if ((p = tab[i = (n - 1) & hash]) == null)
????????tab[i] = newNode(hash, key, value, null);
????else {
????????Node e; K k;
????????//若元素與鏈表第一個元素的hash值和key值均相等,則直接將value賦給該元素
????????if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
????????????e = p;
????????????//若當前位置是一個樹節(jié)點(說明已經(jīng)轉(zhuǎn)為紅黑樹了),則挨個與樹節(jié)點比較,若hash和key與某個節(jié)點相等則,則將value賦給該節(jié)點,否則新增一個樹節(jié)點
? ? ? ? else if (p instanceof TreeNode)
????????????e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
????????else {
????????????//挨個遍歷鏈表中元素,若hash和key匹配,則找到匹配的元素
????????????for (int binCount = 0; ; ++binCount) {
????????????????if ((e = p.next) == null) {
????????????????????//若遍歷到表尾仍然沒有找到,則在尾部新增一個元素
????????????????????p.next = newNode(hash, key, value, null);
????????????????????//若元素個數(shù)達到8個,則將鏈表轉(zhuǎn)成紅黑樹
? ? ? ? ? ? ? ? ? ? if (binCount >= TREEIFY_THRESHOLD - 1)?
? ? ? ? ? ? ? ? ? ? ? ? treeifyBin(tab, hash);
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? p = e;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? if (e != null) {?
? ? ? ? ? ? ? ? V oldValue = e.value;
? ? ? ? ? ? ? ? if (!onlyIfAbsent || oldValue == null)
? ? ? ? ? ? ? ? ? ? e.value = value;
? ? ? ? ? ? ? ? ? ? afterNodeAccess(e);
? ? ? ? ? ? ? ? ? ? return oldValue;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
????????++modCount;
????????if (++size > threshold)
????????????resize();
????????afterNodeInsertion(evict);
????????return null;
}
2.JVM運行時數(shù)據(jù)區(qū)
JVM運行時數(shù)據(jù)區(qū)去掉永久代(Permanent Generation ),改為元空間(MetaSpace)。
jdk1.8之前的JVM運行時數(shù)據(jù)區(qū):
方法區(qū)即永久代(Method Area): 所有線程共享讼呢,用于存儲JVM加載的類信息、常量谦炬、靜態(tài)變量悦屏、JIT編譯器編譯后的代碼等數(shù)據(jù)。它只是JVM規(guī)范中定義的一個概念,是堆的一個邏輯部分础爬,為了與普通堆區(qū)分開來散劫,有一個別名叫做Non-Heap(非堆)。
jdk1.8及以后幕帆,永久代被移除,取而代之的是元空間赖条,它直接從操作系統(tǒng)分配內(nèi)存失乾,獨立且可以自由擴展,最大可分配空間就是系統(tǒng)可用空間纬乍,因此不會遇到PermGen的內(nèi)存溢出錯誤(java.lang.OutOfMemoryError: PermGen space)碱茁。在JVM參數(shù)方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原來的-XX:PermSize和-XX:MaxPermSize仿贬。
二纽竣、關于接口的變動
1、default關鍵字
Jdk1.8新增了一個關鍵字default,用于修飾接口中的非抽象方法,只能通過接口實現(xiàn)類的對象進行調(diào)用
2茧泪、static方法
Jdk1.8允許在接口中定義static方法,調(diào)用方式必須為:接口名.方法名()
3蜓氨、函數(shù)式接口
Jdk1.8提出了一個”函數(shù)式接口”的概念:只有一個抽象方法的接口稱為函數(shù)式接口,可使用@FunctionalInterface注解進行檢查(此注解只是用來檢查是否是函數(shù)式接口,并不是說,加了此注解的接口是函數(shù)式接口,如果加了此注解,接口中有多個抽象方法將會編譯報錯)。常見的函數(shù)式接口:Runnable队伟,Comparator等
三穴吹、Optional容器類
Optional 類(java.util.Optional) 是一個容器類,代表一個值存在或不存在嗜侮,原來用 null 表示一個值不存在港令,現(xiàn)在 Optional 可以更好的表達這個概念。并且可以避免空指針異常锈颗。
常用方法:
Optional.of(T t) : 創(chuàng)建一個 Optional 實例
Optional.empty() : 創(chuàng)建一個空的 Optional 實例
Optional.ofNullable(T t):若 t 不為 null,創(chuàng)建 Optional 實例,否則創(chuàng)建空實例
isPresent() : 判斷是否包含值
orElse(T t) : 如果調(diào)用對象包含值顷霹,返回該值,否則返回t
orElseGet(Supplier s) :如果調(diào)用對象包含值击吱,返回該值淋淀,否則返回 s 獲取的值
map(Function f): 如果有值對其處理,并返回處理后的Optional姨拥,否則返回 Optional.empty()
flatMap(Function mapper):與 map 類似绅喉,要求返回值必須是Optional
四、Lambda表達式
Lambda表達式是對函數(shù)式接口中抽象方法的實現(xiàn)的簡寫方式.
1.? ? 分析Lambda表達式的結(jié)構(gòu)及常規(guī)寫法
格式:? ()->? {}
操作符“->”被稱為 Lambda 操作符或箭頭操作符叫乌。它將 Lambda 分為兩個部分:
左側(cè): 指定了 Lambda 表達式需要的所有參數(shù)
右側(cè): 指定了 Lambda 體柴罐,即 Lambda 表達式要執(zhí)行的功能。
語法格式一:無參數(shù)憨奸,無返回值
() -> System.out.println("Hello Lambda!");
語法格式二:有一個參數(shù)革屠,并且無返回值
(x) -> System.out.println(x)
語法格式三:有兩個以上的參數(shù),有返回值,并且 Lambda 體中有多條語句
Comparator com = (x, y) -> {
System.out.println("函數(shù)式接口");
return Integer.compare(x, y);
};
語法格式四:
(1).Lambda 表達式的參數(shù)列表的數(shù)據(jù)類型可以省略不寫似芝,jdk1.7引入的“類型推斷”機制在jdk1.8得到了強化(注意:若不寫,都不寫)
(2).若只有一個參數(shù)那婉,小括號可以省略不寫
(3).若 Lambda 體中只有一條語句, return 和 大括號都可以省略不寫
2.? ? jdk1.8內(nèi)置的一些函數(shù)式接口
Lambda是基于函數(shù)式接口的,難道使用lambda表達式還必須要寫一個函數(shù)式接口?當然不是! jdk開發(fā)人員想到了這個問題.
Jdk提供了一些函數(shù)式接口:四大核心接口,N個擴展接口位于java.util.function包下.
四大核心函數(shù)式接口:
(1)? ? 消費型接口Comsumer:有一個參數(shù)無返回值的抽象方法,參數(shù)類型T
(2)? ? 供給型接口Supplier:無參數(shù)有返回值的抽象方法,返回值類型T
(3)? ? 函數(shù)型接口Function:一個參數(shù)有返回值的抽象方法,參數(shù)類型T,返回值類型R
(4)? ? 斷言型接口Predicate:一個參數(shù)有返回值的抽象方法,參數(shù)類型T,返回值類型boolean
五党瓮、方法引用和構(gòu)造器引用
1.方法引用
當要傳遞給Lambda體的操作详炬,已經(jīng)有實現(xiàn)的方法了,可以使用方法引用D椤(實現(xiàn)抽象方法的參數(shù)列表呛谜,必須與方法引用方法的參數(shù)列表保持一致!)
方法引用:使用操作符 “::” 將方法名和對象或類的名字分隔開來
例如:
Lambda表達式:? x->System.out.println(x);
等同于:
方法引用(實例名::實例方法名):? ? System.out::println;
Lambda表達式:? t -> Integer.parseInt(t);
等同于
方法引用(類名::類方法名):? Integer::parseInt;
Lambda表達式:? (x, y) -> x.equals(y);
等同于
方法引用(類名::類方法名):? String::equals;
方法引用的判斷規(guī)則:
(1)方法引用所引用的方法的參數(shù)列表與返回值類型枪萄,需要與函數(shù)式接口中抽象方法的參數(shù)列表和返回值類型保持一致隐岛!
(2)若Lambda 的參數(shù)列表的第一個參數(shù),是實例方法的調(diào)用者瓷翻,第二個參數(shù)(或無參)是實例方法的參數(shù)時聚凹,格式: ClassName::MethodName
2.構(gòu)造器引用(例子: MyTestMethodRef.java)
構(gòu)造器的參數(shù)列表,需要與函數(shù)式接口中參數(shù)列表保持一致齐帚!
格式: 類名::new
六妒牙、Stream API
1、Stream簡介
Stream 是 Java8 中處理集合的關鍵抽象概念童谒,它可以指定你希望對集合進行的操作单旁,可以執(zhí)行非常復雜的查找、過濾和映射數(shù)據(jù)等操作饥伊。使用Stream API 對集合數(shù)據(jù)進行操作象浑,就類似于使用 SQL 執(zhí)行數(shù)據(jù)庫查詢。也可以使用 Stream API 來并行執(zhí)行操作琅豆。簡而言之愉豺,Stream API 提供了一種高效且易于使用的處理數(shù)據(jù)的方式。
注意:
(1)Stream 自己不會存儲元素茫因。
(2)Stream 不會改變源對象蚪拦。相反,他們會返回一個持有結(jié)果的新Stream冻押。
(3)Stream 操作是延遲執(zhí)行的驰贷。這意味著他們會等到需要結(jié)果的時候才執(zhí)行。
2洛巢、創(chuàng)建stream: 通過一個數(shù)據(jù)源(如: 集合括袒、數(shù)組), 獲取一個流
(1)? ? Java8 中的 Collection 接口被擴展稿茉,提供了兩個獲取流的方法:
default? Stream? stream() : 返回一個順序流
default? Stream? parallelStream() : 返回一個并行流
(2)? ? Java8 中的 Arrays 的靜態(tài)方法 stream() 可以獲取數(shù)組流:
public? static? ? Stream? stream(T[] array): 返回一個流
public? static? IntStream? stream(int[] array)
public? static? LongStream? stream(long[] array)
public? static? DoubleStream? stream(double[] array)
(3)? ? 可以使用靜態(tài)方法 Stream.of(), 通過顯示值創(chuàng)建一個流锹锰。它可以接收任意數(shù)量的參數(shù)芥炭。
public? static? Stream? of(T... values) : 返回一個流
3、中間操作: 一個中間操作鏈恃慧,對數(shù)據(jù)源的數(shù)據(jù)進行處理
多個中間操作可以連接起來形成一個流水線园蝠,除非流水線上觸發(fā)終止操作,否則中間操作不會執(zhí)行任何的處理痢士!而在終止操作時一次性全部處理彪薛,稱為“惰性求值” 。(例子: MyTestStream3.test1()).
(1)? ? 篩選與切片
filter(Predicate? p): 過濾,從流中過濾出符合條件的元素,接收一個斷言型Lambda
distinct(): 去重復,根據(jù)流中元素的hashCode()和equals()方法去除流中的重復元素
limit(long? maxSize): 截斷流,使其元素不超過給定數(shù)量
skip(long? n): 跳過元素怠蹂,返回一個扔掉了前 n 個元素的流陪汽。若流中元素不足 n 個,則返回一個空流褥蚯。與 limit(n) 互補
(2)? ? 映射
map(Function f): 接收一個函數(shù)作為參數(shù),該函數(shù)會被應用到每個元素上况增,并將其映射成一個新的元素赞庶。
(3)? ? 排序
sorted(): 產(chǎn)生一個新流,其中按自然順序排序
sorted(Comparator? comp): 產(chǎn)生一個新流澳骤,其中按比較器順序排序
4歧强、終止操作: 一個終止操作,執(zhí)行中間操作鏈为肮,并產(chǎn)生結(jié)果
(1)查找與匹配
allMatch(Predicate p): 檢查是否匹配所有元素
anyMatch(Predicate p): 檢查是否至少匹配一個元素
noneMatch(Predicate p): 檢查是否沒有匹配所有元素
findFirst(): 返回第一個元素
findAny(): 返回流中的任意元素
count(): 返回流中的元素總數(shù)
max(Comparator? c): 返回流中最大值
min(Comparator? c): 返回流中最小值
forEach(Consumer? c): 內(nèi)部迭代
(2)規(guī)約
reduce(T? iden,BinaryOperator? b): 可以將流中元素反復結(jié)合起來摊册,得到一個值。返回 T
reduce(BinaryOperator? b): 可以將流中元素反復結(jié)合起來颊艳,得到一個值茅特。返回 Optional
(3)收集
collect(Collector? c): 將流轉(zhuǎn)換為其他形式。接收一個 Collector接口的實現(xiàn)棋枕,用于給Stream中元素做匯總的方法, Collectors提供了豐富的靜態(tài)方法供我們使用
七白修、新的時間日期API(java.time.*包)?
Jdk1.8之前的時間日期API的缺陷:
1.Java的日期/時間類的定義不一致,在java.util和java.sql的包中都有日期類重斑,此外用于格式化和解析的類在java.text包中定義兵睛。
2.java.util.Date同時包含日期和時間,而java.sql.Date僅包含日期窥浪,將其納入java.sql包并不合理祖很。另外這兩個類都有相同的名字,這本身就是一個非常糟糕的設計漾脂。
3.所有的日期類都是可變的假颇,因此他們都不是線程安全的,這是Java日期類最大的問題之一符相。
4.日期類并不提供國際化拆融,沒有時區(qū)支持蠢琳,因此Java引入了java.util.Calendar和java.util.TimeZone類,但他們同樣存在上述所有的問題镜豹。
5.計算兩個日期時間之間的間隔比較麻煩,因此出現(xiàn)了一個joda-time.jar.
Jdk1.8新的時間日期API解決了上述問題
1. 使用 LocalDate傲须、 LocalTime、 LocalDateTime
LocalDate趟脂、 LocalTime泰讽、 LocalDateTime 類的實例是不可變的對象,分別表示使用ISO-8601日歷系統(tǒng)(ISO-8601日歷系統(tǒng)是國際標準化組織制定的現(xiàn)代公民的日期和時間的表示法)的日期昔期、時間已卸、日期和時間。
2. Duration 和 Period
(1) Duration:用于計算兩個“時間”間隔
(2) Period:用于計算兩個“日期”間隔
3. 解析與格式化
java.time.format.DateTimeFormatter 類
4. 按照指定時區(qū)獲取時間
> PS:JDK1.8的新特性還有很多,這里只是簡要介紹了一些我們平時經(jīng)常接觸到的.還有其他的需要自己去深入學習一下!