一、概述

1、介紹

  為什麼出現集合?
  答:面嚮物件語言對事物的體現都是以物件的形式,所以為了方便對多個物件的操作,對物件進行儲存,集合就是儲存物件最常用的一種方式。
  陣列和集合類同是容器,有何不同?
  答:陣列雖然也可以儲存物件,但長度是固定的,集合長度是可變的。陣列中可以儲存基本資料型別,集合中只能儲存物件(引用型別,基本型別的包裝型別)。
  為什麼會出現這麼多的容器呢?
  答:因為每一個容器對資料的儲存方式都有不同。這個儲存方式稱之為:資料結構。
  什麼是迭代器呢?
  答:其實就是集合的取出元素的方式。

2、集合框架

  簡圖:

  Collection:

  Map:

二、Collection<E>(jdk1.2)

  集合中儲存的都是物件的引用(地址)。

1、常用方法

  add(Object obj):新增。
  addAll(Collection coll):新增。
  int size():獲取有效元素的個數。
  void clear():清空集合。
  boolean isEmpty():是否是空集合。
  boolean contains(Object obj):是否包含某個元素,通過元素的equals方法來判斷是否是同一個物件。
  boolean containsAll(Collection c):也是呼叫元素的equals方法來比較的。兩個集合的元素挨個比較。

  boolean remove(Object obj) :刪除,通過元素的equals方法判斷是否是要刪除的那個元素。只會刪除找到的第一個元素。
  boolean removeAll(Collection coll):刪除,取當前集合的差集。
  boolean retainAll(Collection c):取交集,把交集的結果存在當前集合中,不影響c。
  boolean equals(Object obj):集合是否相等。
  Object[] toArray():轉成物件陣列。
  hashCode():獲取集合物件的雜湊值。
  iterator():返回迭代器物件,用於集合遍歷。

  程式碼示例:基本方法

 1 public class Main {
2 public static void main(String[] args) {
3 Collection<Object> coll = new ArrayList<>();
4
5 coll.add("AA");
6 coll.add("BB");
7 coll.add(123); // 自動裝箱
8 coll.add(new Date());
9
10 System.out.println(coll.size()); // 4
11
12 Collection<Object> coll1 = new ArrayList<>();
13 coll1.add(456);
14 coll1.add("CC");
15 coll1.add(123);
16
17 coll.addAll(coll1);
18 System.out.println(coll);
19
20 coll.clear(); // 清空集合元素
21
22 System.out.println(coll.isEmpty()); // true
23 }
24 }

  程式碼示例:初始化類

 1 // 預先定義的實體類
2 public class Person {
3
4 private String name;
5 private int age;
6
7 // 無參構造器
8 // 有參構造器
9 // getter & setter
10 // toString()
11
12 @Override
13 public boolean equals(Object o) {
14 System.out.println("Person equals()....");
15 if (this == o) return true;
16 if (o == null || getClass() != o.getClass()) return false;
17 Person person = (Person) o;
18 return age == person.age &&
19 Objects.equals(name, person.name);
20 }
21
22 @Override
23 public int hashCode() {
24 return Objects.hash(name, age);
25 }
26 }
27
28 // 初始化的集合.下面的程式碼示例,集合都是這個.
29 public Collection<Object> init() {
30 Collection<Object> coll = new ArrayList<>();
31
32 coll.add(123);
33 coll.add(456);
34 coll.add(new Person("Jerry", 20));
35 coll.add("Tom");
36 coll.add(false);
37
38 return coll;
39 }
 1 public void method1() {
2 System.out.println(coll.hashCode());
3
4 // 判斷當前集合中是否包含obj
5 // 想要按 Person 物件的屬性來比較,需要複寫 equals(Object o)
6 // false --> true
7 System.out.println(coll.contains(new Person("Jerry", 20)));
8
9 // 當且僅當形參中的所有元素都存在於當前集合中.true
10 // true
11 System.out.println(coll.containsAll(Arrays.asList(123, 456)));
12
13 coll.remove(1234);
14 coll.remove(new Person("Jerry", 20));
15 // 差集:A - B
16 coll.removeAll(Arrays.asList(123, 4567));
17
18 System.out.println(coll);
19
20 // 兩個集合完全相同,包括順序相同(這個又具體子類決定)
21 // System.out.println(coll.equals(coll1));
22
23 // 交集
24 coll.retainAll(Arrays.asList(123, 666));
25 System.out.println(coll);
26 }
27
28 // 結果
29 -1200490100
30 Person equals()....
31 Person equals()....
32 Person equals()....
33 true
34 true
35 Person equals()....
36 Person equals()....
37 Person equals()....
38 [456, Tom, false]
39 []

  結果分析:
  ①判定一個集合中是否包含某個物件,contains(Object o)方法,會呼叫equals(Object o)去比較,所以需要複寫equals(Object o)方法。
  ②物件Person是第3個被加入到集合中的,比較的時候需要一個一個比較,所以equals()方法被呼叫了3次。
  ③remove(Object o)時同樣equals()方法被呼叫了3次。

  程式碼示例:集合 <--> 陣列

