JAVA进阶教学之(集合)
目錄
1、集合概述
2、集合存儲的數據類型
3、不同的集合,底層都會對應不同的數據結構
4、集合繼承結構圖(部分接口和類)
5、Collection接口中常用的方法
6、Collection 集合迭代(遍歷)
7、Collection的contains( )方法理解
8、集合中元素的刪除
9、List接口特有的方法
10、ArrayList集合初始化容量及擴容
11、LinkedList雙向列表源碼分析
12、Vector安全數組
13、HashSet集合特點
14、TreeSet集合特點
15、Map接口常用方法
16、HashMap集合
17、HashMap和HashTable的區別
18、屬性類Properties
19、TreeSet可排序集合
?
1、集合概述
什么是集合,有什么用?
數組其實就是一個集合,集合實際上就是一個容器,集合可以容納引用類型的數據
?
為什么集合在開發中使用較多?
集合是一個容器,一次可以容納多個對象(集合的大小和機器的性能有關,理論無限大)
在實際開發中,假設網頁內容連接的是數據庫,數據庫中有10條記錄
假設需要把這10條記錄查詢出來,在java程序中會將這10條數據封裝
成10個java對象,然后將10個java對象放到一個集合中,將集合傳遞
給前端,然后遍歷集合,將一個數據一個數據展現出來
?
?
2、集合存儲的數據類型
集合中存儲的都是java對象的內存地址,或者說集合中存儲的是引用,不能直接存儲java對象
?
疑問?為什么list.add(100); 這個集合里面的100是什么數據類型
答案:100,是自動裝箱的100,將int型自動裝箱成為Integer型的包裝型
底層原理:Integer x = new Integer(100);? 然后 list.add(x);? x 表示的是內存地址
?
集合之間通過內存地址的存儲可以嵌套
?
?
3、不同的集合,底層都會對應不同的數據結構
什么是數據結構:
數據結構就是數據進行存儲時,存儲的方式,例如:數組、二叉樹、鏈表、哈希表
使用不同的集合等同于使用了不同的數據結構
java已經將數據結構實現了,已經寫好了這些常用的集合類,只需要掌握怎么使用集合,什么情況用哪種類型的集合
new ArrayList(); 創建一個集合,底層是數組
new LinkedList(); 創建一個集合,底層是鏈表
new TreeSet();創建一個集合對象,底層是二叉樹
?
集合在哪個包下:
java.util.*
?
?
4、集合繼承結構圖(部分接口和類)
在java中集合分成兩大類
第一種類型:單個方式存儲元素(超級父接口:java.util.Collection)
?
?
集合的接口之間的繼承結構圖:
Collection接口調用父類接口Iterable的iterator()方法,拿到集合依賴的迭代器對象
Itertor itertor = "Collection 對象".iterator();? ? ? ?itertor 是迭代器對象
?
?
總結:
- ArrayList:底層是數組
- LinkedList:底層是雙向鏈表
- Vector:底層是線程安全的數組,效率低
- HashSet:底層是HashMap,放到HashSet集合中的元素等同于放到HashMap集合中的key部分
- TreeSet:底層是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合中的Key部分
可以多次看加強記憶
?
?
補充:
?
?
?
?
?
第二種類型:鍵值對方式存儲元素(超級父接口:java.util.Map)
?
總結:
- HashMap:底層是哈希表
- Hashtable:底層是線程安全的哈希表,效率低
- Properties:線程安全的屬性類,并且key和value只能存儲字符串String
- TreeMap:底層是二叉樹,TreeMap 集合的Key 可以按照大小順序排序
?
?
總結:
- List集合存儲元素的特點:
元素有序可重復:存進去和取出來的順序相同,元素有下標,存進去1,還可以存1
- Set(對應的是Map)集合存儲元素的特點:
元素無序不可重復:存進去和取出來的順序不一定相同,元素無下標,存進去1,不可繼續存1
- SortSet(對應的是SortedMap)集合存儲元素的特點:
元素無序不可重復可排序:01同上,可排序是指可以按照大小進行排序
?
?
關鍵點:
Map集合的key,就是一個Set集合
往Set集合中放數據,實際上放到了Map集合的Key部分
?
理解點:
怎么往集合這個容器里面放東西,怎么取出來的這個過程需要理解
?
?
?
5、Collection接口中常用的方法
?
提問:Collection中能存放什么元素?
- 在沒有使用“泛型”之前,Collection中可以存儲Object的所有子類型
- 使用了“泛型”之后,Collection中只能存儲某個具體的類型
- 集合后期我們會學習“泛型”,目前先不用管,我們只需要知道Collection中什么都能存,只要是Object的子類型就行
?
注意:集合中不能直接存儲基本數據類型,也不能存儲java對象,只能存儲java對象的內存地址
?
Collection中常用的方法
? ? 1、boolean add(Object e) 向集合尾部中添加元素,把元素的內存地址添加進集合
代碼演示:
?
?
?
? 2、int size(?)? ?獲取集合中元素的個數,!注意,并不是獲取集合中的容量
代碼演示:
?
?
?
? ?3、void? clear( ) 清空集合中的元素
代碼演示:
?
?
?
? ?4、boolean remove(Object o) 刪除集合中指定元素
代碼演示:
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection;public class CollectionTest01 {public static void main(String[] args) {//創建一個集合對象,由于接口是抽象的,無法實例化//多態 父類對象的引用指向子類對象Collection collection=new ArrayList();//測試集合中可以放置的是不是內存地址//自動裝箱,Integer x = new Integer(100); collection.add(x),x是內存地址collection.add(100);collection.add(3.14);collection.add(new Object());collection.add(new Student());collection.remove(100);System.out.println(collection.size());//3} } class Student{}?
? ?5、boolean contains(Object o)? 判斷集合中是否包含元素o
代碼演示:
?
?
?
? ?6、boolean isEmpty( ) 判斷該集合中元素個數是否為0
代碼演示:
?
?
? ?7、Object[ ] toArray( )? ?調用這個方法可以把集合轉換成數組
?代碼演示:
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection;public class CollectionTest01 {public static void main(String[] args) {//創建一個集合對象,由于接口是抽象的,無法實例化//多態 父類對象的引用指向子類對象Collection collection=new ArrayList();//測試集合中可以放置的是不是內存地址//自動裝箱,Integer x = new Integer(100); collection.add(x),x是內存地址collection.add(100);collection.add(3.14);collection.add(new Object());collection.add(new Student());Object[] objects=collection.toArray();//遍歷數組for (int i = 0; i <objects.length; i++) {System.out.println(objects[i]);} //結果: //100 //3.14 //java.lang.Object@1540e19d //com.lbj.javase.collection.Student@677327b6} } class Student{}?
?
6、Collection 集合迭代(遍歷)
迭代:就是遍歷,不要將迭代兩個字想的太難,因為集合不是數組,沒有固定長度,無法通過For循環遍歷集合
底層:需要用到Collection繼承父類Iterable接口的 iterator( ) 方法,然后再在Collection中調用?iterator( ) 方法返回一個Iterator迭代器對象。
此迭代器對象可以使用其中的兩個方法,
一個方法是boolean hasNext() ,表示如果下一個元素存在,則返回true,有點類似指針;
一方法是Object next( ) 返回迭代的下一個元素,且取出來的元素類型都是Object類。?
代碼演示:
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.collection* @date 2021/3/28 18:40* @Copyright 公司*/ public class CollectionTest02 {public static void main(String[] args) {//創建集合對象Collection collection=new ArrayList();//添加集合元素collection.add("abc");collection.add(100);collection.add(new Object());//集合遍歷//第一步:獲取集合對象的迭代器對象Iterator iterator=collection.iterator();//第二步:開始遍歷while(iterator.hasNext()){//不管存進去的是什么,拿出來的時候一律都是Object類Object o=iterator.next();System.out.println(o);}}}//結果: //abc //100 //java.lang.Object@1540e19d圖示:
圖示2:
注意:以上的遍歷方式,是所有Collection通用的一種方式(除了Map集合不可以用!!!)
?
?
代碼演示2:
ArrayList集合,有序可重復
HashSet集合,無序不可重復
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.collection* @date 2021/3/28 19:51* @Copyright 公司*/ public class CollectionTest03 {public static void main(String[] args) {//創建ArrayList集合空間:有序可重復Collection collection = new ArrayList();//集合中添加元素collection.add(1);collection.add(2);collection.add(1);collection.add(3);//調用迭代器Iterator iterator=collection.iterator();//測試ArrayList集合元素中是否有序可重復while (iterator.hasNext()){Object o=iterator.next();if(o instanceof Integer){System.out.println("o是屬于整數類型");}//輸出的時候都會轉換成字符串,因為這里println默認調用toString方法System.out.println(o.toString());}System.out.println("分割線---------------------------------------------");//創建HashSet集合空間Collection collection1=new HashSet();//添加元素入集合collection1.add(1);collection1.add(1);collection1.add(3);collection1.add(3);collection1.add(2);collection1.add(5);collection1.add(5);//調用迭代器Iterator iterator1=collection1.iterator();//測試HashSet是否無序不可重復while(iterator1.hasNext()){Object o=iterator1.next();System.out.println(o);}} }測試結果:
o是屬于整數類型
1
o是屬于整數類型
2
o是屬于整數類型
1
o是屬于整數類型
3
分割線---------------------------------------------
1
2
3
5
?
?
迭代器容易犯的錯誤:
代碼演示3(迭代器不能自動刷新):
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection; import java.util.Iterator;public class CollectionTest06 {public static void main(String[] args) {//創建集合對象Collection collection=new ArrayList();//集合中添加元素collection.add("100");collection.add(100);collection.add(new Object());//創建迭代器Iterator iterator=collection.iterator();//遍歷集合元素while (iterator.hasNext()){Object o=iterator.next();System.out.println(o);}}}?
代碼演示4(改變迭代器的位置后):
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection; import java.util.Iterator;public class CollectionTest06 {public static void main(String[] args) {//創建集合對象Collection collection=new ArrayList();//創建迭代器//此時獲取的迭代器,指向的是那集合中沒有元素狀態下的迭代器//一定要注意:集合結構一旦發生改變,迭代器必須重新獲取Iterator iterator=collection.iterator();//集合中添加元素collection.add("100");collection.add(100);collection.add(new Object());//遍歷集合元素while (iterator.hasNext()){Object o=iterator.next();System.out.println(o);}}}結果:
Exception in thread "main" java.util.ConcurrentModificationException
?? ?at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
?? ?at java.util.ArrayList$Itr.next(ArrayList.java:859)
?? ?at com.lbj.javase.collection.CollectionTest06.main(CollectionTest06.java:30)
?
7、Collection的contains( )方法理解
語法:bollean contains(Object o) 判斷集合中是否存在某個元素,底層調用了equals方法比較
結論:存放一個集合中的類型,一定要重寫equals方法
代碼演示:
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection;public class CollectionTest04 {public static void main(String[] args) {//創建集合對象Collection collection=new ArrayList();//向集合中添加元素String s=new String("abc");collection.add(s);String s1=new String("def");collection.add(s1);System.out.println("集合中元素個數為"+collection.size());//2//新建的對象StringString x=new String("abc");//判斷:集合內有沒有元素x的內容的存在System.out.println(collection.contains(x));//true//底層System.out.println(s.equals(x));//true} }JVM示意圖:
?
代碼演示2(當類中沒有重寫equals方法時):
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.collection* @date 2021/3/31 20:46* @Copyright 公司*/ public class CollectionTest05 {public static void main(String[] args) {//創建集合對象Collection collection=new ArrayList();//創建用戶對象User user=new User("aaa");User user1=new User("aaa");//加入集合collection.add(user);//問:此時集合中有沒有包含user1的內容//原因:User類中沒有重寫equals方法,因此contains調用的是Object中的equals方法System.out.println(collection.contains(user1));//false//底層System.out.println(user.equals(user1));//false} } class User{public String name;public User() {}public User(String name) {this.name = name;} }同理可得:
boolean remove(Object o) 從集合中移除某個元素的內容,底層調用equals方法
?
?
?
8、集合中元素的刪除
引言:
集合中的元素直接刪除意味著集合的結構發生改變
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection; import java.util.Iterator;public class CollectionTest07 {public static void main(String[] args) {//創建集合對象Collection collection=new ArrayList();//添加元素進入集合collection.add("aaa");collection.add(100);collection.add(new Object());//獲取迭代器Iterator iterator=collection.iterator();//遍歷輸出集合while(iterator.hasNext()){Object o=iterator.next();//刪除元素//刪除元素后,集合的結構發生變化,應該重新去獲取迭代器//但是,循環下一次的時候并沒有重新獲取迭代器,所以會出現異常:Exception in thread "main" java.util.ConcurrentModificationExceptioncollection.remove(o);//輸出System.out.println(o);}} }?
?
規律:
Iterator it=c.iterator(); 獲取迭代器對象,迭代器用來遍歷集合,此時相當于對當前集合的狀態拍了一個快照,迭代器迭代的時候會不斷比對快照和原來集合是否相等,如果不相等,則報異常
?
迭代器的remove()方法(用迭代器的快照進行元素的刪除,則不會出現異常):
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection; import java.util.Iterator;public class CollectionTest07 {public static void main(String[] args) {//創建集合對象Collection collection=new ArrayList();//添加元素進入集合collection.add("aaa");collection.add(100);collection.add(new Object());//獲取迭代器Iterator iterator=collection.iterator();//遍歷輸出集合while(iterator.hasNext()){Object o=iterator.next();//迭代器的快照刪除iterator.remove();//輸出System.out.println(o);}System.out.println(collection.size());} }aaa 100 java.lang.Object@1540e19d 0?
JVM示意圖:
?
結論:
一:用“集合對象.remove(obj)”直接刪除元素的時候,沒有通知迭代器(導致迭代器的快照和原集合狀態不同),系統報異常
二:用“迭代器對象.remove()”迭代器刪除元素的時候,告訴迭代器從快照刪除元素,會自動更新迭代器,自動更新集合,系統不報錯
三:集合狀態發生改變時,要重新獲取迭代器
四:在迭代元素的過程中,一定要使用迭代器Iterator的remove方法,刪除元素
?
?
?
9、List接口特有的方法
引言:List集合存儲元素的特點:
有序(List集合元素有下標)
可重復(可以重復存儲相同數據)
?
1、void?add(int index,Object element)??在列表的指定位置插入指定元素,使用不多,效率低
代碼演示:
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Iterator; import java.util.List;public class ListTest01 {public static void main(String[] args) {//創建集合對象List list=new ArrayList();//添加元素list.add("a");//默認都是向集合的末尾添加元素list.add("b");list.add("c");//添加元素:List接口獨有的//發現添加的元素在下標為1的位置上,且原來的元素需要向后list.add(1,"e");//引入迭代器iIterator iterator=list.iterator();//遍歷while (iterator.hasNext()){Object o =iterator.next();System.out.println(o);}} } 結果:a e b c?
?
2、Object set(int index,Object element) 修改指定下標的元素
代碼演示:
?
?
3、Object get(int index)? 根據下標獲取元素,因此有特有的遍歷方式
代碼演示:
?
?
?
4、int indexOf(Object o)? 獲取元素的下標? int lastIndexOf(Object o) 獲取最后出現此元素的下標【因為List集合中的元素可以重復】
代碼演示:
?
?
5、Object remove(int index)? 刪除指定下標的元素
代碼演示:
?
?
?
10、ArrayList集合初始化容量及擴容
引言:
1、ArrayList集合初始化容量是10【底層先創建了一個長度為0的數組,當添加第一個元素的時候,初始化容量10】
2、ArrayList集合底層是Object類型的數組 Object[ ]
3、構造方法 new ArrayList();? ?new ArrayList(初始化集合容量大小的數);
4、ArrayList集合的擴容,是原容量的1.5倍,即1*(1+1/2)。底層是數組,應該盡可能少擴容,使用ArrayList集合先預估一個初始化容量
5、面試:ArrayList集合用的是最多的集合,因為它是數組結構,每個元素占用空間大小相同,內存地址連續,可以知道首元素內存地址,檢索效率高,隨機增刪效率低,末尾插入元素效率高,線程不安全
代碼演示:
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.List;public class ArrayListTest01 {public static void main(String[] args) {//創建集合對象List list=new ArrayList();//集合size() 方法是獲取當前集合中“元素”的個數,不是獲取集合的容量//System.out.println(list.size());//0//創建的集合容量為20List list1=new ArrayList<>(20);} }?構造方法?new ArrayList(初始化集合容量大小的數),參數里面還可以把另外一個數組傳遞進去
package com.lbj.javase.collection;import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List;public class ArrayListTest02 {public static void main(String[] args) {//創建HashSet集合Collection collection=new HashSet();//添加元素collection.add(100);collection.add(200);collection.add(300);//將HashSet集合傳入ArrayListList list=new ArrayList(collection);for (int i = 0; i <list.size(); i++) {Object o =list.get(i);System.out.println(o);}} }?
ArrayList:把檢索發揮到極致(一般加元素都是末尾添加,且末尾添加元素效率很高)
LinkedList :把隨機增刪發揮到極致
?
?
11、LinkedList雙向列表源碼分析
引言:不懂什么是單鏈表數據結構的可以先看以下文章
JAVA進階教學之(單鏈表數據結構)
?
?LinkedList雙向列表的JVM示意圖:
?
提問:LinkedList集合有初始容量嗎?
無,初始化容量是null
?
提問:寫代碼時需要關心是哪個集合嗎?
不需要,因為我們要面向接口編程,調用的方法都是接口中的方法,不管是LinkedList還是ArrayList,以后寫代碼的時候都不需要關心具體是哪個集合
public class LinkedListTest01 {public static void main(String[] args) {List list=new LinkedList();//可以改為new ArrayList();,效果一樣 list.add("a");list.add("b");list.add("c");for (int i = 0; i <list.size(); i++) {Object o=list.get(i);System.out.println(o);}} }?
?
總結:
?
?
?
12、Vector安全數組
引言:
底層:也是數組(線程安全數組)
初始化容量:10
?
Vector數組擴容:擴容后是原容量的2倍(10--》20--》30--》40)
ArrayList數組擴容:擴容后是原容量的1.5倍(10--》15--》22.5--》33.75)
?
Vector中所有的方法都是線程同步的,都帶有synchronized關鍵字,是線程安全的
?
提問:
怎么將一個線程不安全的ArrayList集合轉換成線程安全的呢?
?
回答:
java.util.Collections;??使用集合工具類
?
注意:
java.util.Collection 是集合接口
java.util.Collections 是集合工具類(方便集合的操作)
?
代碼演示:
?
?
?
?
13、HashSet集合特點
引言:
代碼演示:
package com.lbj.javase.collection;import java.util.HashSet; import java.util.Set;public class HashSetTest01 {public static void main(String[] args) {Set<String> set=new HashSet<>();set.add("6");set.add("2");set.add("3");set.add("7");for (String a:set) {System.out.println(a);}} } 2 3 6 7?
14、TreeSet集合特點
引言:
?
代碼演示:
package com.lbj.javase.collection;import java.util.Set; import java.util.TreeSet;public class TreeSetTest01 {public static void main(String[] args) {Set<String> set=new TreeSet();set.add("1");set.add("3");set.add("2");set.add("8");set.add("5");for (String s:set) {System.out.println(s);}} } 1 2 3 5 8?
?
?
15、Map接口常用方法
引言:
?
部分Map接口方法(代碼演示):
package com.lbj.javase.collection;import java.util.Collection; import java.util.HashMap; import java.util.Map;public class MapTest01 {public static void main(String[] args) {//創建一個 Map 集合Map<Integer,String> map=new HashMap<>();//向Map集合中添加鍵值對map.put(10,"小明");//10在這里進行了自動裝箱map.put(20,"小紅");map.put(30,"小剛");//通過key 獲取 valueString s= map.get(10);System.out.println(s);//獲取 鍵值對 的 數量Integer integer=map.size();System.out.println(integer);//通過 key 刪除 key-valueString s1=map.remove(10);System.out.println(s1);//判斷是否包含某個keyBoolean b=map.containsKey(10);System.out.println(b);//判斷是否包含某個valueBoolean b1=map.containsValue("小明");System.out.println(b1);//清空集合//map.clear();//判斷 集合 是否為空Boolean b2=map.isEmpty();System.out.println(b2);//獲取所有的 valueCollection<String> collection=map.values();for (String s2: map.values()) {System.out.println(s2);}} }小明 3 小明 false false false 小紅 小剛?
?
Map集合的遍歷(!!!非常重要):
?
方法一(直接獲取key,遍歷Set):
缺點:效率相對較低,?String value=map.get(key);會再次調用哈希表,會影響性能
package com.lbj.javase.collection;import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set;public class MapTest02 {public static void main(String[] args) {//第一種方式:獲取所有的key,通過遍歷key,來遍歷valueMap<Integer,String> map=new HashMap<>();//添加元素map.put(1,"zhangsan");map.put(2,"wanwu");map.put(3,"zhaosi");map.put(4,"xiaohong");//遍歷Map集合//獲取所有的keys,所有的keys是一個Set集合Set<Integer> keys=map.keySet();//遍歷key,通過key,獲取valueIterator<Integer> iterable=keys.iterator();while (iterable.hasNext()){//取出其中的一個keyInteger key=iterable.next();//通過key獲取valueString value=map.get(key);System.out.println(key+"="+value);}} }1=zhangsan 2=wanwu 3=zhaosi 4=xiaohong?
?
(同等方法的foreach遍歷):
package com.lbj.javase.collection;import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set;public class MapTest02 {public static void main(String[] args) {//第一種方式:獲取所有的key,通過遍歷key,來遍歷valueMap<Integer,String> map=new HashMap<>();//添加元素map.put(1,"zhangsan");map.put(2,"wanwu");map.put(3,"zhaosi");map.put(4,"xiaohong");//遍歷Map集合//獲取所有的keys,所有的keys是一個Set集合Set<Integer> keys=map.keySet();for (Integer key:keys) {System.out.println(key+"="+map.get(key));}} }?
?
方法二(Map轉Set,遍歷Set):
Set<Map.Entry<K,V>> entrySet()? ?把Map集合直接全部轉換成Set集合
Set集合中元素的類型是 :Map.Entry<K,V>
優點:效率高(原因:Set集合存儲的Map集合中的數據,實際上存儲在node節點中,通過node節點直接獲取Map集合中的數據,不需要再次經過哈希表,性能提高,底層是Node單向鏈表,下面會繼續說說這個Node)
package com.lbj.javase.collection;import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set;public class MapTest02 {public static void main(String[] args) {//第一種方式:獲取所有的key,通過遍歷key,來遍歷valueMap<Integer,String> map=new HashMap<>();//添加元素map.put(1,"zhangsan");map.put(2,"wanwu");map.put(3,"zhaosi");map.put(4,"xiaohong");//第二種方式:Map轉換成Set集合Set<Map.Entry<Integer,String>> set=map.entrySet();//迭代set集合Iterator<Map.Entry<Integer,String>> iterator=set.iterator();while (iterator.hasNext()){Map.Entry<Integer,String> node=iterator.next();Integer i=node.getKey();String s=node.getValue();System.out.println(i+"="+s);}} }1=zhangsan 2=wanwu 3=zhaosi 4=xiaohongJVM示意圖(為了方便理解):
?
?
?
(同等方法的foreach遍歷):
package com.lbj.javase.collection;import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set;public class MapTest02 {public static void main(String[] args) {//第一種方式:獲取所有的key,通過遍歷key,來遍歷valueMap<Integer,String> map=new HashMap<>();//添加元素map.put(1,"zhangsan");map.put(2,"wanwu");map.put(3,"zhaosi");map.put(4,"xiaohong");for (Map.Entry<Integer,String> node:set) {System.out.println(node);//直接獲取node結果一樣,但是實際開發中,一般需要獲取key或者是value,所以拆開使用更好System.out.println(node.getKey()+"="+node.getValue());}} }1=zhangsan 1=zhangsan 2=wanwu 2=wanwu 3=zhaosi 3=zhaosi 4=xiaohong 4=xiaohong
?
?
16、HashMap集合
引言:
HashMap集合底層是 “哈希表/散列表” 的數據結構
?
哈希表:
哈希表是一個數組和單向鏈表的結合體,充分發揮各自優點
數組:在查詢方面效率很高,隨機增刪方面效率很低
單向鏈表:在查詢方面效率很低,隨機增刪方面效率很高
理解:哈希表是一維數組,這個數組中的每一個元素都是一個單項鏈表,相當于一個中間體,查詢效率相對于純數組是略低,隨機增刪改查效率相對于純單向鏈表是略低,但是綜合起來,哈希表是一個優選擇,查詢不需要都掃描,只需要部分掃描,隨機增刪改查是在部分鏈表上完成
?
?
HashMap底層源代碼分析:
?
1、由于HashMap的底層和哈希表的關系非常大,因此我們從哈希值開始入手學習
哈希值:哈希值是key的hashCode()方法的執行結果,hash值通過哈希算法(哈希算法不是一種具體的算法,而是一種思想,將不定長的字符串變成定長的字符串),可以轉換存儲為數組的下標
偽代碼演示:(HashMap的底層是一個數組,數組中的每個下標上存儲的是單鏈表,單鏈表中的節點有4個屬性,分別如下):?
public class HashMap{//HashMap底層實際上就是一個數組Node<K,V>[ ] table;//靜態內部HashMap.Nodestatic class Node<K,V> implements Map.Entry<K,V> {final int hash; //存儲哈希值final K key; //存儲到Map集合中的那個keyV value; //存儲到Map集合中的那個valueNode<K,V> next; //存儲下一個節點的內存地址 }?
?
哈希表數據結構(配合示意圖食用效果會更好):
2、需要了解哈希表,可以從 map.put(k,v)? 的實現原理入手
- 第一步:先將k,v封裝到Node對象中
- 第二步:底層會調用k的hashCode()方法得出hash值
- 第三步:通過哈希算法,將hash值轉換成數組的下標,判斷當前下標位置上如果沒有任何元素,就把Node添加到當前位置上
- 第四步:判斷當前下標位置上如果有鏈表,此時會將k的值和鏈表中的每一個節點的k值進行equals比較,如果返回false,則表示沒有重復,新節點將被添加到鏈表尾端;如果其中有一個equals返回true,則表示有重復,新節點的value會被覆蓋
?
3、從map.get(k) 的實現原理入手
- 第一步:先調用k的hashCode()方法的出hash值
- 第二步:通過哈希算法,將hash值轉換成數組的下標,判斷當前下標位置上如果沒有任何元素,返回null
- 第三步:判斷當前下標位置上如果有鏈表,此時會將k的值和鏈表中的每一個節點的k值進行equals比較,如果返回false,那么get方法返回null,只要其中有一個節點的k值和參數k值的equals比較的時候返回true,那么此時這個節點的value就是我們需要的value,get方法最終返回這個要找的value
?
示意圖:
??
哈希表補充:
如果k1和k2的hash值相同,一定是放到同一個單項鏈表上
如果k1和k2的hash值不相同,但是由于哈希算法執行結束后轉換的數組下標可能相同(7%3=1,4%3=1),此時會發生“哈希碰撞”
?
4、了解哈希表后,就可以分析HashMap集合的key部分特點
- 無序:因為不一定掛載到哪一個單向鏈表上
- 不可重復:equals()方法保證HashMap集合中的key不可重復,如果key重復了,新value會覆蓋舊value的值
- 放在HashMao集合的key部分的元素其實就是放到HashSet集合中了
- 所以HashSet集合中的元素也需要同時重寫hashCode()+equals()方法
?
5、重點:
- 通過示意圖可以得出HashMap集合的key,會先后調用兩個方法,一個方法是hashCode(),一個方法是equals(),那么這兩個方法都需要進行重寫
?
?
6、哈希表HashMap使用不當時會無法發揮出哈希表的性能
- 原因:假設將所有的hashCode() 的方法返回值固定為某個值,那么會導致底層哈希表變成純的單向鏈表,這種情況稱為:散列分布不均勻
- 什么是散列分布均勻?
- 假設有100個元素,10個單向鏈表,那么每個單向鏈表上有10個節點的時候,是最好的結果,屬于散列分布均勻
- 假設將所有的hashCode() 的方法返回值都設定為不一樣的值,可以嗎?
- 不行,因為這樣的話會導致底層哈希表稱為一個純一維數組,沒有鏈表的概念,屬于散列分布不均勻
- 結論:因此,散列分布均勻需要重寫hashCode() 的方法時有一定的技巧
?
?
?
7、關于HashMap集合的擴容
默認初始化容量是16,默認加載因子是0.75
默認加載因子:是當HashMap集合底層數組的容量達到75%的時候,數組開始擴容
?
重點,記住:HashMap集合初始化容量最好是2的倍數,這是官方推薦的
這是因為達到散列均勻,為了提高HashMap集合的存取效率,所以必須的
1<<4? 是二進制位移,位移效率比運算效率高很多
?
擴容:擴容之后的容量是原容量的2倍
?
8、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是這兩個方法需要同時生成
?
9、JDK 8 的HashMap有新特性
如果哈希表單向鏈表中元素超過8個,單向鏈表這種數據結構會變成紅黑樹數據結構
如果紅黑樹上的節點數量小于6個時,會重新把紅黑樹變成單向鏈表數據結構,提高檢索效率
?
終極結論:
放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同時重寫hashCode()+equals(),且hashCode()會先被調用
?
?
?
?
17、HashMap和HashTable的區別
提問:HashMap集合key部分可以存儲null嗎?
答案:允許,但是需要注意 key的null值只能有一個,再多也只能被覆蓋而已
代碼演示:
package com.lbj.javase.collection;import java.util.HashMap; import java.util.Map;public class MapTest03 {public static void main(String[] args) {Map map=new HashMap<>();//HashMap集合允許key為nullmap.put(null,null);//key重復的時候,value進行覆蓋map.put(null,100);System.out.println(map.size());//1//通過key獲取valueSystem.out.println(map.get(null));//100} }?
?
- HashTable的方法都是帶有synchronized,屬于線程安全的,效率低的
- 因此Hashtable的key和value都是不能為null的,但是HashMap的key和value都可以為null
- HashTable的初始化容量是11,默認加載因子是0.75f,HashTable的擴容是原容量*2+1
代碼演示:
package com.lbj.javase.collection;import java.util.Hashtable; import java.util.Map;public class MapTest04 {public static void main(String[] args) {Map map=new Hashtable<>();//Exception in thread "main" java.lang.NullPointerException// at java.util.Hashtable.put(Hashtable.java:460)// at com.lbj.javase.collection.MapTest04.main(MapTest04.java:18)map.put(null,null);//空指針異常//同理map.put(1,null);map.put(null,1);} }?
?
?
?
18、屬性類Properties
目前只需要掌握Properties屬性類對象的相關方法即可
Properties是一個Map集合,繼承HashTable
Properties的key和value都是String類型
Properties被稱為屬性類對象
Properties是線程安全的
代碼演示:
package com.lbj.javase.collection;import java.util.Properties;public class PropertiesTest01 {public static void main(String[] args) {//創建一個Properties對象Properties properties=new Properties();//需要掌握兩個方法,一個存,一個取//存properties.setProperty("url","jdbc:mysql://localhost:3306/aaa");properties.setProperty("driver","com.mysql.jdbc.Driver");properties.setProperty("username","root");properties.setProperty("password","123");//取String s=properties.getProperty("url");String s1=properties.getProperty("password");System.out.println(s+s1);//jdbc:mysql://localhost:3306/aaa123} }?
?
19、TreeSet可排序集合
TreeSet集合底層實際上是一個TreeMap,TreeMap集合底層是二叉樹
放到TreeSet集合中的元素,等同于放到TreeMap集合中的key部分
TreeSet集合中的元素:無序不可重復,但是取出時可以按照元素類型的大小順序自動排序
代碼演示(按照字典順序,默認升序):
package com.lbj.javase.collection;import java.util.TreeSet;public class TreeSetTest02 {public static void main(String[] args) {TreeSet<String> treeSet=new TreeSet<>();treeSet.add("d");treeSet.add("d");treeSet.add("b");treeSet.add("b");treeSet.add("a"); //.add()底層其實就是treeMap中的.put()方法for (String s:treeSet) {System.out.println(s);}//a b dTreeSet<Integer> treeSet1=new TreeSet<>();treeSet1.add(6);treeSet1.add(3);treeSet1.add(1);treeSet1.add(2);for (Integer i:treeSet1) {System.out.println(i);}//1 2 3 6} }?
TreeSet無法對自定義類型排序:
提問:對于自定義類型來說,TreeSet可以排序嗎?
以下程序中對于Worker類型來說,無法排序,因為Worker類中沒有指定Worker對象之間的比較規則
(誰大誰小無法說明)
代碼演示:
package com.lbj.javase.collection;import java.util.TreeSet;public class TreeSetTest03 {public static void main(String[] args) {TreeSet<Worker> treeSet=new TreeSet<>();Worker worker=new Worker(19);Worker worker1=new Worker(34);Worker worker2=new Worker(21);Worker worker3=new Worker(35);//添加一個自定義類的對象進去treeSet中,這一步就是錯誤的treeSet.add(worker);treeSet.add(worker1);treeSet.add(worker2);treeSet.add(worker3);//遍歷for (Worker w:treeSet) {System.out.println(w);}} } class Worker{private int age;public Worker(int age) {this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;} }?
出現異常:
com.lbj.javase.collection.TreeSetTest03
Exception in thread "main" java.lang.ClassCastException: com.lbj.javase.collection.Worker cannot be cast to java.lang.Comparable
?? ?at java.util.TreeMap.compare(TreeMap.java:1294)
?? ?at java.util.TreeMap.put(TreeMap.java:538)
?? ?at java.util.TreeSet.add(TreeSet.java:255)
?? ?at com.lbj.javase.collection.TreeSetTest03.main(TreeSetTest03.java:22)
?
異常分析:
Worker類沒有實現java.lang.Comparable接口,導致類型的強制轉換不成功,無法進行比較,而再上一例中的String和Integer源碼中都實現了Comparable接口,可以進行類型轉換,進行比較無非就是<0、=0、>0 因此就可以進行排序
?
自定義類型重寫CompareTo方法:
代碼演示:
問題:為什么在CompareTo方法里面寫了一個return age2-age1 就可以對年齡進行比較?
解答:請看下面的比較規則
?
compareTo() 方法的返回值很重要:
?
- 返回0,表示相同,value會覆蓋
- 返回>0,表示會在右子樹上尋找
- 返回<0,表示會在左子樹上尋找
代碼演示(比較規則應該怎么寫):
package com.lbj.javase.collection;import java.util.TreeSet;public class TreeSetTest04 {public static void main(String[] args) {//創建一個treeSet集合TreeSet<Vip> treeSet=new TreeSet<>();Vip vip=new Vip("zhangsan",23);Vip vip1=new Vip("lisi",23);Vip vip2=new Vip("wanwu",32);Vip vip3=new Vip("zhaosi",38);//將創建的對象添加進treeSet集合中treeSet.add(vip);treeSet.add(vip1);treeSet.add(vip2);treeSet.add(vip3);//遍歷集合,查看排序for (Vip v:treeSet) {System.out.println(v);}} }class Vip implements Comparable<Vip>{private String name;private int age;public Vip(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic int compareTo(Vip o) {//寫排序規則,按照哪一個屬性進行比較if(this.age==o.age){//年齡相相等的時候,比較姓名,因為姓名是String類型,已經實現了CompareToreturn this.name.compareTo(o.name);}else {//年齡不相等的時候,只比較年齡return this.age-o.age;}}@Overridepublic String toString() {return "Vip{" +"name='" + name + '\'' +", age=" + age +'}';} }Vip{name='lisi', age=23} Vip{name='zhangsan', age=23} Vip{name='wanwu', age=32} Vip{name='zhaosi', age=38}?
?
?自平衡二叉樹的概念(存放的過程就是排序的過程):
圖示:
?
?
結論:
放到TreeSet或者TreeMap集合key部分的元素想要做到排序,包括兩種方式(P714):
第一種:放在集合中的元素實現java.lang.Compareable接口
第二種:在構造TreeSet或者TreeMap集合的時候傳入一個比較器對象
Comparable和Comparator怎么選擇呢?
當比較規則不會發生改變的時候,或者說當比較規則只有一個的時候,建議實現Comparable接口
當比較規則出現多個,并且需要多個比較規則之間頻繁切換,建議使用Comparator接口
代碼演示:
package com.lbj.javase.collection;import java.util.Comparator; import java.util.TreeSet;/** 比較器方法*/ public class TreeSetTest05 {public static void main(String[] args) {//創建TreeSet集合的時候,需要用到比較器的方式//通過構造方法傳遞一個比較器進去TreeSet集合中TreeSet<WuGui> treeSet=new TreeSet<>(new WuGuiComparator());WuGui wuGui=new WuGui(10);WuGui wuGui1=new WuGui(6);WuGui wuGui2=new WuGui(15);WuGui wuGui3=new WuGui(11);treeSet.add(wuGui);treeSet.add(wuGui1);treeSet.add(wuGui2);treeSet.add(wuGui3);//遍歷for (WuGui w:treeSet) {System.out.println(w);}} }class WuGui{private int age;public WuGui(int age) {this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "烏龜{" +"age=" + age +'}';} }//需要單獨寫一個比較器 //比較器實現java.util.Compatator接口(Comparable是java.lang包下的,Comparator是java.util包下的) class WuGuiComparator implements Comparator<WuGui>{@Overridepublic int compare(WuGui o1, WuGui o2) {//指定比較規則//按照年齡排序return o1.getAge()-o2.getAge();} }烏龜{age=6} 烏龜{age=10} 烏龜{age=11} 烏龜{age=15}?
?
?
總結
以上是生活随笔為你收集整理的JAVA进阶教学之(集合)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JdbcTemplate(概念和准备)
- 下一篇: hfss螺旋平面_利用HFSS设计平面等