java集合類(容器)
Java中的集合類主要由Collection和Map這兩個接口派生而出,其中Collection接口又派生出三個子接口,分別是Set、List、Queue。所有的Java集合類,都是Set、List、Queue、Map這四個接口的實現類,這四個接口將集合分成了四大類:
- Set代表無序的,元素不可重復的集合;
- List代表有序的,元素可以重復的集合;
- Queue代表先進先出(FIFO)的隊列;
- Map代表具有映射關系(key-value)的集合,其所有的key是一個Set集合,即key無序且不能重復。
Conllection
我們先看看Conllection體系繼承樹
藍色框:接口 紅色框:實現類 紅色框+陰影:常用實現類
List集合(接口)(有序且重復)
public interface List<E> extends Collection<E> {...
}
ArrayList(數組)
:動態數組,支持隨機訪問.
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{...
}
LinkedList(鏈表)
:基于雙向鏈表實現,只能順序訪問,但是可以快速地在鏈表中間插入和刪除元素.
public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{...
}
Vector(安全數組)
:和ArrayList類似,但是它是線程安全的.
public class Vector<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{...
}
Set集合(接口)(無序且不可重復)
HashSet
:哈希表,基于HashMap,可以放入null,但是只能放一個null.
public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, java.io.Serializable
{...public HashSet() {map = new HashMap<>();}...
}
LinkedHashSet
:基于鏈表和哈希表,保證了順序以及唯一性.
public class LinkedHashSet<E>extends HashSet<E>implements Set<E>, Cloneable, java.io.Serializable
{...
}
TreeSet
:紅黑樹,元素不能為null,基于TreeMap
public class TreeSet<E> extends AbstractSet<E>implements NavigableSet<E>, Cloneable, java.io.Serializable
{...public TreeSet() {this(new TreeMap<E,Object>());}...
}
Queue隊列(接口)(FIFO先進先出)
ArrayDeque
:ArrayDeque類是 雙端隊列的線性實現類,繼承接口Deque(雙端隊列),接口Deque繼承Queue
public class ArrayDeque<E> extends AbstractCollection<E>implements Deque<E>, Cloneable, Serializable
{...
}
迭代器(Iterator)
迭代器是屬于設計模式之一,迭代器模式提供了一種方法來順序訪問一個聚合對象中的各個元素,而不保留該對象的內部表示.
Iterator對象稱為迭代器,主要用于遍歷Collection集合中的元素。
集合的頂層接口Collection繼承Iterable接口。
public interface Collection<E> extends Iterable<E>{...
}
使用方法:
Map
代表具有映射關系的集合(key-value)key由set存儲,所以無序且不能改變,value由單向鏈表存儲.
public interface Map<K,V>{...
}
以下是Map的繼承樹
HashMap(鏈表+紅黑樹)
: 1.7基于數組+鏈表實現,1.8基于數組+鏈表+紅黑樹。
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable
{...
}
LinkedHashMap
:哈希表+雙向鏈表維護順序,繼承自 HashMap
public class LinkedHashMap<K,V>extends HashMap<K,V>implements Map<K,V>
{...
}
TreeMap
:紅黑樹,TreeMap是NavigableMap接口的直接實現類,其底層數據結構是紅黑樹結構,不過因為NavigableMap接口也間接繼承了Map接口,因此Map擁有的特點TreeMap也同樣擁有。并且TreeMap基本沒有自身特點,我們可以直接認為Map的特點就是TreeMap的特點。
public class TreeMap<K,V>extends AbstractMap<K,V>implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{...
}
public interface NavigableMap<K,V> extends SortedMap<K,V> {...
}
public interface SortedMap<K,V> extends Map<K,V> {}
安全容器
-
Vector,HashTable,等(java5 之前自帶的集合類,雖然安全,但是性能較差)
-
Concurrent(java5之后的安全類,以Concurrent開頭的集合類,支持并發訪問)
-
CopyOnWrite(以CopyOnWrite開頭的集合類,采用復制副本修改的方法實現并發讀寫)
Stream流
以流的方式操作集合,java8之后提出.
Stream
將要處理的元素集合看作一種流,在流的過程中,借助Stream API
對流中的元素進行操作,比如:篩選、排序、聚合等。
聚集方法
對流中的元素進行處理
方法分類
:Stream
可以由數組或集合創建,對流的操作分為兩種:
- 中間操作,每次返回一個新的流,可以有多個流.
- 終端操作,每個流只能進行一次終端操作,終端操作結束后流無法再次使用.終端操作會產生一個新的集合或值.
方法特征
- 有狀態的方法(給流增加一些屬性,比如不能重復等)
- 短路方法(盡早結束對流的操作,不比檢查所有元素)
特性:
- stream不存儲數據,而是按照特定的規則對數據進行計算,一般會輸出結果.
- stream不會改變數據源,通常情況下會產生一個新的集合或一個值.
- stream具有延遲執行特性,只有代用終端操作時,中間操作才會執行.
使用方式
- 使用Stream的builder()方法創建對應Builder
- 重復調用add()向流添加多個元素
- 調用Builder的builder()方法獲取對應Steam
- 調用Stream的聚集方法
Stream的創建
- 通過
java.util.Collection.stream()
方法用集合創建流.
//創建數組List<String> list = Arrays.asList("a","b","c");//創建順序流Stream<String> stream = list.stream();stream.forEach(System.out::println);System.out.println("\n");//創建并行流Stream<String> parallelStream = list.parallelStream();parallelStream.forEach(System.out::println);
結果:
a
b
cb
c
a
- 使用
java.util.Arrays.stream(T[] array)
方法用數組創建流
//創建數組
int[] array = {1,2,3,4,5};
//創建流
IntStream intStream = Arrays.stream(array);
intStream.forEach(System.out::println);
結果:
1
2
3
4
5
-
使用
Stream
的靜態方法:of()、iterate()、generate()
-
of()方法是對集合添加元素的優化,因為List和Set接口用add()方法一個個元素添加特別的麻煩,map接口添加元素用put(K,V)也是一個個添加特別麻煩。of()方法可以一次性添加多個元素,是一個靜態的方法,只能用在接口List,Map和Set,不能用在他們的實現類.
-
迭代器創建
-
Stream.generate,
generate
方法返回一個無限連續的無序流,其中每個元素由提供的供應商(Supplier
)生成。generate
方法用于生成常量流和隨機元素流。
-
static Stream generate(Supplier<? extends T> s)
參數:傳遞生成流元素的供應商(
Supplier
)。
返回:它返回一個新的無限順序無序的流(Stream
)。
//of()方法創建
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
stream1.forEach(System.out::println);
System.out.println("\n");
//迭代器創建
Stream<Integer> stream2 = Stream.iterate(0,(x)->x+3).limit(4);
stream2.forEach(System.out::println);
System.out.println("\n");
//Stream.generate
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);
結果:
1
2
3
4
50
3
6
90.38712696410649783
0.5341484475982541
0.7667782667234894
注意:
stream
和parallelStream
的簡單區分:stream
是順序流,由主線程按順序對流執行操作,而parallelStream
是并行流,內部以多線程并行執行的方式對流進行操作,但前提是流中的數據處理沒有順序要求。例如篩選集合中的奇數,兩者的處理不同之處:
如果流中的數據量足夠大,并行流可以加快處速度。
除了直接創建并行流,還可以通過parallel()
把順序流轉換成并行流:
//創建數組
List<String> list = Arrays.asList("a", "b", "c");
//創建順序流
Stream<String> stream = list.stream();
//將順序流轉換為并行流
stream.parallel();
stream.forEach(System.out::println);
結果:
b
c
a
Stream的使用
我們需要先了解一個類:Optional.
Optional 類是一個可以為null的容器對象。如果值存在則isPresent()方法會返回true,調用get()方法會返回該對象。
Optional 是個容器:它可以保存類型T的值,或者僅僅保存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。
Optional 類的引入很好的解決空指針異常。
public final class Optional<T> extends Object{}
首先定義一個員工類
public class Person {private String name; // 姓名private int salary; // 薪資private int age; // 年齡private String sex; //性別private String area; // 地區// 構造方法public Person(String name, int salary, int age,String sex,String area) {this.name = name;this.salary = salary;this.age = age;this.sex = sex;this.area = area;}//添加get和set方法...
}
為集合添加一些初始元素.
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
=personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
=personList.add(new Person("Anni", 8200, 24, "female", "New York"));
=personList.add(new Person("Owen", 9500, 25, "male", "New York"));
=personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
遍歷/匹配(foreach/find/match)
Stream支持類似集合的遍歷和匹配元素,只是stream中的元素是以Optional類型存在的.
Stream的遍歷和匹配也比較簡單.
//定義一個List
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
//遍歷輸出符合條件的元素
list.stream().filter(x->x>6).forEach(System.out::println);
//匹配第一個
Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
//匹配任意(適用于并行流)
Optional<Integer> findAny = list.parallelStream().filter(x->x>6).findAny();
//是否包含符合特定條件的元素
boolean anyMatch = list.stream().anyMatch(x->x>6);
System.out.println("匹配第一個值:" + findFirst.get());
System.out.println("匹配任意一個值:" + findAny.get());
System.out.println("是否存在大于6的值:" + anyMatch);
7
9
8
匹配第一個值:7
匹配任意一個值:8
是否存在大于6的值:true
篩選(filter)
篩選,是按照一定的規則校驗流中的元素,將符合條件的元素提取到新的流中的操作。
案例一:篩選出Integer
集合中大于7的元素,并打印出來
List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9);
Stream<Integer> stream = list.stream();
stream.filter(x->x>7).forEach(System.out::println);
8
9
案例二: 篩選員工中工資高于8000的人,并形成新的集合。 形成新集合依賴collect
(收集),后文有詳細介紹。
//篩選篩選員工中工資高于8000的人,并形成新的集合。
//map()方法通常與流(Stream)一起使用,用于將流中的每個元素映射到另一個元素。
List<String> fiterList = personList.stream() .filter(x>x.getSalary()>8000).map(Person::getName)
.collect(Collectors.toList());
System.out.print("薪資高于8000美元的員工:" + fiterList);
薪資高于8000美元的員工:[Tom, Anni, Owen]
聚合(max/min/count)
max
、min
、count
這些字眼你一定不陌生,沒錯,在mysql中我們常用它們進行數據統計。Java stream中也引入了這些概念和用法,極大地方便了我們對集合、數組的數據統計工作。
案例一:獲取String
集合中最長的元素。
List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");
Optional<String> max = list.stream().max(Comparator.comparing(String::length));
System.out.println("最長的字符串:" + max.get());
最長的字符串:weoujgsd
案例二:獲取Integer
集合中的最大值。
List<Integer> list = Arrays.asList(7, 6, 9, 4, 11, 6);//max通過自定義方法可以實現不同的功能
//自然排序
Optional<Integer> max = list.stream().max(Integer::compareTo);
//自定義排序(從大到小排序)(o1-o2就是升序,o2-o1就是降序)
//因為在comparator里面,-1代表小于,0代表等于,1代表大于
Optional<Integer> max2 = list.stream().max(((o1, o2) -> o2-o1));
System.out.println("自然排序的最大值:" + max.get());
System.out.println("自定義排序的最大值:" + max2.get());
自然排序的最大值:11
自定義排序的最大值:4
案例三:獲取員工薪資最高的人。
//獲取員工薪資最高的人
Optional<Person> max = personList.stream().max(Comparator.comparing(Person::getSalary));
System.out.println("員工薪資最大值:" + max.get().getSalary());
員工薪資最大值:9500
案例四:計算Integer
集合中大于6的元素的個數。
//計算Integer集合中大于6的元素的個數
List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9);
long count = list.stream().filter(x->x>6).count();
System.out.println("list中大于6的元素個數:" + count);
list中大于6的元素個數:4
映射(map/flatMap)
映射,可以將一個流的元素按照一定的映射規則映射到另一個流中。分為map
和flatMap
:
map
:接收一個函數作為參數,該函數會被應用到每個元素上,并將其映射成一個新的元素。flatMap
:接收一個函數作為參數,將流中的每個值都換成另一個流,然后把所有流連接成一個流。
案例一:英文字符串數組的元素全部改為大寫。整數數組每個元素+3。
//英文字符串數組的元素全部改為大寫。整數數組每個元素+3
String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
List<String> stringList = Arrays.stream(strArr)
.map(String::toUpperCase).collect(Collectors.toList());List<Integer> integerList = Arrays.asList(1, 3, 5, 7, 9, 11);
List<Integer> intListNew = integerList.stream().map(x->x+3)
.collect(Collectors.toList());
System.out.println("每個元素大寫:" + stringList);
System.out.println("每個元素+3:" + intListNew);
每個元素大寫:[ABCD, BCDD, DEFDE, FTR]
每個元素+3:[4, 6, 8, 10, 12, 14]
案例二:將員工的薪資全部增加1000。
//不改變原來員工集合的方式(新建一個對象進行修改)
List<Person> personListNew = personList.stream().map(person -> {Person personNew = new Person(person.getName(), 0, 0, null, null);personNew.setSalary(person.getSalary() + 10000);return personNew;
}).collect(Collectors.toList());
System.out.println("一次改動前:" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
System.out.println("一次改動后:" + personListNew.get(0).getName() + "-->" + personListNew.get(0).getSalary());// 改變原來員工集合的方式(在原來對象的基礎上進行修改)
List<Person> personListNew2 = personList.stream().map(person -> {person.setSalary(person.getSalary() + 10000);return person;
}).collect(Collectors.toList());
System.out.println("二次改動前:" + personList.get(0).getName() + "-->" + personListNew.get(0).getSalary());
System.out.println("二次改動后:" + personListNew2.get(0).getName() + "-->" + personListNew.get(0).getSalary());
一次改動前:Tom-->8900
一次改動后:Tom-->18900
二次改動前:Tom-->18900
二次改動后:Tom-->18900
案例三:將兩個字符數組合并成一個新的字符數組。
//將兩個字符數組合并成一個新的字符數組
List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
List<String> listNew = list.stream().flatMap(s->{//將每個元素轉換成一個streamString[] split = s.split(",");Stream<String> stringStream = Arrays.stream(split);return stringStream;
}).collect(Collectors.toList());System.out.println("處理前的集合:" + list);
System.out.println("處理后的集合:" + listNew);
處理前的集合:[m,k,l,a, 1,3,5,7]
處理后的集合:[m, k, l, a, 1, 3, 5, 7]
此外,map系列還有mapToInt、mapToLong、mapToDouble三個函數,它們以一個映射函數為入參,將流中每一個元素處理后生成一個新流。以mapToInt為例,看兩個示例:
public static void main(String[] args) {// 輸出字符串集合中每個字符串的長度List<String> stringList = Arrays.asList("mu", "CSDN", "hello","world", "quickly");stringList.stream().mapToInt(String::length).forEach(System.out::println);// 將int集合的每個元素增加1000List<Integer> integerList = Arrays.asList(4, 5, 2, 1, 6, 3);integerList.stream().mapToInt(x -> x + 1000).forEach(System.out::println);
}
mapToInt三個函數生成的新流,可以進行很多后續操作,比如求最大最小值、求和、求平均值:
public static void main(String[] args) {List<Double> doubleList = Arrays.asList(1.0, 2.0, 3.0, 4.0, 2.0);double average = doubleList.stream().mapToDouble(Number::doubleValue).average().getAsDouble();double sum = doubleList.stream().mapToDouble(Number::doubleValue).sum();double max = doubleList.stream().mapToDouble(Number::doubleValue).max().getAsDouble();System.out.println("平均值:" + average + ",總和:" + sum + ",最大值:" + max);
}
歸約(reduce)
歸約,也稱縮減,顧名思義,是把一個流縮減成一個值,能實現對集合求和、求乘積和求最值操作。
reduce函數其用作從一個流中生成一個值,其生成的值不是隨意的,而是根據指定的計算模型。
比如終止操作中提到 count、min 和 max 方法,因為常用而被納入標準庫中。事實上這些方法都是一種 reduce 操作。
reduce操作三要素
:
Optional reduce(BinaryOperator accumulator);
T reduce(T identity, BinaryOperator accumulator);
U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator combiner);
可以看到,在 Stream API 中,提供了三個 reduct 操作方法,根據參數不同進行區分。
identity,accumulator,combiner
接下來,我來介紹一下這三個參數分別有什么作用.
- identiy
identiy(初始值)是 reduce 操作的初始值,也就是當元素集合為空時的默認結果。對應下方代碼示例,也就是說 reduce 操作的初始值是 0和1。
- accumulator
accumulator(累加器)是一個函數,它接受兩個參數,reduce 操作的部分元素和元素集合中的下一個元素。它返回一個新的部分元素。在這個例子中,累加器是一個 lambda 表達式,它將集合中兩個整數相加并返回一個整數:(x,y)->x+y。
- combiner
combiner(組合器)是一個函數,它用于在 reduce 操作被并行化或者當累加器的參數類型和實現類型不匹配時,將 reduce 操作的部分結果進行組合。在上面代碼示例中,我們不需要使用組合器,因為上面我們的 reduce 操作不需要并行,而且累加器的參數類型和實現類型都是 Integer。
combiner(組合器),說白了就是在并行流中會用到.
如圖所示:
案例一:求Integer
集合的元素之和、乘積和最大值。
//求Integer集合的元素之和、乘積和最大值。
List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
//求和方式1
Optional<Integer> sum = list.stream().reduce((x,y)->x+y);
//求和方式2
Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
//求和方式3
Integer sum3 = list.stream().reduce(0,Integer::sum);//求乘積
Optional<Integer> product = list.stream().reduce((x,y)->x*y);//求最大值方式1
Optional<Integer> max1 = list.stream().reduce((x, y) -> x > y ? x : y);
//求最大值方式2
Integer max2 = list.stream().reduce(1,Integer::max);
System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);
System.out.println("list求積:" + product.get());
System.out.println("list求最大值:" + max1.get() + "," + max2);
list求和:29,29,29
list求積:2112
list求最大值:11,11
案例二:求所有員工的工資之和和最高工資。
//求工資之和方式1
Optional<Integer> sumSalary1 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
//求工資之和方式2
Integer sumSalary2 = personList.stream().reduce(0, (sum, p) ->sum += p.getSalary(), (sum1, sum2) -> sum1 + sum2);
// 求工資之和方式3
Integer sumSalary3 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum);// 求最高工資方式1
Integer maxSalary1 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),Integer::max);
// 求最高工資方式2
Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),(max1, max2) -> max1 > max2 ? max1 : max2);
// 求最高工資方式3
Integer maxSalary3 = personList.stream().map(Person::getSalary).reduce(Integer::max).get();System.out.println("工資之和:" + sumSalary1.get() + "," + sumSalary2 + "," + sumSalary3);
System.out.println("最高工資:" + maxSalary1 + "," + maxSalary2 + "," + maxSalary3);
工資之和:49300,49300,49300
最高工資:9500,9500,9500
收集(collect)
collect
,收集,可以說是內容最繁多、功能最豐富的部分了。從字面上去理解,就是把一個流收集起來,最終可以是收集成一個值也可以收集成一個新的集合。
collect
主要依賴java.util.stream.Collectors
類內置的靜態方法。
- 歸集(toList/toSet/toMap)
因為流不存儲數據,那么在流中的數據完成處理后,需要將流中的數據重新歸集到新的集合里。toList
、toSet
和toMap
比較常用,另外還有toCollection
、toConcurrentMap
等復雜一些的用法。
下面用一個案例演示toList
、toSet
和toMap
:
List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
List<Integer> listNew= list.stream().filter(x->x%2==0).collect(Collectors.toList());
Set<Integer> set = list.stream().filter(x->x%2==0).collect(Collectors.toSet());System.out.println("toList:" + listNew);
System.out.println("toSet:" + set);
toList:[6, 4, 6, 6, 20]
toSet:[4, 20, 6]
Map<?,Person> map = personList.stream().filter(p -> p.getSalary() > 8000).collect(Collectors.toMap(Person::getName,p->p));
System.out.println("toMap:" + map);
toMap:{Tom=Person@15aeb7ab, Owen=Person@7b23ec81, Anni=Person@6acbcfc0}
- 統計(count/averaging)
Collectors
提供了一系列用于數據統計的靜態方法:
- 計數:count
- 平均值:averagingInt、averagingLong、averagingDouble
- 最值:maxBy、minBy
- 求和:summingInt、summingLong、summingDouble
- 統計以上所有:summarizingInt、summarizingLong、summarizingDouble
案例:統計員工人數、平均工資、工資總額、最高工資。
// 求總數
Long count = personList.stream().collect(Collectors.counting());
// 求平均工資
Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
// 求最高工資
Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
// 求工資之和
Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
// 一次性統計所有信息
DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));System.out.println("員工總數:" + count);
System.out.println("員工平均工資:" + average);
System.out.println("員工工資總和:" + sum);
System.out.println("員工工資所有統計:" + collect);
員工總數:6
員工平均工資:8216.666666666666
員工工資總和:49300
員工工資所有統計:DoubleSummaryStatistics{count=6, sum=49300.000000, min=7000.000000, average=8216.666667, max=9500.000000}
- 分組(partitioningBy/groupingBy)
- 分區:將
stream
按條件分為兩個Map
,比如員工按薪資是否高于8000分為兩部分。 - 分組:將集合分為多個Map,比如員工按性別分組。有單級分組和多級分組。
案例:將員工按薪資是否高于8000分為兩部分;將員工按性別和地區分組
//將員工按薪資是否高于8000分組
Map<Boolean,List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x->x.getSalary()>8000));
//將員工按照性別分組
Map<String,List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
// 將員工先按性別分組,再按地區分組
Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));System.out.println("員工按薪資是否大于8000分組情況:" + part);
System.out.println("員工按性別分組情況:" + group);
System.out.println("員工按性別、地區:" + group2);
員工按薪資是否大于8000分組情況:{false=[Person@7291c18f, Person@34a245ab, Person@7cc355be], true=[Person@6e8cf4c6, Person@12edcd21, Person@34c45dca]}
員工按性別分組情況:{female=[Person@34a245ab, Person@12edcd21, Person@7cc355be], male=[Person@6e8cf4c6, Person@7291c18f, Person@34c45dca]}
員工按性別、地區:{female={New York=[Person@12edcd21, Person@7cc355be], Washington=[Person@34a245ab]}, male={New York=[Person@6e8cf4c6, Person@34c45dca], Washington=[Person@7291c18f]}}
- 接合(joining)
joining
可以將stream中的元素用特定的連接符(沒有的話,則直接連接)連接成一個字符串。
String name = personList.stream().map(person -> person.getName()).collect(Collectors.joining(","));
System.out.println("所有員工的姓名:" + name);
所有員工的姓名:Tom,Jack,Lily,Anni,Owen,Alisa
- 歸約(reducing)
Collectors
類提供的reducing
方法,相比于stream
本身的reduce
方法,增加了對自定義歸約的支持。
// 每個員工減去起征點后的薪資之和
Integer sum = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j - 5000)));
System.out.println("員工扣稅薪資總和:" + sum);
員工扣稅薪資總和:19300
排序(sorted)
sorted,中間操作。有兩種排序:
- sorted():自然排序,流中元素需實現Comparable接口
- sorted(Comparator com):Comparator排序器自定義排序
案例:將員工按工資由高到低(工資一樣則按年齡由大到小)排序
// 按工資升序排序(自然排序)
List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName).collect(Collectors.toList());
// 按工資倒序排序
List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()).map(Person::getName).collect(Collectors.toList());
// 先按工資再按年齡升序排序
List<String> newList3 = personList.stream().sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName).collect(Collectors.toList());
// 先按工資再按年齡自定義排序(降序)
List<String> newList4 = personList.stream().sorted((p1, p2) -> {if (p1.getSalary() == p2.getSalary()) {return p2.getAge() - p1.getAge();} else {return p2.getSalary() - p1.getSalary();}
}).map(Person::getName).collect(Collectors.toList());System.out.println("按工資升序排序:" + newList);
System.out.println("按工資降序排序:" + newList2);
System.out.println("先按工資再按年齡升序排序:" + newList3);
System.out.println("先按工資再按年齡自定義降序排序:" + newList4);
按工資升序排序:[Jack, Lily, Alisa, Anni, Tom, Owen]
按工資降序排序:[Owen, Tom, Anni, Alisa, Lily, Jack]
先按工資再按年齡升序排序:[Jack, Lily, Alisa, Anni, Tom, Owen]
先按工資再按年齡自定義降序排序:[Owen, Tom, Anni, Alisa, Lily, Jack]
提取/組合
流也可以進行合并、去重、限制、跳過等操作。
String[] arr1 = { "a", "b", "c", "d" };
String[] arr2 = { "d", "e", "f", "g" };Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
// concat:合并兩個流 distinct:去重
List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
// limit:限制從流中獲得前n個數據
List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
// skip:跳過前n個數據
List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());System.out.println("流合并:" + newList);
System.out.println("limit:" + collect);
System.out.println("skip:" + collect2);
流合并:[a, b, c, d, e, f, g]
limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
skip:[3, 5, 7, 9, 11]
哈希(hash)
了解哈希
定義
散列表(Hash table,也叫哈希表),是根據鍵(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它通過計算一個關于鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱做散列函數,存放記錄的數組稱做散列表。
哈希表其實是一種數據結構。
特點
如果我們要根據名字找到其中的任何一個元素,就需要遍歷整個數組。最壞情況下時間復雜度是O(n) ,但是借助 Hash 可以將時間復雜度降為O(1)。
原理
Hash表采用一個映射函數 f :key —> address 將關鍵字映射到該記錄在表中的存儲位置,從而在想要查找該記錄時,可以直接根據關鍵字和映射關系計算出該記錄在表中的存儲位置,通常情況下,這種映射關系稱作為Hash函數,而通過Hash函數和關鍵字計算出來的存儲位置(注意這里的存儲位置只是表中的存儲位置,并不是實際的物理地址)稱作為Hash地址。
優點
哈希表的效率非常高,查找、插入、刪除操作只需要接近常量的時間即0(1)的時間級。如果需要在一秒種內查找上千條記錄通常使用哈希表,哈希表的速度明顯比樹快,樹的操作通常需要O(N)的時間級。哈希表不僅速度快,編程實現也相對容易。如果不需要遍歷數據,不二的選擇。
缺點
它是基于數組的,數組創建后難于擴展。有些情況下,哈希表被基本填滿時,性能下降得非常嚴重,所以開發者必須要清楚表中將要存儲的數據量。或者也可以定期地把數據轉移到更大的哈希表中,不過這個過程耗時相對比較大。
應用
-
唯一標識或數據檢驗
能夠對輸入數據或文件進行校驗,判斷是否相同或是否被修改。如圖片識別,可針對圖像二進制流進行摘要后MD5,得到的哈希值作為圖片唯一標識;
-
安全加密
對于敏感數據比如密碼字段進行MD5或SHA加密傳輸。哈希算法還可以檢驗信息的擁有者是否真實。如,用保存密碼的哈希值代替保存密碼,基本可以杜絕泄密風險。
-
數字簽名
由于非對稱算法的運算速度較慢,所以在數字簽名協議中,單向散列函數扮演了一個重要的角色。對Hash值,又稱“數字摘要”進行數字簽名,在統計上可以認為與對文件本身進行數字簽名是等效的。
-
散列函數
是構造散列表的關鍵。它直接決定了散列沖突的概率和散列表的性質。不過相對哈希算法的其他方面應用,散列函數對散列沖突要求較低,出現沖突時可以通過開放尋址法或鏈表法解決沖突。對散列值是否能夠反向解密要求也不高。反而更加關注的是散列的均勻性,即是否散列值均勻落入槽中以及散列函數執行的快慢也會影響散列表性能。所以散列函數一般比較簡單,追求均勻和高效。
-
負載均衡
常用的負載均衡算法有很多,比如輪詢、隨機、加權輪詢。如何實現一個會話粘滯的負載均衡算法呢?可以通過哈希算法,對客戶端IP地址或會話SessionID計算哈希值,將取得的哈希值與服務器列表大小進行取模運算,最終得到應該被路由到的服務器編號。這樣就可以把同一IP的客戶端請求發到同一個后端服務器上。
-
分布式存儲
一致性哈希算法解決緩存等分布式系統的擴容、縮容導致大量數據搬移難題。
Hash函數
定義
所謂的 hash 函數就是將字符串轉換為數字的算法。
種類
-
直接定址法
取關鍵字或者關鍵字的某個線性函數為 Hash 地址,即address(key) = a * key + b; 如知道學生的學號從2000開始,最大為4000,則可以將address(key)=key-2000(其中a = 1)作為Hash地址。
-
平方取中法
對關鍵字進行平方計算,取結果的中間幾位作為 Hash 地址。如有以下關鍵字序列 {421,423,436} ,平方之后的結果為 {177241,178929,190096} ,那么可以取中間的兩位數 {72,89,00} 作為 Hash 地址。
-
折疊法
將關鍵字拆分成幾部分,然后將這幾部分組合在一起,以特定的方式進行轉化形成Hash地址。如圖書的 ISBN 號為 8903-241-23,可以將 address(key)=89+03+24+12+3 作為 Hash 地址。
-
除留取余法
如果知道 Hash 表的最大長度為 m,可以取不大于m的最大質數 p,然后對關鍵字進行取余運算,address(key)=key % p。這里 p 的選取非常關鍵,p 選擇的好的話,能夠最大程度地減少沖突,p 一般取不大于m的最大質數。
在設計Hash函數的時候。一定要保證相同字符串產生的 Hash 值相同,同時要盡量的減小Hash沖突的發生,這樣才算是好的 hash 函數。
Hash算法
定義
Hash 算法能將將任意長度的二進制明文映射為較短的二進制串的算法,并且不同的明文很難映射為相同的 Hash 值。
散列算法(Hash Algorithm),又稱哈希算法,雜湊算法,是一種從任意文件中創造小的數字「指紋」的方法。與指紋一樣,散列算法就是一種以較短的信息來保證文件唯一性的標志,這種標志與文件的每一個字節都相關,而且難以找到逆向規律。
常見Hash算法有MD5和SHA系列,目前MD5和SHA1已經被破解,一般推薦至少使用SHA2-256算法。
MD5
定義
MD5屬于Hash算法中的一種,它輸入任意長度的信息,在處理過程中以512位輸入數據塊為單位,輸出為128位的信息(數字指紋)。
適用場景
-
防篡改,保障文件傳輸可靠性
如SVN中對文件的控制;文件下載過程中,網站提供MD5值供下載后判斷文件是否被篡改;BT中對文件塊進行校驗的功能。
-
增強密碼保存的安全性
例如網站將用戶密碼的MD5值保存,而不是存儲明文用戶密碼,當然,還會加SALT,進一步增強安全性。
-
數字簽名
在部分網上賭場中,使用MD5算法來保證過程的公平性,并使用隨機串進行防碰撞,增加解碼難度。
過程
- 消息填充,補長到512的倍數。最后64位為消息長度(填充前的長度)的低64位,一定要補長(64+1~512),內容為100…0(如若消息長448,則填充512+64)。
- 分割,把結果分割為512位的塊:Y0,Y1,…(每一個有16個32比特長字)。
- 計算,初始化MD buffer,128位常量(4個32bit字),進入循環迭代,共L次。每次一個輸入128位,另一個輸入512位,結果輸出128位,用于下一輪輸入。
- 輸出,最后一步的輸出即為散列結果128位。
SHA-1
安全哈希算法(Secure Hash Algorithm)主要適用于數字簽名標準(Digital Signature Standard DSS)里面定義的數字簽名算法(Digital Signature Algorithm DSA)。對于長度小于2^64b的消息,SHA-1將輸入流按照每塊512b(64B)進行分塊,并產生20B或160b的信息摘要。
SHA-2
SHA-2是六個不同算法的合稱,包括:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。除了生成摘要的長度、循環運行的次數等一些微小差異外,這些算法的基本結構是一致的。對于任意長度的消息,SHA256都會產生一個256bit長的消息摘要。