1 public void method2() {
2 // 集合 --> 陣列
3 final Object[] objects = coll.toArray();
4 final Object[] array = coll.toArray(new Object[0]);
5
6 // 陣列 --> 集合
7 final List<String> strings = Arrays.asList("A", "B", "C");
8 final List<Integer> integers = Arrays.asList(123, 456);
9 }

2、Iterator<E>(迭代器)

  GOF給迭代器模式的定義為:提供一種方法訪問一個容器(container)物件中各個元素,而又不需暴露該物件的內部細節。迭代器模式,就是為容器而生。類似於"公交車上的售票員"、"火車上的乘務員"、"空姐"。
  一種集合的取出元素的方式。定義在集合的內部,用於直接訪問集合內 部的元素,是一個內部類。集合物件每次呼叫iterator()方法都得到一個全新的迭代器物件,預設遊標都在集合的第一個元素之前。Iterator主要用於遍歷Collection,不包括map。
  用foreach遍歷Collection,底層還是使用的迭代器。迭代器原理:

  JDK8原始碼:

  程式碼示例:迭代器的使用

 1 // 注意:這裡是迭代器裡的remove,不是集合裡面的。
2 public void method3() {
3 Iterator<Object> iterator = coll.iterator();
4 while (iterator.hasNext()) {
5 // next():①指標下移 ②將下移以後集合位置上的元素返回
6 System.out.println(iterator.next());
7 }
8
9 // 返回一個全新的迭代器
10 iterator = coll.iterator();
11 while (iterator.hasNext()) {
12 // iterator.remove(); 報錯IllegalStateException
13 final Object next = iterator.next();
14
15 if ("Tom".equals(next)) {
16 iterator.remove();
17 // iterator.remove(); 報錯IllegalStateException
18 }
19 }
20 }

3、List<E>、Queue<E>、Set<E>(重點)

  List<E>元素是有序的,可重複,因為該集合體繫有索引。實現類:
  ArrayList:底層使用的陣列資料結構。特點:查詢速度很快,但是增刪稍慢(會移動後面的元素)。初始長度10,50%延長。執行緒不同步的。jdk1.2
  LinkedList:底層使用的連結串列資料結構。特點:增刪速度很快,查詢稍慢。執行緒不同步的。
  Vector:底層使用的陣列資料結構。初始長度10,100%延長。執行緒同步的。後被ArrayList替代了。jdk1.0

  Queue<E>有序的,可重複。實現類:
  Deque<E>(介面):
  LinkedList:底層使用的連結串列資料結構。特點:增刪速度很快,查詢稍慢。執行緒不同步的。

  Set<E>元素是無序的,不重複。沒有索引。實現類:
  HashSet:底層使用的HashMap(陣列+連結串列)資料結構。無序的,不可重複,執行緒不同步。可以儲存null值。
  TreeSet:底層使用的TreeMap(排序二叉樹,紅黑樹)資料結構。可以對set集合中的元素按指定屬性進行排序。有序的。查詢速度比List快。

三、List<E>

1、ArrayList<E>

  程式碼示例:迭代器的使用

 1 // 其他常用方法不演示
2 public class Main {
3 public static void main(String[] args) {
4 List<String> list = new ArrayList<>();
5 list.add("A");
6 list.add("B");
7 list.add("C");
8 list.add("D");
9
10 final Iterator<String> iterator = list.iterator();
11 while (iterator.hasNext()) {
12 System.out.print(iterator.next());
13 }
14 }
15 }
16
17 // 結果
18 // A B C D

2、Vector<E>

  列舉就是Vector特有的取出方式,和迭代器很像,其實是一樣的,由於名稱過長,後被迭代器取代了。

 1 public static void main(String[] args) {
2 Vector<String> vector = new Vector<>();
3
4 vector.add("A");
5 vector.add("B");
6
7 // 返回此向量的元件的列舉.
8 final Enumeration<String> elements = vector.elements();
9
10 while (elements.hasMoreElements()) {
11 System.out.println(elements.nextElement());
12 }
13 }
14
15 // 結果
16 // A B

3、Stack<E>(棧)

 1 // 先進後出
2 public class Main {
3 public static void main(String[] args) {
4 Stack<String> stack = new Stack<>();
5
6 // 壓棧
7 stack.push("A");
8 stack.push("B");
9 stack.push("C");
10
11 while (!stack.empty()) {
12 // 從棧頂彈出一個.會刪除元素
13 System.out.println(stack.pop());
14 }
15
16 // 從棧頂彈出一個.不會刪除元素
17 // stack.peek();
18 }
19 }
20
21 // 結果
22 C B A

四、Queue<E>

1、Queue<E>(佇列)

 1 // 先進先出
2 public class Main {
3 public static void main(String[] args) {
4 Queue<Integer> queue = new LinkedList<>();
5
6 // 新增.入隊
7 queue.offer(1);
8 queue.offer(10);
9 queue.offer(5);
10 queue.offer(9);
11 System.out.println(queue);
12
13 // 出隊
14 final Integer poll = queue.poll();
15 System.out.println(poll);
16 System.out.println(queue);
17
18 // 獲取.隊頭
19 final Integer peek = queue.peek();
20 System.out.println(peek);
21 System.out.println(queue);
22 }
23 }
24
25 // 結果
26 [1, 10, 5, 9]
27 1
28 [10, 5, 9]
29 10
30 [10, 5, 9]

2、Deque<E>(雙端佇列)

3、LinkedList<E>(雙向連結串列)

  對於頻繁的插入或刪除元素的操作,建議使用LinkedList類,效率較高。

  特有方法
  addFirst():在第一個位置新增元素。
  addLast()
  getFirst():獲取第一個元素,但不刪除元素。若沒有,出現異常。
  getLast()
  removeFirst():獲取元素,但是刪除元素。若沒有,出現異常。
  removeLast()

  在jdk1.6以後出現了替代方法。
  offerFirst():在第一個位置新增元素。
  offerLast()
  peekFirst():獲取元素,但不刪除元素。若沒有,返回null。
  peekLast()
  pollFirst():獲取元素,但是刪除元素。若沒有,返回null。
  pollLast()

五、Set<E>

