一、概述
1、介紹
為什么出現集合?
答:面向對象語言對事物的體現都是以對象的形式,所以為了方便對多個對象的操作,對對象進行存儲,集合就是存儲對象最常用的一種方式。
數組和集合類同是容器,有何不同?
答:數組雖然也可以存儲對象,但長度是固定的,集合長度是可變的。數組中可以存儲基本數據類型,集合中只能存儲對象(引用類型,基本類型的包裝類型)。
為什么會出現這么多的容器呢?
答:因為每一個容器對數據的存儲方式都有不同。這個存儲方式稱之為:數據結構。
什么是迭代器呢?
答:其實就是集合的取出元素的方式。
2、集合框架
簡圖:


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