本文摘抄自網絡,做爲我的學習筆記java
知識點:算法
map 新原理 api
在 jdk1.8 中對 hashMap 等 map 集合的數據結構進行了優化。原來的 hashMap 採用的數據結構是哈希表(數組+鏈表),hashMap 默認大小是 16,一個 0-15 索引的數組。怎麼往裏面存儲元素呢?首先調用元素的 hashcode 方法,計算出 哈希碼值,通過 哈希算法算成數組的索引值,若是對應的索引處沒有元素,直接存放,若是有對象在,那麼比較他們的 equals 方法,若是內容同樣,後一個 value 會將前一個 value 的值覆蓋,若是不同,在 1.7 的時候,後加的放在前面造成一個鏈表,這就形成了「碰撞」,在某些狀況下,若是鏈表無限下去,,那麼效率極低,碰撞是避免不了的。數組
加載因子:0.75,數組擴容,達到總容量的 75%,就進行擴容,可是沒法避免碰撞的狀況發生。安全
在 1.8 以後,在 數組 + 鏈表 + 紅黑樹 來實現 hashmap,當碰撞的元素大於 8 & 總容量大於 64,會有紅黑樹的引入。除了添加以外,效率都比鏈表高,1.8 以後鏈表新進元素加到末尾。網絡
ConcurrentHashMap(鎖分段機制),concurrentLevel ,jdk1.8 採用 CAS 算法(無鎖算法,再也不使用鎖分段)。數據結構
數組 + 鏈表 中也引入了紅黑樹的使用。多線程
Lambda 表達式app
Lambda 表達式本質上是一段匿名內部類,也能夠是一段傳遞的代碼框架
Comparartor<Integer> cpt = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> set = new TreeSet<>(cpt);
Lambda 表達式的語法總結:() -> ();
注:當一個接口中存在多個抽象方法時,若是使用 lambda 表達式,並不能智能地匹配對應的抽象方法,所以引入了函數式接口的概念
函數式接口
函數式接口的提出是爲了給 Lambda 表達式的使用提供更好的支持。
什麼是函數式接口?
簡單來講就是隻定義了一個抽象方法的接口(Object 類的 public 方法除外),就是函數式接口,而且還提供了註解:@FunctionalInterface
常見的四大函數式接口:
// 消費型
public static void changeStr(String str, Consumer<String> con){
con.accept(str);
}
// 調用
changeStr("hello", (str) -> System.out.println(str));
說明:這裏方法的參數是字符串和 Consumer,Consumer 是接口,調用時須要實現這個接口,即實現了消費這個操做。
// 供給型
public static String getValue(Supplier<String> sup){ return sup.get(); }
// 調用
System.out.println(getValue(() -> "1111111"));
說明:這裏同上,返回的是個字符串,按理有個 return,可是 lambda 表達式說,按照個人規範,能夠省略這個 return。
// 函數式接口
public static Long changeNum(Long num, Function<Long, Long> function){ return function.apply(num); }
// 調用
Long result = changeNum(100L, (x) -> x+200L);
// 斷言型 public static boolean changeBoolean(String str, Predicate<String> pre){ return pre.test(str); }
// 調用
boolean reboolean = changeBoolean("hello", (str)->str.length() > 5);
總結:函數式接口的提出是爲了讓咱們更加方便的使用 lambda 表達式,不須要本身再手動建立一個函數式接口,直接拿來用就行了。
方法引用
若 lambda 體中的內容有方法已經實現了,那麼可使用 「方法引用」。也能夠理解爲方法引用是 lambda 表達式的另一種表現形式,而且其語法比 lambda 表達式更加簡單。
1)方法引用
三種表現形式:
1. 對象 :: 實例方法名
2. 類 :: 靜態方法名
3. 類 :: 實例方法名(lambda 參數列表中第一個參數是實例方法的調用者,第二個參數是實例方法的參數時可用)
// 方法引用 /** * 1. lambda 體中調用方法的參數列表與返回值類型, * 要與函數式接口中抽象方法的參數列表和返回值保持一致 * 2. 若 lambda 參數列表中的第一個參數是實例方法的調用者, * 而第二個參數是實例方法的參數時,可使用 ClassName::method */ Consumer<Integer> consumer = (x) -> System.out.println("x"); consumer.accept(100); // 方法引用 - 類名 :: 靜態方法名 BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y); BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare; Integer resBi = biFun2.apply(1200, 200); System.out.println("resBi = " + resBi); // 方法引用 - 類名 :: 實例方法名 BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2); BiFunction<String, String, Boolean> fun2 = String::equals; Boolean resB2 = fun2.apply("hello", "hello"); System.out.println("resB2 = " + resB2);
2)構造器引用
格式:ClassName :: new
Supplier<Product> sup = () -> new Product(); System.out.println("sup.get() = " + sup.get()); Supplier<Product> sup2 = Product::new; System.out.println("sup2.get() = " + sup2.get());
// 一個參數的狀況 Function<String, Product> bifun2 = Product::new; BiFunction<Integer, String, Product> bifun = (x, y) -> new Product(y, x);
3)數組引用
格式:Type[] :: new
// 數組引用 Function<Integer, String[]> func = (x) -> new String[x]; Function<Integer, String[]> func2 = String[] :: new ; String[] strArray = func2.apply(10); Arrays.stream(strArray).forEach(System.out::println);
Strean API
Stream 操做的三個步驟
stream 的建立:
// Stream 操做 // 1. 經過 Collection 系列集合提供的 stream() 或者 paralleStream() List<String> slist = new ArrayList<>(); Stream<String> stream1 = slist.parallelStream(); Stream<String> stream11 = slist.stream(); // 2. 經過 Arrays 的靜態方法 stream() 獲取數組流 String[] strArr = new String[10]; Stream<String> stream2 = Arrays.stream(strArr); // 3. 經過 Stream 類的靜態方法 of Stream<String> stream3 = Stream.of("aa", "nn", "cc"); // 4. 建立無限流 // 迭代 Stream<Integer> stream4 = Stream.iterate(0, (x)->x+2); // 生成 Stream.generate(()->Math.random());
stream 的中間操做(Intermediate):一個流能夠後面跟隨 0 個或者多個 intermediate 操做。其目的主要是打開流,作出某種程度的數據映射 / 過濾,而後返回一個新的流,交給下一個操做者使用。這類操做都是惰性化的(lazy),就是說,僅僅調用到這類方法,並無真正開始流的遍歷。
// 篩選、過濾、去重 list.stream() .filter(e -> e.getPrice()>20) .limit(3) .skip(1) .forEach(System.out::println); System.out.println("---------------------"); // 經過 map 映射,生成新的流 list.stream() .map(e -> {if(e.getPrice()>20){return e;}else{return null;}}) .forEach(System.out::println); System.out.println("++++++++++++++++++++++"); // 作排序 list.stream() .sorted((p111, p222) -> p111.getPrice().compareTo(p222.getPrice()) ) .forEach(System.out::println);
stream 的終止操做(Terminal):一個流只能有一個 terminal 操做,當這個操做執行後,流就被用光了,沒法再被操做。因此這一定是流的最後一個操做。Terminal 操做的執行,纔會真正的開始流的遍歷,而且會生成一個結果。
/** * 查找和匹配 * allMatch 檢查是否匹配全部元素 * anyMatch 檢查是否至少匹配一個元素 * noneMatch 檢查是否沒有匹配全部的元素 * findFirst 返回第一個元素 * findAny 返回當前流中的任意元素 * count 返回流中元素的總個數 * max 返回流中最大值 * min 返回流中最小值 */ boolean allMatchRes = list.stream().allMatch((e) -> e.getPrice()>30); System.out.println("allMatchRes = " + allMatchRes); boolean anyMatchRes = list.stream().anyMatch(e -> e.getPrice()>30); System.out.println("anyMatchRes = " + anyMatchRes); boolean noneMatchRes = list.stream().noneMatch(e ->e.getPrice() > 50); System.out.println("noneMatchRes = " + noneMatchRes); Optional<Product> findFirstRes = list.stream().findFirst(); System.out.println("findFirstRes = " + findFirstRes.get());
還有兩個強大的終止操做 reduce 和 collect 。
reduce 操做是將流中元素反覆結合起來,獲得一個值
/** * reduce : 規約操做 */ List<Integer> numList = Arrays.asList(1,2,3,4,5,6,7,8,9,10); Integer count = numList.stream().reduce(0, (x, y) -> x+y); System.out.println("count = " + count); Optional<Integer> reduce = list.stream()
.map(Product::getPrice)
.reduce(Integer::sum); System.out.println("reduce = " + reduce);
collect 操做:collect 操做將流轉換成其餘形式,接收一個 Collection 接口的實現,用於給 Stream 中元素作彙總的方法
/** * collect 收集操做 */ List priceList = list.stream() .map(Product :: getPrice) .collect(Collectors.toList()); priceList.stream().forEach(System.out :: println);
在對於一個 Stream 進行屢次轉換操做(Intermediate操做),每次都對 Stream 的每一個元素進行轉換,並且是執行屢次,這樣的時間複雜度就是 N 個 for 循環裏把全部操做都作完的總和嗎?其實不是這樣的,轉換操做都是 lazy 的,多個轉換操做只會在 Terminal 操做的時候融合起來,一次循環完成。咱們能夠這樣理解:Stream 裏有個操做函數的集合,每次轉換操做就是把轉換函數放入集合中,在 Terminal 操做的時候循環 Stream 對應的集合,而後對每一個元素執行全部的函數。so easy !
並行流和串行流
在 jdk1.8 新的 stream 包中,針對集合的操做提供了並行操做流和串行操做流。並行流就是把內容切割成多個數據塊,而且使用多個線程分別處理每一個數據塊的內容。Stream api 中聲明能夠經過 parallel() 與 sequential() 方法在並行流和串行流之間進行切換。
jdk1.8 並行流使用的是 fork / join 框架進行並行操做。
ForkJoin 框架
Fork / Join 框架:就是在必要的狀況下,將一個大的任務,進行拆分(fork)成若干小任務(拆分到不能拆分時),再將一個個的小任務運算結果進行 join 彙總。
關鍵字:遞歸分合,分而治之
減小了線程的等待時間,提升了性能。
Optional 容器
使用 Optional 容器能夠快速的定位 NPE,而且在必定程度上能夠減小對參數非空檢驗的代碼量。
接口中能夠定義默認實現方法和靜態方法
在接口中可使用 default 和 static 關鍵字來修飾接口中的普通方法。
在 jdk1.8 中不少接口會新增方法,爲了保證 1.8 向下兼容, 1.7 版本中的接口實現類不用每一個都從新實現新添加的接口方法,引入了 default 默認實現,static 的用法是直接用接口調用方法便可。當一個類繼承父類又實現接口時,若後二者方法名相同,則優先繼承父類中的同名方法,即「類優先」,若是是實現兩個同名方法的接口,則要求實現類必須手動聲明默認實現哪一個接口中的方法。
新的日期 API LocalDate | LocalTime | LocalDateTime
新的日期 API 都是不可變的,更適用於多線程的使用環境中
LocalDateTime dateTime = LocalDateTime.now(); System.out.println("dateTime = " + dateTime); System.out.println(dateTime.getYear()); System.out.println(dateTime.getMonthValue()); System.out.println(dateTime.getDayOfMonth()); System.out.println(dateTime.getHour()); System.out.println(dateTime.getMinute()); System.out.println(dateTime.getSecond()); System.out.println(dateTime.getNano()); // 納秒 LocalDateTime dateTime2 = LocalDateTime.of(2020, 3, 31, 20, 9); System.out.println("dateTime2 = " + dateTime2); LocalDateTime dateTime3 = dateTime2.plusHours(3); System.out.println("dateTime3 = " + dateTime3); Instant ins = Instant.now(); System.out.println("ins = " + ins);
... 未完不續了 ...
表示日期的 LocalDate
表示時間的 LocalTime
表示日期時間的 LocalDateTime
新的日期 API 的幾個優勢:
以前使用的 java.util.Date 月份從 0 開始,咱們通常會 加1 使用,很不方便,java.time.LocalDate 月份和星期都改爲 enum
java.util.Date 和 SimpleDateFormate 都不是線程安全的,而 LocalDate 和 LocalTime 和最基本的 String 同樣,是不變類型,不但線程安全,並且不能修改
java.util.Date 是一個「萬能接口」,它包含日期、時間、毫秒數,新接口更好用的緣由是考慮到了日期的操做,常常發生往前或日後推幾天的狀況,用 java.util.Date 配合Calendar 代碼較繁瑣。