1、介紹

  Set<E>元素是不重複。沒有索引。實現類:
  HashSet:底層使用的HashMap(陣列+連結串列)資料結構。無序的,不可重複,執行緒不安全。可以儲存null值。
  LinkedHashSet:底層使用的 LinkedHashMap。無序的,不可重複。
  TreeSet:底層使用的TreeMap(排序二叉樹,紅黑樹)資料結構。可以對set集合中的元素按指定屬性進行排序。有序的。查詢速度比List快。

  以HashSet為例理解無序、不重複:
  無序的:不等於隨機性(並不是說列印的順序沒有按照新增的順序),是指的儲存的資料在底層陣列中並非按照陣列索引的順序依次新增,而是根據資料的雜湊值確定的索引位置。
  不重複:呼叫物件equals()方法保證相同元素只新增一次。
  要求:向Set(主要指:HashSet、LinkedHashSet)中新增的資料,其所在的類一定要重寫hashCode()和equals()。重寫的hashCode()和equals()儘可能保持一致性:相等的物件必須具有相等的雜湊碼。

  重寫 hashCode() 方法的基本原則:
  (1)程式執行時,同一個物件多次呼叫 hashCode() 方法應該返回相同的值。
  (2)當兩個物件的 equals() 方法比較返回 true 時,這兩個物件的 hashCode() 方法的返回值也應相等。
  (3)物件中用作 equals() 方法比較的 Field,都應該用來計算 hashCode 值。

  重寫 equals() 方法的基本原則:
  當一個類有自己特有的"邏輯相等"概念,需要重寫equals()的時候,總是要重寫hashCode(),根據一個類的equals方法(改寫後),兩個截然不同的例項有可能在邏輯上是相等的,但是,根據Object.hashCode()方法,它們僅僅是兩個物件。因此,違反了"相等的物件必須具有相等的雜湊碼"。
  結論:複寫equals方法的時候一般都需要同時複寫hashCode方法。通常參與計算hashCode的物件的屬性也應該參與到equals()中進行計算。

2、HashSet<E>

  HashSet 按 Hash 演算法來儲存集合中的元素,因此具有很好的存取、查詢和刪除效能。
  底層資料結構:看JDK8的原始碼,HashSet底層是一個HashMap。而HashMap底層是陣列+連結串列(7),陣列+連結串列+紅黑樹(8)。
  元素新增過程:由於HashSet底層就是HashMap,所以它的原理只需要瞭解HashMap即可。

 1 // API使用
2 public class Main {
3 public static void main(String[] args) {
4 Set<String> set1 = new HashSet<>();
5 Set<String> set2 = new HashSet<>();
6
7 set1.add("a");
8 set1.add("b");
9 set1.add("c");
10
11 set2.add("c");
12 set2.add("d");
13 set2.add("e");
14
15 // 取交集
16 set1.retainAll(set2);
17 System.out.println(set1); // [c]
18 // 取並集
19 // set1.addAll(set2);
20 // System.out.println(set1); // [a, b, c, d, e]
21 // 取差集
22 // set1.removeAll(set2);
23 // System.out.println(set1); // [a, b]
24 }
25 }

3、LinkedHashSet<E>

  作為HashSet的子類,LinkedHashSet 根據元素的 hashCode 值來決定元素的儲存位置(依然是無序的), 但它同時使用雙向連結串列維護元素的新增次序,這使得元素看起來是以插入順序儲存的。也可以按照新增的順序遍歷。
  對於頻繁的遍歷操作,LinkedHashSet效率高於HashSet。LinkedHashSet插入效能略低於 HashSet。
  程式碼示例:

 1 public class Main {
2 public static void main(String[] args) {
3 Set set = new LinkedHashSet();
4 set.add("AA");
5 set.add(456);
6 set.add(456);
7
8 set.add(new User("劉德華", 60));
9
10 Iterator iterator = set.iterator();
11 while (iterator.hasNext()) {
12 System.out.println(iterator.next());
13 }
14 }
15 }
16
17 // 結果:按照新增的順序遍歷.
18 AA
19 456
20 User{name='劉德華', age=60}
21
22 // 若Set set = new HashSet();則遍歷不按照新增的順序

  底層結構:

