Java基礎(六)——集合


一、概述

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源碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM