第4章 集合框架
爲什麼需要集合框架
我們在存儲大量的數據的時候,首先想到的是數組;但是數組存在的問題: ①大小是固定的,受限②類型是固定。但是在項目開發過程中我們存儲的數據大部分情況都不是固定的數量。存儲公司的ERP的會員數,不是固定的,存儲公司的供應商,這些都不是固定的數量。如何存儲此類數據,java提供了一整套集合框架。
java提供的集合框架
爲了提高編程的效率,java在jdk中提供一個包: java.util.*,該包下提供一系列的接口和實現類。用來存儲大量的數據。整體的繼承關係圖:
總結如下:
①Map接口產生Collection接口,Map接口可以理解爲跟接口
②Map是一套體系,存儲的映射關係: key—–value的映射
③Collection接口有兩個直接子接口: List和Set
④List系列存儲有序可以重複的數據
⑤Set系列存儲的無序不允許重複的數據
⑥兩個比較器: Comparable和Comparator
⑦兩個工具類: Collections和Arrays
常用的集合接口和實現類
List接口
List存儲的有序允許重複的數據,線性表; List的常用方法;
public interface List<E> extends Collection<E>
Collection接口常用方法
Modifier and Type | Method and Description |
---|---|
boolean |
add(E e) 確保此集合包含指定的元素(可選操作)。 |
boolean |
addAll(Collection c) 將指定集合中的所有元素添加到此集合(可選操作)。 |
void |
clear() 從此集合中刪除所有元素(可選操作)。 |
boolean |
contains(Object o) 如果此集合包含指定的元素,則返回 true 。 |
boolean |
containsAll(Collection c) 如果此集合包含指定 集合 中的所有元素,則返回true。 |
boolean |
equals(Object o) 將指定的對象與此集合進行比較以獲得相等性。 |
int |
hashCode() 返回此集合的哈希碼值。 |
boolean |
isEmpty() 如果此集合不包含元素,則返回 true 。 |
Iterator |
iterator() 返回此集合中的元素的迭代器。 |
default Stream |
parallelStream() 返回可能並行的 Stream 與此集合作爲其來源。 |
boolean |
remove(Object o) 從該集合中刪除指定元素的單個實例(如果存在)(可選操作)。 |
boolean |
removeAll(Collection c) 刪除指定集合中包含的所有此集合的元素(可選操作)。 |
default boolean |
removeIf(Predicate filter) 刪除滿足給定謂詞的此集合的所有元素。 |
boolean |
retainAll(Collection c) 僅保留此集合中包含在指定集合中的元素(可選操作)。 |
int |
size() 返回此集合中的元素數。 |
default Spliterator |
spliterator() 創建一個Spliterator 在這個集合中的元素。 |
default Stream |
stream() 返回以此集合作爲源的順序 Stream 。 |
Object[] |
toArray() 返回一個包含此集合中所有元素的數組。 |
T[] |
toArray(T[] a) 返回包含此集合中所有元素的數組; 返回的數組的運行時類型是指定數組的運行時類型。 |
List子接口常用方法:
Modifier and Type | Method and Description |
---|---|
boolean |
add(E e) 將指定的元素追加到此列表的末尾(可選操作)。 |
void |
add(int index, E element) 將指定的元素插入此列表中的指定位置(可選操作)。 |
boolean |
addAll(Collection c) 按指定集合的迭代器(可選操作)返回的順序將指定集合中的所有元素附加到此列表的末尾。 |
boolean |
addAll(int index, Collection c) 將指定集合中的所有元素插入到此列表中的指定位置(可選操作)。 |
void |
clear() 從此列表中刪除所有元素(可選操作)。 |
boolean |
contains(Object o) 如果此列表包含指定的元素,則返回 true 。 |
boolean |
containsAll(Collection c) 如果此列表包含指定 集合的 所有元素,則返回true。 |
boolean |
equals(Object o) 將指定的對象與此列表進行比較以獲得相等性。 |
E |
get(int index) 返回此列表中指定位置的元素。 |
int |
hashCode() 返回此列表的哈希碼值。 |
int |
indexOf(Object o) 返回此列表中指定元素的第一次出現的索引,如果此列表不包含元素,則返回-1。 |
boolean |
isEmpty() 如果此列表不包含元素,則返回 true 。 |
Iterator |
iterator() 以正確的順序返回該列表中的元素的迭代器。 |
int |
lastIndexOf(Object o) 返回此列表中指定元素的最後一次出現的索引,如果此列表不包含元素,則返回-1。 |
ListIterator |
listIterator() 返回列表中的列表迭代器(按適當的順序)。 |
ListIterator |
listIterator(int index) 從列表中的指定位置開始,返回列表中的元素(按正確順序)的列表迭代器。 |
E |
remove(int index) 刪除該列表中指定位置的元素(可選操作)。 |
boolean |
remove(Object o) 從列表中刪除指定元素的第一個出現(如果存在)(可選操作)。 |
boolean |
removeAll(Collection c) 從此列表中刪除包含在指定集合中的所有元素(可選操作)。 |
default void |
replaceAll(UnaryOperator operator) 將該列表的每個元素替換爲將該運算符應用於該元素的結果。 |
boolean |
retainAll(Collection c) 僅保留此列表中包含在指定集合中的元素(可選操作)。 |
E |
set(int index, E element) 用指定的元素(可選操作)替換此列表中指定位置的元素。 |
int |
size() 返回此列表中的元素數。 |
default void |
sort(Comparator c) 使用隨附的 Comparator 排序此列表來比較元素。 |
default Spliterator |
spliterator() 在此列表中的元素上創建一個Spliterator 。 |
List |
subList(int fromIndex, int toIndex) 返回此列表中指定的 fromIndex (含)和 toIndex 之間的視圖。 |
Object[] |
toArray() 以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的數組。 |
T[] |
toArray(T[] a) 以正確的順序返回一個包含此列表中所有元素的數組(從第一個到最後一個元素); 返回的數組的運行時類型是指定數組的運行時類型。 |
總結常用方法:
添加方法: add 、 add(int index, E element)、 addAll(Collection c)
刪除方法: remove(int index),remove(Object o)、removeAll(Collection c), clear()
修改方法: set(int index, E element), replaceAll(UnaryOperator operator)
查詢方法: get、indexOf(Object o)、lastIndexOf(Object o)、 subList(int fromIndex, int toIndex), size()
遍歷方法: iterator(), listIterator() , listIterator(int index)
判斷方法: isEmpty(), contains(Object o), containsAll(Collection c)
流操作方法: stream(), parallelStream()
排序方法: sort()
轉換方法: toArray() 、toArray(T[] a)
ArrayList用法
ArrayList底層是用數組存儲的,默認長度10, 數組類型是Object。動態數組,可變數組。
package ch005;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Demo1 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("小昭");
list.add("殷離");
System.out.println("size:"+ list.size());
System.out.println("是否爲空:"+ list.isEmpty());
System.out.println("是否包括(小昭):"+list.contains("小昭"));
System.out.println("獲取所有的女生:"+list.subList(1, 5));
Object [] objs = list.toArray();
System.out.print("轉換對象數組:");
for(Object o: objs) {
System.out.print(o+",");
}
System.out.println("修改元素:"+list.set(1, "滅絕師太"));
System.out.println("修改之後:"+list);
//1.通過for遍歷
System.out.print("遍歷元素:");
for(int i=0 ;i<list.size(); i++) {
System.out.print(list.get(i)+",");
}
System.out.println("\n使用迭代器遍歷:");
Iterator it = list.iterator();
while(it.hasNext()) {
//注意的問題: 遍歷的時候不允許添加或刪除元素,
//否則會出現併發修改異常ConcurrentModificationException
list.add("xx");
System.out.println(it.next());
}
System.out.println("\n刪除元素:"+list.remove(0));
System.out.println("刪除元素:"+list.remove("周芷若"));
System.out.println("刪除兩個元素之後:"+list.size());
}
}
創建ArrayList的說明:
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
elementData是ArrayList存儲數據的緩衝區,ArrayList的容量就是數組緩衝區的長度,當第一次添加元素的時候,空的ArrayList會被擴容到長度爲10。
LinkedList
底層基於鏈表存儲的, ArrayList是數組,連續的內存空間;而鏈表不是連續空間。鏈表的每一個節點不但要存儲數據還要存儲上下元素的位置。鏈表相當於小朋友手拉手組成一個隊列,左手存儲的上一個元素的位置,右手存儲下一個元素的位置,小朋友本身是元素存數據。
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable
//雙鏈表實現了List和Deque接口。 實現所有可選列表操作,並允許所有元素(包括null )。
//所有的操作都能像雙向列表一樣預期。 索引到列表中的操作將從開始或結束遍歷列表,以更接近指定的索引爲準。
常用方法:
Modifier and Type | Method and Description |
---|---|
boolean |
add(E e) 將指定的元素追加到此列表的末尾。 |
void |
add(int index, E element) 在此列表中的指定位置插入指定的元素。 |
boolean |
addAll(Collection c) 按照指定集合的迭代器返回的順序將指定集合中的所有元素追加到此列表的末尾。 |
boolean |
addAll(int index, Collection c) 將指定集合中的所有元素插入到此列表中,從指定的位置開始。 |
void |
addFirst(E e) 在該列表開頭插入指定的元素。 |
void |
addLast(E e) 將指定的元素追加到此列表的末尾。 |
void |
clear() 從列表中刪除所有元素。 |
Object |
clone() 返回此 LinkedList 的淺版本。 |
boolean |
contains(Object o) 如果此列表包含指定的元素,則返回 true 。 |
Iterator |
descendingIterator() 以相反的順序返回此deque中的元素的迭代器。 |
E |
element() 檢索但不刪除此列表的頭(第一個元素)。 |
E |
get(int index) 返回此列表中指定位置的元素。 |
E |
getFirst() 返回此列表中的第一個元素。 |
E |
getLast() 返回此列表中的最後一個元素。 |
int |
indexOf(Object o) 返回此列表中指定元素的第一次出現的索引,如果此列表不包含元素,則返回-1。 |
int |
lastIndexOf(Object o) 返回此列表中指定元素的最後一次出現的索引,如果此列表不包含元素,則返回-1。 |
ListIterator |
listIterator(int index) 從列表中的指定位置開始,返回此列表中元素的列表迭代器(按適當的順序)。 |
boolean |
offer(E e) 將指定的元素添加爲此列表的尾部(最後一個元素)。 |
boolean |
offerFirst(E e) 在此列表的前面插入指定的元素。 |
boolean |
offerLast(E e) 在該列表的末尾插入指定的元素。 |
E |
peek() 檢索但不刪除此列表的頭(第一個元素)。 |
E |
peekFirst() 檢索但不刪除此列表的第一個元素,如果此列表爲空,則返回 null 。 |
E |
peekLast() 檢索但不刪除此列表的最後一個元素,如果此列表爲空,則返回 null 。 |
E |
poll() 檢索並刪除此列表的頭(第一個元素)。 |
E |
pollFirst() 檢索並刪除此列表的第一個元素,如果此列表爲空,則返回 null 。 |
E |
pollLast() 檢索並刪除此列表的最後一個元素,如果此列表爲空,則返回 null 。 |
E |
pop() 從此列表表示的堆棧中彈出一個元素。 |
void |
push(E e) 將元素推送到由此列表表示的堆棧上。 |
E |
remove() 檢索並刪除此列表的頭(第一個元素)。 |
E |
remove(int index) 刪除該列表中指定位置的元素。 |
boolean |
remove(Object o) 從列表中刪除指定元素的第一個出現(如果存在)。 |
E |
removeFirst() 從此列表中刪除並返回第一個元素。 |
boolean |
removeFirstOccurrence(Object o) 刪除此列表中指定元素的第一個出現(從頭到尾遍歷列表時)。 |
E |
removeLast() 從此列表中刪除並返回最後一個元素。 |
boolean |
removeLastOccurrence(Object o) 刪除此列表中指定元素的最後一次出現(從頭到尾遍歷列表時)。 |
E |
set(int index, E element) 用指定的元素替換此列表中指定位置的元素。 |
int |
size() 返回此列表中的元素數。 |
Spliterator |
spliterator() 在此列表中的元素上創建*late-binding和故障快速* Spliterator 。 |
Object[] |
toArray() 以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的數組。 |
T[] |
toArray(T[] a) 以正確的順序返回一個包含此列表中所有元素的數組(從第一個到最後一個元素); 返回的數組的運行時類型是指定數組的運行時類型。 |
案例:
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.add(5);
list.add(6);
list.add(7);
//帖子置頂
list.addFirst(1);
list.addLast(8);
System.out.println(list);
System.out.println("First=="+list.getFirst());
System.out.println("Last==="+list.getLast());
list.removeFirst();
list.removeLast();
System.out.println("刪除之後: "+list);
//Deque 隊列,火車站買票排隊就是一個隊列
list.push(10);
list.push(11);
System.out.println("push之後:"+list);
Object first = list.poll();//檢索並刪除此列表的頭(第一個元素) 買票之後走人
System.out.println("poll的元素:"+first);
System.out.println("poll之後:"+list);
}
LinkedList源碼分析:
存儲數據的對象: 靜態內部類,只能在當前類使用
private static class Node<E> {
E item; //數據
Node<E> next;//下一個節點的引用
Node<E> prev;//上一個節點的引用
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
添加元素:
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
連接一個數據作爲最後的元素
*/
void linkLast(E e) {
final Node<E> l = last; //獲取最後的元素
final Node<E> newNode = new Node<>(l, e, null);//創建節點
last = newNode;//把新節點賦值last
if (l == null)
first = newNode; //把新節點賦值first第一個
else
l.next = newNode;//把最後一個節點的next引用,指向到新節點,組成隊列
size++;//整個隊列的元素加 1
modCount++;
}
上機練習代碼
練習一、練習二、
①實體類:
package ch005;
public class Student {
private int no;
private String name;
private char sex;
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student [no=" + no + ", name=" + name + ", sex=" + sex + "]";
}
public Student(int no, String name, char sex) {
super();
this.no = no;
this.name = name;
this.sex = sex;
}
public Student() {
super();
}
}
②業務類
package ch005;
import java.util.ArrayList;
import java.util.List;
public class StuMgr {
// 包,存放的鑰匙,有錢包,有紙巾,有水筆,化妝品,手機,充電寶
// 包: 紙巾包,只能存儲紙巾,取出比較方便
private List<Student> list = new ArrayList<>();
//添加方法
public void addStudent(Student student) {
list.add(student);
}
//查詢
public Student findStudent(String name) {
Student stu = null;
for(Student s : list) {
if(s.getName().equals(name)) {
stu = s;//s是找到的對象,賦值給stu
break;
}
}
return stu;
}
//顯示所有
public void showAll() {
System.out.println("學號\t姓名\t性別");
for (int i = 0; i < list.size(); i++) {
Student s = list.get(i);
System.out.println(s.getNo() + "\t" + s.getName() + "\t" + s.getSex());
}
}
}
③測試類
public static void main(String[] args) {
System.out.println("---------歡迎使用學生管理系統-------------");
Scanner input = new Scanner(System.in);
StuMgr mgr = new StuMgr();
while (true) {
System.out.println("請選擇:1.添加學生 2.查詢學生 3.刪除學生 4.修改信息 5.退出系統");
int no = input.nextInt();
if (no == 1) {
while (true) {
System.out.print("請輸入學生學號:");
int num = input.nextInt();
System.out.print("請輸入學生姓名:");
String name = input.next();
System.out.print("請輸入學生性別:");
char sex = input.next().charAt(0);
Student s = new Student(num, name, sex);
mgr.addStudent(s);
System.out.print("是否繼續?");
String str = input.next();
if (str.equals("n")) {
break;
}
}
mgr.showAll();
} else if (no == 2) {
while(true) {
System.out.print("請輸入您要查詢的學生姓名:");
String name = input.next();
Student student = mgr.findStudent(name);
if (student != null) {
System.out.println("查詢結果:" + student);
}else {
System.out.println(name +"不存在");
}
System.out.print("是否繼續?");
String str = input.next();
if (str.equals("n")) {
break;
}
}
} else if (no == 3) {
} else if (no == 4) {
} else {
System.out.println("退出系統..");
break;
}
}
input.close();
}
練習三、
package ch005;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Task3 {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(1001,"張無忌",'男');
students[1] = new Student(1002,"趙敏",'女');
students[2] = new Student(1002,"周芷若",'女');
List<Student> list = new ArrayList<>();
for(int i=0; i<students.length; i++) {
list.add(students[i]);
}
Student s = new Student(1003,"小昭",'女');
list.add(s);
System.out.println("查看List集合:");
Iterator<Student> it = list.iterator();
while(it.hasNext()) {
Student sss = it.next();
System.out.println(sss);
}
}
}
Set接口
set是Collection的子接口,特點是: 無序不允許重複。 List是一隊人,可以重複; Set 一堆人不能重複。常用實現類HashSet, TreeSet , LinkedHashSet(按照插入的順序排列)
HashSet用法
基於Hash算法實現的集合,Hash產生一個唯一的編碼,也可以高效的查詢數據; 此類實現Set
接口,由哈希表(實際爲HashMap
實例)支持。 對集合的迭代次序不作任何保證; 特別是,它不能保證訂單在一段時間內保持不變。 這個類允許null
元素。
常用方法:
package ch005;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo2 {
public static void main(String[] args) {
Set set = new HashSet();
Student s1 = new Student(1001,"張無忌",'男');
Student s2 = new Student(1002,"趙敏",'女');
Student s3 = new Student(1002,"周芷若",'女');
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println("size:"+set.size());
System.out.println("是否爲空:"+set.isEmpty());
Iterator it = set.iterator();
while(it.hasNext()) {
Student s = (Student) it.next();
System.out.println(s);
}
}
}
如果指定存儲類型:
package ch005;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo2 {
public static void main(String[] args) {
Set<Student> set = new HashSet<>();
Student s1 = new Student(1001,"張無忌",'男');
Student s2 = new Student(1002,"趙敏",'女');
Student s3 = new Student(1002,"周芷若",'女');
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println("size:"+set.size());
System.out.println("是否爲空:"+set.isEmpty());
Iterator<Student> it = set.iterator();
while(it.hasNext()) {
Student s = it.next();
System.out.println(s);
}
}
}
HashSet的底層實現:
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable{
static final long serialVersionUID = -5024744406713321676L;
//HashSet存儲容器
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
* HashSet底層藉助HashMap存儲的,以key的方式存儲,value是一個虛擬對象 PRESENT
*/
public HashSet() {
map = new HashMap<>();
}
//存儲數據的實現方式
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
.......
}
TreeSet用法
有序的Set, 按照的自然順序排列的集合,數值類型的 1,2,3,4; 字符類型的: A,B,C,D; 漢字不會排序。
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("abc");
set.add("bbc");
set.add("abb");
set.add("abd");
System.out.println(set);
Set<Integer> set1 = new TreeSet<>();
set1.add(200);
set1.add(100);
set1.add(500);
set1.add(50);
System.out.println(set1);
Iterator<Integer> it = set1.iterator();
while(it.hasNext()) {
Integer num = it.next();
System.out.println(num);
}
}
如果存儲的是自定義類型,會出現什麼問題?
public static void main(String[] args) {
Set<Student> set = new TreeSet<>();
Student s1 = new Student(1001,"張無忌",'男');
Student s2 = new Student(1002,"趙敏",'女');
Student s3 = new Student(1002,"周芷若",'女');
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println(set.size());
}
出現如下異常:
Exception in thread "main" java.lang.ClassCastException: ch005.Student cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(Unknown Source)
at java.util.TreeMap.put(Unknown Source)
at java.util.TreeSet.add(Unknown Source)
at ch005.Demo2.main(Demo2.java:15)
類型轉換異常: Student對象不能轉換成Comparable。爲什麼出現這個異常? TreeSet是有序的Set,有序是按照什麼來進行排序的?
public TreeSet() {
this(new TreeMap<E,Object>());
}
要排序就要進行比較,TreeSet藉助於TreeMap實現的,TreeMap的排序方式是兩個接口是的
①Comparable 表示可以比較的, Closeable可關閉的,對象具備的特徵;同學之間可以比較身高、體重,
②Comparator 比較器: 藉助於外部的尺度,進行比較; 對象之間不好比較: 貨車座位有序的,藉助於車票的序號,電影院容器也是有序的;藉助於座位號比較的。
使用Comparable比較器:
package ch005;
public class Student implements Comparable<Student>{
private Integer no;
private String name;
private char sex;
public Integer getNo() {
return no;
}
public void setNo(Integer no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student [no=" + no + ", name=" + name + ", sex=" + sex + "]";
}
public Student(int no, String name, char sex) {
super();
this.no = no;
this.name = name;
this.sex = sex;
}
public Student() {
super();
}
@Override
public int compareTo(Student o) {
//用當前對象的某個屬性和穿過來的參數對象的屬性進行比較
//比較的結果 > 0 表示當前對象的值大於參數
//比較的結果 == 0 說明兩個對象比較的屬性相同
//比較的結果 < 0 當前對象小於參數的數據
int result = this.name.compareTo(o.getName());
if(result == 0) {
//如果名字相同再按照編號比較
return this.getNo().compareTo(o.getNo());
}
System.out.println(name+"和"+o.getName()+"比較的結果:"+result);
return result;
}
}
測試類:
//a=97 b=98 c=99 d=100 e=101 f=102 g=103 h=104 i=105 j=106
public static void main(String[] args) {
Set<Student> set = new TreeSet<>();
Student s1 = new Student(1001,"jack",'男');
Student s2 = new Student(1002,"rose",'女');
Student s3 = new Student(1003,"bluce",'女');
Student s4 = new Student(1004,"rose",'女');
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
System.out.println(set.size());
for(Student s: set) {
System.out.println(s);
}
}
使用Comparator外部的比較器:胡潤百富榜,也稱爲殺豬榜,就是一個外部的比較器,土豪之間不好意思比較誰的資產更多。
創建一個實體類,不實現Comparable接口,自身不具備比較性,要藉助於第三方的比較器:
package ch005;
public class Dept {
private Integer deptNo;
private String deptName;
public Dept() {
super();
}
public Dept(Integer deptNo, String deptName) {
super();
this.deptNo = deptNo;
this.deptName = deptName;
}
public Integer getDeptNo() {
return deptNo;
}
public void setDeptNo(Integer deptNo) {
this.deptNo = deptNo;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
@Override
public String toString() {
return "Dept [deptNo=" + deptNo + ", deptName=" + deptName + "]";
}
}
測試類:
package ch005;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
class MyComparator implements Comparator<Dept>{
@Override
public int compare(Dept o1, Dept o2) {
//1.按照名字順序比較
//return o1.getDeptName().compareTo(o2.getDeptName());
//2.按照名字的長度進行比較
int x = o1.getDeptName().length();
int y = o2.getDeptName().length();
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
}
public class Demo2 {
//a=97 b=98 c=99 d=100 e=101 f=102 g=103 h=104 i=105 j=106
public static void main(String[] args) {
Set<Dept> set = new TreeSet<>( new MyComparator() );
set.add(new Dept(1001,"sell"));
set.add(new Dept(1002,"service"));
set.add(new Dept(1003,"abc"));
for (Dept dept : set) {
System.out.println(dept);
}
}
}
自定義的比較器核心代碼只有一行: return o1.getDeptName().compareTo(o2.getDeptName()); 但是java語言是比較規範的語言,所以我們需要完整的定義一個類。就相當於自己吃飯我們要先種菜、收菜、自己加工等一整套流程。但是核心的業務是吃飯而已;如何優化整個流程: 點外賣。簡化了所有的流程,只關注核心業務。在jdk1.8中提供了Lambda表達式來簡化編碼。
簡化寫法: 使用Lambda表達式,
public static void main(String[] args) {
Set<Dept> set =
new TreeSet<>( (o1,o2) -> o1.getDeptName().compareTo(o2.getDeptName()) );
set.add(new Dept(1001,"sell"));
set.add(new Dept(1002,"service"));
set.add(new Dept(1003,"abc"));
for (Dept dept : set) {
System.out.println(dept);
}
}
只關注核心的一句話: 比較部門名稱,簡化了創建類的格式.
Map接口
map存儲的是鍵值對,key —value的映射關係; 所以稱爲雙列集合,List就是單列集合; map的key不允許重複,value可以重複。Map接口的常見實現類: HashMap, TreeMap、 LinkedHashMap。需要注意的是都不是線程安全對象,線程安全的Map是Hashtable。在juc包中還有線程安全的實現類: ConcurrentHashMap.
HashMap用法
構造方法:
Constructor and Description |
---|
HashMap() 構造一個空的 HashMap ,默認初始容量(16)和默認負載係數(0.75)。 |
HashMap(int initialCapacity) 構造一個空的 HashMap 具有指定的初始容量和默認負載因子(0.75)。 |
HashMap(int initialCapacity, float loadFactor) 構造一個空的 HashMap 具有指定的初始容量和負載因子。 |
HashMap(Map m) 構造一個新的 HashMap 與指定的相同的映射 Map 。 |
底層用的也是數組,數組的長度是16,負載因子0.75; 當容量達到75%的時候,容器要擴容。16*75%=12,意思是元素到達12的時候觸發容器擴容機制。
案例:
public static void main(String[] args) {
Map<String,Student> map = new HashMap<>();
Student s1 = new Student(1001,"張無忌",'男');
Student s2 = new Student(1002,"趙敏",'女');
Student s3 = new Student(1002,"周芷若",'女');
//映射關係: 鹿晗-->關曉彤 黃曉明---楊穎 鄧超-->孫儷 劉強東---章澤天
map.put("a", s1);
map.put("b", s2);
map.put("c", s3);
System.out.println("size:"+map.size());
System.out.println("是否包括(趙敏):"+map.containsKey("b"));
System.out.println("是否包括對象:"+map.containsValue(s2));
Student s = map.get("c");
System.out.println("根據key獲取對象:"+s);
//map的遍歷: 1.根據key獲取value,先獲取所有的key,然後再根據key獲取value
Set<String> keys = map.keySet();
Iterator<String> it = keys.iterator();
while(it.hasNext()) {
String key = it.next();
Student stn = map.get(key);
System.out.println(key+"-->"+stn.getName());
}
}
LinkedList用的靜態內部類Node存儲的,一個Node包括三部分; 類似Map一個元素包括兩部分: key-value, 如何管理方便: 可以借鑑LinkedList的存儲 機制,也創建一個類存儲key和value。這個類叫Entry: 取“條目”的意思。查看HashMap的源碼:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
.......
HashMap使用Node節點封裝key和value; 鍵值對,注意一個細節問題: next,表示下一個節點的引用;從中可以猜測HashMap中應該有一個單向鏈表結構。案例:
public static void main(String[] args) {
Map<String,Student> map = new HashMap<>();
Student s1 = new Student(1001,"張無忌",'男');
Student s2 = new Student(1002,"趙敏",'女');
Student s3 = new Student(1002,"周芷若",'女');
map.put("a", s1);
map.put("b", s2);
map.put("c", s3);
Set<Entry<String, Student>> entrySet = map.entrySet();
for (Entry<String, Student> entry : entrySet) {
String key = entry.getKey();
Student value = entry.getValue();
System.out.println(key+"---"+value);
}
}
TreeMap
有序的Map,自定義排序參考TreeSet實現。
public static void main(String[] args) {
Map<String,Student> map = new TreeMap<>();
Student s1 = new Student(1001,"張無忌",'男');
Student s2 = new Student(1002,"趙敏",'女');
Student s3 = new Student(1002,"周芷若",'女');
map.put("xxx", s1);
map.put("b34b", s2);
map.put("a12", s3);
Set<Entry<String, Student>> entrySet = map.entrySet();
for (Entry<String, Student> entry : entrySet) {
String key = entry.getKey();
Student value = entry.getValue();
System.out.println(key+"---"+value);
}
}
LinkedHashMap
按照添加的順序存儲元素,基於鏈表, 先放的元素作爲前端節點;後續元素依次連接到前面元素。
public static void main(String[] args) {
Map<String,Student> map = new LinkedHashMap<>();
Student s1 = new Student(1001,"張無忌",'男');
Student s2 = new Student(1002,"趙敏",'女');
Student s3 = new Student(1002,"周芷若",'女');
map.put("a12", s3);
map.put("b34b", s2);
map.put("xxx", s1);
Set<Entry<String, Student>> entrySet = map.entrySet();
for (Entry<String, Student> entry : entrySet) {
String key = entry.getKey();
Student value = entry.getValue();
System.out.println(key+"---"+value);
}
}
jdk1.8對集合的增強
jdk 1.8對於集合各種操作簡化處理的學習
- 集合轉爲stream
- 面向流的filter(有返回值true,false),排序sorted,map[mapToInt……,無返回值,把當前值映射成爲另一個對象],distinct,distinctbyKey[重寫filter參數類型的方法 Predicate distinctByKey(Function<? super T, Object> keyExtractor)]
- 處理後的stream轉爲集合collect collectors.toList() Collectors.toSet(),Collectors.toMap(Person::getName,Function.identity()) Collectors.groupingBy((f)->……/Person::getName)
- 處理後的流取第一個值findFirst optional類型 ispresent判斷
- 多個流的連接stream.of
- 操作集合的降維(降一維)flatMap(Function.identity)
stream用法
流的意思,可以向操作文件流一樣操作集合中的數據集; 也可以理解把集合中的數據當做數據庫。
Stream操作的三個步驟
- 創建stream
- 中間操作(過濾、map)
- 終止操作
首先創建備用的List集合:
List<String> stringList = new ArrayList<>();
stringList.add("ddd2");
stringList.add("aaa2");
stringList.add("bbb1");
stringList.add("aaa1");
stringList.add("bbb3");
stringList.add("ccc");
stringList.add("bbb2");
stringList.add("ddd1");
Filter(過濾)
過濾通過一個predicate接口來過濾並只保留符合條件的元素,該操作屬於中間操作,所以我們可以在過濾後的結果來應用其他Stream操作(比如forEach)。forEach需要一個函數來對過濾後的元素依次執行。forEach是一個最終操作,所以我們不能在forEach之後來執行其他Stream操作。
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
Student s1 = new Student(1001,"張無忌",'男');
Student s2 = new Student(1002,"趙敏",'女');
Student s3 = new Student(1003,"周芷若",'女');
list.add(s1);
list.add(s2);
list.add(s3);
/*
* list.stream().filter(s -> s.getNo() == 1002) .forEach(System.out::println);
*/
List<Student> list2 = list.stream() //1.獲取stream
.filter(s -> s.getNo() >= 1002)//2.過濾
.collect(Collectors.toList());//3.轉換成List
for (Student student : list2) {
System.out.println(student);
}
}
查找姓名長度爲2的元素
public static void main(String[] args) {
//查找姓名長度爲2的元素
List<Student> list = new ArrayList<>();
Student s1 = new Student(1001,"張無忌",'男');
Student s2 = new Student(1002,"趙敏",'女');
Student s3 = new Student(1003,"周芷若",'女');
list.add(s1);
list.add(s2);
list.add(s3);
/*
for (Student student : list) {
if(student.getName().length() == 2) {
System.out.println(student);
}
}
*/
Stream<Student> filter = list.stream().filter(s -> s.getName().length() ==2);
List<Student> list2 = filter.collect(Collectors.toList());
for(Student s: list2) {
System.out.println(s);
}
}
Sorted(排序)
排序是一個 中間操作,返回的是排序好後的 Stream。如果你不指定一個自定義的 Comparator 則會使用默認排序。
// 測試 Sort (排序)
stringList
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);// aaa1 aaa2
需要注意的是,排序只創建了一個排列好後的Stream,而不會影響原有的數據源,排序之後原數據stringCollection是不會被修改的:
System.out.println(stringList);// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Map(映射)
中間操作 map 會將元素根據指定的 Function 接口來依次將元素轉成另外的對象。
下面的示例展示了將字符串轉換爲大寫字符串。你也可以通過map來將對象轉換成其他類型,map返回的Stream類型是根據你map傳遞進去的函數的返回值決定的。
// 測試 Map 操作
stringList.stream().map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match(匹配)
Stream提供了多種匹配操作,允許檢測指定的Predicate是否匹配整個Stream。所有的匹配操作都是 最終操作 ,並返回一個 boolean 類型的值。
// 測試 Match (匹配)操作
boolean anyStartsWithA = stringList.stream().anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA = stringList.stream().allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ = stringList.stream().noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
Count(計數)
計數是一個 最終操作,返回Stream中元素的個數,返回值類型是 long。
//測試 Count (計數)操作
long startsWithB = stringList.stream().filter((s) -> s.startsWith("b")).count();
System.out.println(startsWithB); // 3
Reduce(規約)
這是一個 最終操作 ,允許通過指定的函數來講stream中的多個元素規約爲一個元素,規約後的結果是通過Optional 接口表示的:
//測試 Reduce (規約)操作
Optional<String> reduced = stringList.stream().sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
這個方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然後依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字符串拼接、數值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相當於Integer sum = integers.reduce(0, (a, b) -> a+b);
也有沒有起始值的情況,這時會把 Stream 的前面兩個元素組合起來,返回的是 Optional。
// 字符串連接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 無起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 過濾,字符串連接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);
上面代碼例如第一個示例的 reduce(),第一個參數(空白字符)即爲起始值,第二個參數(String::concat)爲 BinaryOperator。這類有起始值的 reduce() 都返回具體的對象。而對於第四個示例沒有起始值的 reduce(),由於可能沒有足夠的元素,返回的是 Optional,請留意這個區別。更多內容查看: IBM:Java 8 中的 Streams API 詳解
Parallel Streams(並行流)
前面提到過Stream有串行和並行兩種,串行Stream上的操作是在一個線程中依次完成,而並行Stream則是在多個線程上同時執行。
下面的例子展示了是如何通過並行Stream來提升性能:
首先我們創建一個沒有重複元素的大表:
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
我們分別用串行和並行兩種方式對其進行排序,最後看看所用時間的對比。
Sequential Sort(串行排序)
//串行排序
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
輸出結果:
1000000
sequential sort took: 709 ms//串行排序所用的時間
Parallel Sort(並行排序)
//並行排序
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
輸出結果:
1000000
parallel sort took: 475 ms//串行排序所用的時間
上面兩個代碼幾乎是一樣的,但是並行版的快了 50% 左右,唯一需要做的改動就是將 stream()
改爲parallelStream()
。
ream上的操作是在一個線程中依次完成,而並行Stream則是在多個線程上同時執行。
下面的例子展示了是如何通過並行Stream來提升性能:
首先我們創建一個沒有重複元素的大表:
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
我們分別用串行和並行兩種方式對其進行排序,最後看看所用時間的對比。
Sequential Sort(串行排序)
//串行排序
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
輸出結果:
1000000
sequential sort took: 709 ms//串行排序所用的時間
Parallel Sort(並行排序)
//並行排序
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
輸出結果:
1000000
parallel sort took: 475 ms//串行排序所用的時間
上面兩個代碼幾乎是一樣的,但是並行版的快了 50% 左右,唯一需要做的改動就是將 stream()
改爲parallelStream()
。