4、TreeSet<E>

  底層是排序二叉樹,所以①必須是相同型別的物件。②必須可排序。自然排序(實現Comparable介面)和定製排序(Comparator)。
  自然排序中,判斷兩個物件是否相同的標準為:compareTo()返回0,不是equals()。
  定製排序中,判斷兩個物件是否相同的標準為:compare()返回0。不是equals()。
  若compareTo始終返回0,則add始終只有一個元素。因為後面的都認為與前面的相同,因此沒有加入進來。判斷兩個物件是否相等的標準是compareTo返回值相同。
  底層資料結構:看JDK8的原始碼,TreeSet底層是一個TreeMap。採用紅黑樹的儲存結構。有序的。查詢速度比List快。
  元素新增過程:
  程式碼示例:讓User具有可比性,自然排序:Comparable

 1 public class User implements Comparable<User> {
2 private String name;
3 private int age;
4
5 // 無參構造器
6 // 有參構造器
7 // getter & setter
8 // toString()
9 // 可以不復寫equals、hashCode
10
11 @Override
12 public int compareTo(User user) {
13 System.out.println(this.name + "-------------" + user.getName());
14
15 if (this.age > user.age) {
16 return 1;
17 }
18 if (this.age < user.age) {
19 return -1;
20 }
21 return 0;
22 }
23 }
24
25 // 測試類
26 public static void main(String[] args) {
27 TreeSet<User> treeSet = new TreeSet<>();
28
29 treeSet.add(new User("java1", 30));
30 treeSet.add(new User("java2", 20));
31 treeSet.add(new User("java3", 31));
32 treeSet.add(new User("java4", 60));
33
34 final Iterator<User> iterator = treeSet.iterator();
35 while (iterator.hasNext()) {
36 final User user = iterator.next();
37 System.out.println(user.getName() + "---------" + user.getAge());
38 }
39 }
40
41 // 結果
42 java1-------------java1 // 第一次
43 java2-------------java1 // 2和1比,2在左邊
44 java3-------------java1 // 3和1比,3在右邊
45 java4-------------java1 // 4和1比,4在右邊
46 java4-------------java3 // 4和3比,4在右邊
47 java2---------20
48 java1---------30
49 java3---------31
50 java4---------60

  結果分析:不難理解列印結果的比較次數。

  程式碼示例:指定比較器,定製排序:Comparator

 1 // 比較器
2 public static void main(String[] args) {
3 final Comparator<User> comparator = new Comparator<User>() {
4 // 按照年齡從小到大排列
5 @Override
6 public int compare(User o1, User o2) {
7 return Integer.compare(o1.getAge(), o2.getAge());
8 }
9 };
10
11 TreeSet<User> treeSet = new TreeSet<>(comparator);
12
13 treeSet.add(new User("java1", 30));
14 treeSet.add(new User("java2", 20));
15 treeSet.add(new User("java3", 31));
16 treeSet.add(new User("java4", 60));
17 treeSet.add(new User("java5", 60));
18
19 final Iterator<User> iterator = treeSet.iterator();
20 while (iterator.hasNext()) {
21 final User user = iterator.next();
22 System.out.println(user.getName() + "---------" + user.getAge());
23 }
24 }
25
26 // 結果
27 java2---------20
28 java1---------30
29 java3---------31
30 java4---------60

六、Map<K,V>

1、介紹(重點)

  雙列資料,儲存key-value對的資料。用作鍵的物件必須實現hashCode方法和equals方法。實現類:
  HashMap:底層使用的(陣列+連結串列7+紅黑樹8)資料結構。特點:可以存入null鍵null值。執行緒不同步,效率高。jdk1.2
  Hashtable:底層使用的(陣列+連結串列)資料結構。特點:不可以存入null鍵null值。執行緒同步,效率低。jdk1.0
  TreeMap:底層使用的(排序二叉樹,紅黑樹)資料結構。實現了SortedMap有序序列介面,特點:可以用於給map集合中的鍵按指定屬性進行排序。執行緒不同步。
  WeakHashMap:底層使用的(陣列+連結串列)資料結構。特點:鍵是"弱鍵"。

2、HashMap<K,V>

  HashMap中的Entry物件是無序排列的。此類不保證對映的順序,特別是它不保證該順序恆久不變。

  程式碼示例:三種遍歷方式

 1 public class Main {
2 public static void main(String[] args) {
3 Map<String, String> map = new HashMap<>();
4 map.put("A", "1");
5 map.put("B", "2");
6 map.put("c", "3");
7
8 // 1.獲取鍵集:keySet()
9 final Set<String> keySet = map.keySet();
10 for (String key : keySet) {
11 System.out.println("keySet()----" + key + "----" + map.get(key));
12 }
13
14 // 2.獲取值集:values()
15 final Collection<String> values = map.values();
16 for (String value : values) {
17 System.out.println("values()----" + value);
18 }
19
20 // 3.獲取entry集:entrySet()
21 final Set<Map.Entry<String, String>> entries = map.entrySet();
22 for (Map.Entry<String, String> entry : entries) {
23 System.out.println("entrySet()----" + entry.getKey() + "----" + entry.getValue());
24 }
25 }
26 }

3、Hashtable<K,V>

4、TreeMap<K,V>

  向TreeMap中新增key-value,要求key①必須是相同型別的物件。②必須可排序。用於給map集合中的鍵按指定屬性進行排序。
  程式碼示例:讓User具有可比性,自然排序:Comparable

 1 public class User implements Comparable<User> {
2 private String name;
3 private int age;
4
5 // 無參構造器
6 // 有參構造器
7 // getter & setter
8 // toString()
9 // 可以不復寫equals、hashCode
10
11 // 年齡從小到大排列
12 @Override
13 public int compareTo(User user) {
14 System.out.println(this.name + "---------" + user.getName());
15 return Integer.compare(this.age, user.age);
16 }
17 }
18
19 // 測試類
20 public static void main(String[] args) {
21 TreeMap<User, Integer> treeMap = new TreeMap<>();
22 User u1 = new User("Tom", 23);
23 User u2 = new User("Jerry", 32);
24 User u3 = new User("Jack", 20);
25 User u4 = new User("Rose", 18);
26
27 treeMap.put(u1, 98);
28 treeMap.put(u2, 89);
29 treeMap.put(u3, 76);
30 treeMap.put(u4, 100);
31
32 for (Map.Entry<User, Integer> entry : treeMap.entrySet()) {
33 System.out.println(entry.getKey() + "---->" + entry.getValue());
34 }
35 }
36
37 // 結果
38 Tom---------Tom
39 Jerry---------Tom
40 Jack---------Tom
41 Rose---------Tom
42 Rose---------Jack
43 User{name='Rose', age=18}---->100
44 User{name='Jack', age=20}---->76
45 User{name='Tom', age=23}---->98
46 User{name='Jerry', age=32}---->89

  結果分析:按鍵 User 指定屬性 age 排序,不難理解列印結果的比較次數。排序二叉樹:

  程式碼示例:指定比較器,定製排序:Comparator

 1 // 比較器
2 public static void main(String[] args) {
3 TreeMap<User, Integer> treeMap = new TreeMap<>(new Comparator<User>() {
4 @Override
5 public int compare(User u1, User u2) {
6 return Integer.compare(u1.getAge(), u2.getAge());
7 }
8 });
9
10 User u1 = new User("Tom", 23);
11 User u2 = new User("Jerry", 32);
12 User u3 = new User("Jack", 20);
13 User u4 = new User("Rose", 18);
14
15 treeMap.put(u1, 98);
16 treeMap.put(u2, 89);
17 treeMap.put(u3, 76);
18 treeMap.put(u4, 100);
19
20 for (Map.Entry<User, Integer> entry : treeMap.entrySet()) {
21 System.out.println(entry.getKey() + "---->" + entry.getValue());
22 }
23 }
24
25 // 結果.和自然排序的結果一致.
26 User{name='Rose', age=18}---->100
27 User{name='Jack', age=20}---->76
28 User{name='Tom', age=23}---->98
29 User{name='Jerry', age=32}---->89

5、Properties

  常用來處理配置檔案。key和value都是String型別。
  程式碼示例:讀取屬性檔案

 1 // jdbc.properties
2 name=Tom
3 password=abc123
4
5 // 未關閉資源
6 public static void main(String[] args) throws Exception {
7 Properties pros = new Properties();
8
9 FileInputStream fis = new FileInputStream("jdbc.properties");
10 pros.load(fis);
11
12 String name = pros.getProperty("name");
13 String password = pros.getProperty("password");
14
15 System.out.println("name = " + name + ", password = " + password);
16 }
17
18 // 結果
19 name = Tom, password = abc123

6、ConcurrentHashMap<K,V>

  請檢視JDK原始碼。