集合
什麼是集合?有什麼用?
數組其實就是一個集合。集合實際上就是一個容器。可以來容納其它類型的數據。
集合爲什麼說在開發中使用較多?
集合是一個容器,是一個載體,可以一次容納多個對象。
在實際開發中,假設連接數據庫,數據庫當中有10條記錄,
那麼假設把這10條記錄查詢出來,在java程序中會將10條數據封裝成10個java對象,
然後將10個java對象放到某一個集合當中,將集合傳到前端,
然後遍歷集合,將一個數據一個數據展現出來。
集合不能直接存儲基本數據類型,另外集合也不能直接存儲java對象,
集合當中存儲的都是java對象的內存地址。(或者說集合中存儲的是引用。)
list.add(100); //自動裝箱Integer
注意:
集合在java中本身是一個容器,是一個對象。
集合中任何時候存儲的都是“引用”。
在java中每一個不同的集合,底層會對應不同的數據結構。
往不同的集合中存儲元素,等於將數據放到了不同的數據結構當中。
什麼是數據結構?數據存儲的結構就是數據結構。
不同的數據結構,數據存儲方式不同。
例如:
數組、二叉樹、鏈表、哈希表...
以上這些都是常見的數據結構。
你往集合c1中放數據,可能是放到數組上了。
你往集合c2中放數據,可能是放到二叉樹上了。
.....
你使用不同的集合等同於使用了不同的數據結構。
new ArrayList(); 創建一個集合,底層是數組。
new LinkedList(); 創建一個集合對象,底層是鏈表。
new TreeSet(); 創建一個集合對象,底層是二叉樹。
.....
所有的集合類和集合接口都在java.util包下。
java.util.*;
在java中集合分爲兩大類:
一類是單個方式存儲元素:
單個方式存儲元素,這一類集合中超級父接口:java.util.Collection;
Collection中能存放什麼元素?
沒有使用“泛型”之前,Collection中可以存儲Object的所有子類型。
使用了“泛型”之後,Collection中只能存儲某個具體的類型。
集合後期我們會學習“泛型”語法。目前先不用管。Collection中什麼都能存,
只要是Object的子類型就行。(集合中不能直接存儲基本數據類型,也不能存java對象,只是存儲java對象的內存地址。)
Collection中的常用方法
boolean add(Object e) 向集合中添加元素
int size() 獲取集合中元素的個數
void clear() 清空集合
boolean contains(Object o) 判斷當前集合中是否包含元素o,包含返回true,不包含返回false
boolean remove(Object o) 刪除集合中的某個元素。
boolean isEmpty() 判斷該集合中元素的個數是否爲0
Object[] toArray() 調用這個方法可以把集合轉換成數組。【作爲了解,使用不多。】
代碼演示:(Collection常用方法)
import java.util.ArrayList;
import java.util.Collection;
public class CollectionTest01 {
public static void main(String[] args) {
// 創建一個集合對象
//Collection c = new Collection(); // 接口是抽象的,無法實例化。
// 多態
Collection c = new ArrayList();
// 測試Collection接口中的常用方法
c.add(1200); // 自動裝箱(java5的新特性。),實際上是放進去了一個對象的內存地址。Integer x = new Integer(1200);
c.add(3.14); // 自動裝箱
c.add(new Object());
c.add(new Student());
c.add(true); // 自動裝箱
// 獲取集合中元素的個數
System.out.println("集合中元素個數是:" + c.size()); // 5
// 清空集合
c.clear();
System.out.println("集合中元素個數是:" + c.size()); // 0
// 再向集合中添加元素
c.add("hello"); // "hello"對象的內存地址放到了集合當中。
c.add("world");
c.add("浩克");
c.add("綠巨人");
c.add(1);
// 判斷集合中是否包含"綠巨人"
boolean flag = c.contains("綠巨人");
System.out.println(flag); // true
boolean flag2 = c.contains("綠巨人2");
System.out.println(flag2); // false
System.out.println(c.contains(1)); // true
System.out.println("集合中元素個數是:" + c.size()); // 5
// 刪除集合中某個元素
c.remove(1);
System.out.println("集合中元素個數是:" + c.size()); // 4
// 判斷集合是否爲空(集合中是否存在元素)
System.out.println(c.isEmpty()); // false
// 清空
c.clear();
System.out.println(c.isEmpty()); // true(true表示集合中沒有元素了!)
c.add("abc");
c.add("def");
c.add(100);
c.add("helloworld!");
c.add(new Student());
// 轉換成數組(瞭解,使用不多。)
Object[] objs = c.toArray();
for(int i = 0; i < objs.length; i++){
// 遍歷數組
Object o = objs[i];
System.out.println(o);
}
}
}
class Student{
}
集合遍歷/迭代(重點*****)
步驟:第一步:獲取集合對象的迭代器對象Iterator
第二步:通過以上獲取的迭代器對象開始迭代/遍歷集合。
以下兩個方法是迭代器對象Iterator中的方法:
boolean hasNext()如果仍有元素可以迭代,則返回 true。
Object next() 返回迭代的下一個元素。
注意:以下講解的遍歷方式/迭代方式,是所有Collection通用的一種方式。
在Map集合中不能用。在所有的Collection以及子類中使用。
代碼演示:(HashSet和ArrayList集合)
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.HashSet;
public class CollectionTest02 {
public static void main(String[] args) {
// 創建集合對象
// ArrayList集合:有序可重複
Collection c = new ArrayList();
// 添加元素
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
c.add(100);
// 對集合Collection進行遍歷/迭代
// 第一步:獲取集合對象的迭代器對象Iterator
Iterator it = c.iterator();
// 第二步:通過以上獲取的迭代器對象開始迭代/遍歷集合。
while(it.hasNext()){
// 存進去是什麼類型,取出來還是什麼類型。
Object obj = it.next();
/*if(obj instanceof Integer){
System.out.println("Integer類型");
}*/
// 只不過在輸出的時候會轉換成字符串。因爲這裏println會調用toString()方法。
System.out.println(obj);
}
// HashSet集合:無序不可重複
Collection c2 = new HashSet();
// 無序:存進去和取出的順序不一定相同。
// 不可重複:存儲100,不能再存儲100.
c2.add(100);
c2.add(200);
c2.add(300);
c2.add(90);
c2.add(400);
c2.add(50);
c2.add(60);
c2.add(100);
Iterator it2 = c2.iterator();
while(it2.hasNext()){
System.out.println(it2.next());
}
}
}
深入Collection集合的contains方法和equals方法:
boolean contains(Object o)
判斷集合中是否包含某個對象o
如果包含返回true, 如果不包含返回false。
contains方法是用來判斷集合中是否包含某個元素的方法,
那麼它在底層是怎麼判斷集合中是否包含某個元素的呢?
調用了equals方法進行比對。
equals方法返回true,就表示包含這個元素。
結論:存放在一個集合中的類型,一定要重寫equals方法。
代碼演示:
import java.util.ArrayList;
import java.util.Collection;
/*
測試contains方法
測試remove方法。
*/
public class CollectionTest05 {
public static void main(String[] args) {
// 創建集合對象
Collection c = new ArrayList();
// 創建用戶對象
User u1 = new User("jack");
// 加入集合
c.add(u1);
// 判斷集合中是否包含u2
User u2 = new User("jack");
// 沒有重寫equals之前:這個結果是false
//System.out.println(c.contains(u2)); // false
// 重寫equals方法之後,比較的時候會比較name。
System.out.println(c.contains(u2)); // true
c.remove(u2);
System.out.println(c.size()); // 0
/*Integer x = new Integer(10000);
c.add(x);
Integer y = new Integer(10000);
System.out.println(c.contains(y)); // true*/
// 創建集合對象
Collection cc = new ArrayList();
// 創建字符串對象
String s1 = new String("hello");
// 加進去。
cc.add(s1);
// 創建了一個新的字符串對象
String s2 = new String("hello");
// 刪除s2
cc.remove(s2); // s1.equals(s2) java認爲s1和s2是一樣的。刪除s2就是刪除s1。
// 集合中元素個數是?
System.out.println(cc.size()); // 0
}
}
class User{
private String name;
public User(){}
public User(String name){
this.name = name;
}
// 重寫equals方法
// 將來調用equals方法的時候,一定是調用這個重寫的equals方法。
// 這個equals方法的比較原理是:只要姓名一樣就表示同一個用戶。
public boolean equals(Object o) {
if(o == null || !(o instanceof User)) return false;
if(o == this) return true;
User u = (User)o;
// 如果名字一樣表示同一個人。(不再比較對象的內存地址了。比較內容。)
return u.name.equals(this.name);
}
}
集合元素中的remove
重點:當集合的結構發生改變時,迭代器必須重新獲取,
如果還是用以前老的迭代器,會出現 異常:java.util.ConcurrentModificationException
重點:在迭代集合元素的過程中,不能調用集合對象的remove方法,刪除元素: c.remove(o); 迭代過程中不能這樣。
會出現:java.util.ConcurrentModificationException
重點:在迭代元素的過程當中,一定要使用迭代器Iterator的remove方法,刪除元素, 不要使用集合自帶的remove方法刪除元素。
代碼演示
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionTest06 {
public static void main(String[] args) {
// 創建集合
Collection c = new ArrayList();
// 注意:此時獲取的迭代器,指向的是那是集合中沒有元素狀態下的迭代器。
// 一定要注意:集合結構只要發生改變,迭代器必須重新獲取。
// 當集合結構發生了改變,迭代器沒有重新獲取時,調用next()方法時:java.util.ConcurrentModificationException
Iterator it = c.iterator();
// 添加元素
c.add(1); // Integer類型
c.add(2);
c.add(3);
// 獲取迭代器
//Iterator it = c.iterator();
/*while(it.hasNext()){
// 編寫代碼時next()方法返回值類型必須是Object。
// Integer i = it.next();
Object obj = it.next();
System.out.println(obj);
}*/
Collection c2 = new ArrayList();
c2.add("abc");
c2.add("def");
c2.add("xyz");
Iterator it2 = c2.iterator();
while(it2.hasNext()){
Object o = it2.next();
// 刪除元素
// 刪除元素之後,集合的結構發生了變化,應該重新去獲取迭代器
// 但是,循環下一次的時候並沒有重新獲取迭代器,所以會出現異常:java.util.ConcurrentModificationException
// 出異常根本原因是:集合中元素刪除了,但是沒有更新迭代器(迭代器不知道集合變化了)
//c2.remove(o); // 直接通過集合去刪除元素,沒有通知迭代器。(導致迭代器的快照和原集合狀態不同。)
// 使用迭代器來刪除可以嗎?
// 迭代器去刪除時,會自動更新迭代器,並且更新集合(刪除集合中的元素)。
it2.remove(); // 刪除的一定是迭代器指向的當前元素。
System.out.println(o);
}
System.out.println(c2.size()); //0
}
}
ArrayList集合
1、默認初始化容量10(底層先創建了一個長度爲0的數組,當添加第一個元素的時候,初始化容量10。)
2、集合底層是一個Object[]數組。
3、構造方法:
new ArrayList();
new ArrayList(20);
4、ArrayList集合的擴容:
增長到原容量的1.5倍。
ArrayList集合底層是數組,怎麼優化?
儘可能少的擴容。因爲數組擴容效率比較低,建議在使用ArrayList集合
的時候預估計元素的個數,給定一個初始化容量。
5、數組優點:
檢索效率比較高。(每個元素佔用空間大小相同,內存地址是連續的,知道首元素內存地址,
然後知道下標,通過數學表達式計算出元素的內存地址,所以檢索效率最高。)
6、數組缺點:
隨機增刪元素效率比較低。
另外數組無法存儲大數據量。(很難找到一塊非常巨大的連續的內存空間。)
7、向數組末尾添加元素,效率很高,不受影響。
8、面試官經常問的一個問題?
這麼多的集合中,你用哪個集合最多?
答:ArrayList集合。
因爲往數組末尾添加元素,效率不受影響。
另外,我們檢索/查找某個元素的操作比較多。
9、ArrayList集合是非線程安全的。(不是線程安全的集合。)
代碼演示
import java.util.ArrayList;
import java.util.List;
/*
*/
public class ArrayListTest01 {
public static void main(String[] args) {
// 默認初始化容量是10
// 數組的長度是10
List list1 = new ArrayList();
// 集合的size()方法是獲取當前集合中元素的個數。不是獲取集合的容量。
System.out.println(list1.size()); // 0
// 指定初始化容量
// 數組的長度是20
List list2 = new ArrayList(20);
// 集合的size()方法是獲取當前集合中元素的個數。不是獲取集合的容量。
System.out.println(list2.size()); // 0
list1.add(1);
list1.add(2);
list1.add(3);
list1.add(4);
list1.add(5);
list1.add(6);
list1.add(7);
list1.add(8);
list1.add(9);
list1.add(10);
System.out.println(list1.size());
// 再加一個元素
list1.add(11);
System.out.println(list1.size()); // 11個元素。
/*
int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity,oldCapacity >> 1);
*/
// 100 二進制轉換成10進制: 00000100右移一位 00000010 (2) 【4 / 2】
// 原先是4、現在增長:2,增長之後是6,增長之後的容量是之前容量的:1.5倍。
// 6是4的1.5倍
}
}
HashSet集合轉換爲List集合
代碼演示:
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
/*
集合ArrayList的構造方法
*/
public class ArrayListTest02 {
public static void main(String[] args) {
// 默認初始化容量10
List myList1 = new ArrayList();
// 指定初始化容量100
List myList2 = new ArrayList(100);
// 創建一個HashSet集合
Collection c = new HashSet();
// 添加元素到Set集合
c.add(100);
c.add(200);
c.add(900);
c.add(50);
// 通過這個構造方法就可以將HashSet集合轉換成List集合。
List myList3 = new ArrayList(c);
for(int i = 0; i < myList3.size(); i++){
System.out.println(myList3.get(i));
}
}
}
Vactor集合
1、底層也是一個數組。
2、初始化容量:10
3、怎麼擴容的?
擴容之後是原容量的2倍。
10--> 20 --> 40 --> 80
4、ArrayList集合擴容特點:
ArrayList集合擴容是原容量1.5倍。
5、Vector中所有的方法都是線程同步的,都帶有synchronized關鍵字,是線程安全的。效率比較低,使用較少了。
6、怎麼將一個線程不安全的ArrayList集合轉換成線程安全的呢?
使用集合工具類:
java.util.Collections;
java.util.Collection 是集合接口。
java.util.Collections 是集合工具類。
代碼演示
import java.util.*;
public class VectorTest {
public static void main(String[] args) {
// 創建一個Vector集合
List vector = new Vector();
//Vector vector = new Vector();
// 添加元素
// 默認容量10個。
vector.add(1);
vector.add(2);
vector.add(3);
vector.add(4);
vector.add(5);
vector.add(6);
vector.add(7);
vector.add(8);
vector.add(9);
vector.add(10);
// 滿了之後擴容(擴容之後的容量是20.)
vector.add(11);
Iterator it = vector.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
// 這個可能以後要使用!!!!
List myList = new ArrayList(); // 非線程安全的。
// 變成線程安全的
Collections.synchronizedList(myList); // 這裏沒有辦法看效果,因爲多線程沒學,你記住先!
// myList集合就是線程安全的了。
myList.add("111");
myList.add("222");
myList.add("333");
}
}
鏈表:LinkedList
鏈表的優點:
由於鏈表上的元素在空間存儲上內存地址不連續。
所以隨機增刪元素的時候不會有大量元素位移,因此隨機增刪效率較高。
在以後的開發中,如果遇到隨機增刪集合中元素的業務比較多時,建議
使用LinkedList。
LinkedList集合底層也是有下標的。
注意:ArrayList之所以檢索效率比較高,不是單純因爲下標的原因。是因爲底層數組發揮的作用。
LinkedList集合照樣有下標,但是檢索/查找某個元素的時候效率比較低,因爲只能從頭節點開始一個一個遍歷。
鏈表的缺點:
不能通過數學表達式計算被查找元素的內存地址,每一次查找都是從頭
節點開始遍歷,直到找到爲止。所以LinkedList集合檢索/查找的效率
較低。
ArrayList:把檢索發揮到極致。(末尾添加元素效率還是很高的。)
LinkedList:把隨機增刪發揮到極致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
鏈表沒有初始化容量,最初鏈表是沒有任何元素的,first和last都是null。
代碼演示
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/*
*/
public class LinkedListTest01 {
public static void main(String[] args) {
List list = new LinkedList();
list.add("a");
list.add("b");
list.add("c");
for(int i = 0; i <list.size(); i++){
Object obj = list.get(i);
System.out.println(obj);
}
// LinkedList集合有初始化容量嗎?沒有。
// 最初這個鏈表中沒有任何元素。first和last引用都是null。
// 不管是LinkedList還是ArrayList,以後寫代碼時不需要關心具體是哪個集合。
// 因爲我們要面向接口編程,調用的方法都是接口中的方法。
//List list2 = new ArrayList(); // 這樣寫表示底層你用了數組。
List list2 = new LinkedList(); // 這樣寫表示底層你用了雙向鏈表。
// 以下這些方法你面向的都是接口編程。
list2.add("123");
list2.add("456");
list2.add("789");
for(int i = 0; i < list2.size(); i++){
System.out.println(list2.get(i));
}
}
}
一類是以鍵值對兒的方式存儲元素
以鍵值對的方式存儲元素,這一類集合中超級父接口:java.util.Map;
Map
java.util.Map接口中常用的方法:
1、Map和Collection沒有繼承關係。
2、Map集合以key和value的方式存儲數據:鍵值對
key和value都是引用數據類型。
key和value都是存儲對象的內存地址。
key起到主導的地位,value是key的一個附屬品。
3、Map接口中常用方法:
V put(K key, V value) 向Map集合中添加鍵值對
V get(Object key) 通過key獲取value
void clear() 清空Map集合
boolean containsKey(Object key) 判斷Map中是否包含某個key
boolean containsValue(Object value) 判斷Map中是否包含某個value
boolean isEmpty() 判斷Map集合中元素個數是否爲0
V remove(Object key) 通過key刪除鍵值對
int size() 獲取Map集合中鍵值對的個數。
Collection<V> values() 獲取Map集合中所有的value,返回一個Collection
Set<K> keySet() 獲取Map集合所有的key(所有的鍵是一個set集合)
Set<Map.Entry<K,V>> entrySet()
將Map集合轉換成Set集合
假設現在有一個Map集合,如下所示:
map1集合對象
key value
----------------------------
1 zhangsan
2 lisi
3 wangwu
4 zhaoliu
Set set = map1.entrySet();
set集合對象
1=zhangsan 【注意:Map集合通過entrySet()方法轉換成的這個Set集合,Set集合中元素的類型是 Map.Entry<K,V>】
2=lisi 【Map.Entry和String一樣,都是一種類型的名字,只不過:Map.Entry是靜態內部類,是Map中的靜態內部類】
3=wangwu
4=zhaoliu ---> 這個東西是個什麼?Map.Entry
代碼演示:
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(1, "zhangsan"); // 1在這裏進行了自動裝箱。
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
// 通過key獲取value
String value = map.get(2);
System.out.println(value);
// 獲取鍵值對的數量
System.out.println("鍵值對的數量:" + map.size());
// 通過key刪除key-value
map.remove(2);
System.out.println("鍵值對的數量:" + map.size());
// 判斷是否包含某個key
// contains方法底層調用的都是equals進行比對的,所以自定義的類型需要重寫equals方法。
System.out.println(map.containsKey(new Integer(4))); // true
// 判斷是否包含某個value
System.out.println(map.containsValue(new String("wangwu"))); // true
// 獲取所有的value
Collection<String> values = map.values();
// foreach
for(String s : values){
System.out.println(s);
}
// 清空map集合
map.clear();
System.out.println("鍵值對的數量:" + map.size());
// 判斷是否爲空
System.out.println(map.isEmpty()); // true
}
}
Map集合遍歷:
代碼演示:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/*
Map集合的遍歷。【非常重要】
*/
public class MapTest02 {
public static void main(String[] args) {
// 第一種方式:獲取所有的key,通過遍歷key,來遍歷value
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
// 遍歷Map集合
// 獲取所有的key,所有的key是一個Set集合
Set<Integer> keys = map.keySet();
// 遍歷key,通過key獲取value
// 迭代器可以
/*Iterator<Integer> it = keys.iterator();
while(it.hasNext()){
// 取出其中一個key
Integer key = it.next();
// 通過key獲取value
String value = map.get(key);
System.out.println(key + "=" + value);
}*/
// foreach也可以
for(Integer key : keys){
System.out.println(key + "=" + map.get(key));
}
// 第二種方式:Set<Map.Entry<K,V>> entrySet()
// 以上這個方法是把Map集合直接全部轉換成Set集合。
// Set集合中元素的類型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
// 遍歷Set集合,每一次取出一個Node
// 迭代器
/*Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while(it2.hasNext()){
Map.Entry<Integer,String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value);
}*/
// foreach
// 這種方式效率比較高,因爲獲取key和value都是直接從node對象中獲取的屬性值。
// 這種方式比較適合於大數據量。
for(Map.Entry<Integer,String> node : set){
System.out.println(node.getKey() + "--->" + node.getValue());
}
}
}
HashMap集合
HashMap集合:
1、HashMap集合底層是哈希表/散列表的數據結構。
2、哈希表是一個怎樣的數據結構呢?
哈希表是一個數組和單向鏈表的結合體。
數組:在查詢方面效率很高,隨機增刪方面效率很低。
單向鏈表:在隨機增刪方面效率較高,在查詢方面效率很低。
哈希表將以上的兩種數據結構融合在一起,充分發揮它們各自的優點。
3、HashMap集合底層的源代碼:
public class HashMap{
// HashMap底層實際上就是一個數組。(一維數組)
Node<K,V>[] table;
// 靜態的內部類HashMap.Node
static class Node<K,V> {
final int hash; // 哈希值(哈希值是key的hashCode()方法的執行結果。hash值通過哈希函數/算法,可以轉換存儲成數組的下標。)
final K key; // 存儲到Map集合中的那個key
V value; // 存儲到Map集合中的那個value
Node<K,V> next; // 下一個節點的內存地址。
}
}
哈希表/散列表:一維數組,這個數組中每一個元素是一個單向鏈表。(數組和鏈表的結合體。)
4、最主要掌握的是:
map.put(k,v)
v = map.get(k)
以上這兩個方法的實現原理,是必須掌握的。
5、HashMap集合的key部分特點:
無序,不可重複。
爲什麼無序? 因爲不一定掛到哪個單向鏈表上。
不可重複是怎麼保證的? equals方法來保證HashMap集合的key不可重複。
如果key重複了,value會覆蓋。
放在HashMap集合key部分的元素其實就是放到HashSet集合中了。
所以HashSet集合中的元素也需要同時重寫hashCode()+equals()方法。
6、哈希表HashMap使用不當時無法發揮性能!
假設將所有的hashCode()方法返回值固定爲某個值,那麼會導致底層哈希表變成了
純單向鏈表。這種情況我們成爲:散列分佈不均勻。
什麼是散列分佈均勻?
假設有100個元素,10個單向鏈表,那麼每個單向鏈表上有10個節點,這是最好的,
是散列分佈均勻的。
假設將所有的hashCode()方法返回值都設定爲不一樣的值,可以嗎,有什麼問題?
不行,因爲這樣的話導致底層哈希表就成爲一維數組了,沒有鏈表的概念了。
也是散列分佈不均勻。
散列分佈均勻需要你重寫hashCode()方法時有一定的技巧。
7、重點:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同時重寫hashCode和equals方法。
8、HashMap集合的默認初始化容量是16,默認加載因子是0.75
這個默認加載因子是當HashMap集合底層數組的容量達到75%的時候,數組開始擴容。
重點,記住:HashMap集合初始化容量必須是2的倍數,這也是官方推薦的,
這是因爲達到散列均勻,爲了提高HashMap集合的存取效率,所必須的。
代碼演示:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
*/
public class HashMapTest01 {
public static void main(String[] args) {
// 測試HashMap集合key部分的元素特點
// Integer是key,它的hashCode和equals都重寫了。
Map<Integer,String> map = new HashMap<>();
map.put(1111, "zhangsan");
map.put(6666, "lisi");
map.put(7777, "wangwu");
map.put(2222, "zhaoliu");
map.put(2222, "king"); //key重複的時候value會自動覆蓋。
System.out.println(map.size()); // 4
// 遍歷Map集合
Set<Map.Entry<Integer,String>> set = map.entrySet();
for(Map.Entry<Integer,String> entry : set){
// 驗證結果:HashMap集合key部分元素:無序不可重複。
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
HashCode
1、向Map集合中存,以及從Map集合中取,都是先調用key的hashCode方法,然後再調用equals方法!
equals方法有可能調用,也有可能不調用。
拿put(k,v)舉例,什麼時候equals不會調用?
k.hashCode()方法返回哈希值,
哈希值經過哈希算法轉換成數組下標。
數組下標位置上如果是null,equals不需要執行。
拿get(k)舉例,什麼時候equals不會調用?
k.hashCode()方法返回哈希值,
哈希值經過哈希算法轉換成數組下標。
數組下標位置上如果是null,equals不需要執行。
2、注意:如果一個類的equals方法重寫了,那麼hashCode()方法必須重寫。
並且equals方法返回如果是true,hashCode()方法返回的值必須一樣。
equals方法返回true表示兩個對象相同,在同一個單向鏈表上比較。
那麼對於同一個單向鏈表上的節點來說,他們的哈希值都是相同的。
所以hashCode()方法的返回值也應該相同。
3、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是這兩個方法需要同時生成。
4、終極結論:
放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同時重寫hashCode方法和equals方法。
5、對於哈希表數據結構來說:
如果o1和o2的hash值相同,一定是放到同一個單向鏈表上。
當然如果o1和o2的hash值不同,但由於哈希算法執行結束之後轉換的數組下標可能相同,此時會發生“哈希碰撞”。
HashMap集合key部分允許null嗎?
允許
但是要注意:HashMap集合的key null值只能有一個。
代碼演示(深入版):
import java.util.Objects;
import java.util.HashSet;
import java.util.Set;
public class HashMapTest02 {
public static void main(String[] args) {
Student s1 = new Student("zhangsan");
Student s2 = new Student("zhangsan");
// 重寫equals方法之前是false
//System.out.println(s1.equals(s2)); // false
// 重寫equals方法之後是true
System.out.println(s1.equals(s2)); //true (s1和s2表示相等)
System.out.println("s1的hashCode=" + s1.hashCode()); //284720968 (重寫hashCode之後-1432604525)
System.out.println("s2的hashCode=" + s2.hashCode()); //122883338 (重寫hashCode之後-1432604525)
// s1.equals(s2)結果已經是true了,表示s1和s2是一樣的,相同的,那麼往HashSet集合中放的話,
// 按說只能放進去1個。(HashSet集合特點:無序不可重複)
Set<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
System.out.println(students.size()); // 這個結果按說應該是1. 但是結果是2.顯然不符合HashSet集合存儲特點。怎麼辦?
}
}
class Student {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// hashCode
// equals(如果學生名字一樣,表示同一個學生。)
/*public boolean equals(Object obj){
if(obj == null || !(obj instanceof Student)) return false;
if(obj == this) return true;
Student s = (Student)obj;
return this.name.equals(s.name);
}*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
Hashtable
Hashtable的key可以爲null嗎?
Hashtable的key和value都是不能爲null的。
HashMap集合的key和value都是可以爲null的。
Hashtable方法都帶有synchronized:線程安全的。
線程安全有其它的方案,這個Hashtable對線程的處理
導致效率較低,使用較少了。
Hashtable和HashMap一樣,底層都是哈希表數據結構。
Hashtable的初始化容量是11,默認加載因子是:0.75f
Hashtable的擴容是:原容量 * 2 + 1
代碼演示
import java.util.Hashtable;
import java.util.Map;
public class HashtableTest01 {
public static void main(String[] args) {
Map map = new Hashtable();
//map.put(null, "123");
map.put(100, null);
}
}
Properties
代碼演示:
Prope rties是一個Map集合,繼承Hashtable,Properties的key和value都是String類型。
Properties被稱爲屬性類對象。
Properties是線程安全的。
import java.util.Properties;
public class PropertiesTest01 {
public static void main(String[] args) {
// 創建一個Properties對象
Properties pro = new Properties();
// 需要掌握Properties的兩個方法,一個存,一個取。
pro.setProperty("url", "jdbc:mysql://localhost:3306/bjpowernode");
pro.setProperty("driver","com.mysql.jdbc.Driver");
pro.setProperty("username", "root");
pro.setProperty("password", "123");
// 通過key獲取value
String url = pro.getProperty("url");
String driver = pro.getProperty("driver");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
System.out.println(url);
System.out.println(driver);
System.out.println(username);
System.out.println(password);
}
}
TreeSet
1、TreeSet集合底層實際上是一個TreeMap
2、TreeMap集合底層是一個二叉樹。
3、放到TreeSet集合中的元素,等同於放到TreeMap集合key部分了。
4、TreeSet集合中的元素:無序不可重複,但是可以按照元素的大小順序自動排序。(根據字典順序排序)
稱爲:可排序集合。
對自定義的類型來說,TreeSet可以排序嗎?
可以排序,但是放在TreeSet集合中的元素需要實現java.lang.Comparable接口。
並且實現(重寫)compareTo方法。equals可以不寫。
如果不重寫方法會出現以下異常:
java.lang.ClassCastException:
class com.bjpowernode.javase.collection.Person
cannot be cast to class java.lang.Comparable
出現這個異常的原因是:
Person類沒有實現java.lang.Comparable接口。
代碼演示:(TreeSet排序)
import java.util.TreeSet;
public class TreeSetTest04 {
public static void main(String[] args) {
Customer c1 = new Customer(32);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
Customer c4 = new Customer(25);
// 創建TreeSet集合
TreeSet<Customer> customers = new TreeSet<>();
// 添加元素
customers.add(c1);
customers.add(c2);
customers.add(c3);
customers.add(c4);
// 遍歷
for (Customer c : customers){
System.out.println(c);
}
}
}
// 放在TreeSet集合中的元素需要實現java.lang.Comparable接口。
// 並且實現compareTo方法。equals可以不寫。
class Customer implements Comparable<Customer>{
int age;
public Customer(int age){
this.age = age;
}
// 需要在這個方法中編寫比較的邏輯,或者說比較的規則,按照什麼進行比較!
// k.compareTo(t.key)
// 拿着參數k和集合中的每一個k進行比較,返回值可能是>0 <0 =0
// 比較規則最終還是由程序員指定的:例如按照年齡升序。或者按照年齡降序。
@Override
public int compareTo(Customer c) { // c1.compareTo(c2);
// this是c1
// c是c2
// c1和c2比較的時候,就是this和c比較。
/*int age1 = this.age;
int age2 = c.age;
if(age1 == age2){
return 0;
} else if(age1 > age2) {
return 1;
} else {
return -1;
}*/
//return this.age - c.age; // =0 >0 <0
return c.age - this.age;
}
public String toString(){
return "Customer[age="+age+"]";
}
}
代碼演示(深入版):
import java.util.TreeSet;
/*
先按照年齡升序,如果年齡一樣的再按照姓名升序。
*/
public class TreeSetTest05 {
public static void main(String[] args) {
TreeSet<Vip> vips = new TreeSet<>();
vips.add(new Vip("zhangsi", 20));
vips.add(new Vip("zhangsan", 20));
vips.add(new Vip("king", 18));
vips.add(new Vip("soft", 17));
for(Vip vip : vips){
System.out.println(vip);
}
}
}
class Vip implements Comparable<Vip>{
String name;
int age;
public Vip(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Vip{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/*
compareTo方法的返回值很重要:
返回0表示相同,value會覆蓋。
返回>0,會繼續在右子樹上找。【10 - 9 = 1 ,1 > 0的說明左邊這個數字比較大。所以在右子樹上找。】
返回<0,會繼續在左子樹上找。
*/
@Override
public int compareTo(Vip v) {
// 寫排序規則,按照什麼進行比較。
if(this.age == v.age){
// 年齡相同時按照名字排序。
// 姓名是String類型,可以直接比。調用compareTo來完成比較。
return this.name.compareTo(v.name);
} else {
// 年齡不一樣
return this.age - v.age;
}
}
}
TreeSet的第二種排序方法:比較器
TreeSet集合中元素可排序的第二種方式:使用比較器的方式。
最終的結論:
放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括兩種方式:
第一種:放在集合中的元素實現java.lang.Comparable接口。
第二種:在構造TreeSet或者TreeMap集合的時候給它傳一個比較器對象。
Comparable和Comparator怎麼選擇呢?
當比較規則不會發生改變的時候,或者說當比較規則只有1個的時候,建議實現Comparable接口。
如果比較規則有多個,並且需要多個比較規則之間頻繁切換,建議使用Comparator接口。
Comparator接口的設計符合OCP原則。
代碼演示:
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetTest06 {
public static void main(String[] args) {
// 創建TreeSet集合的時候,需要使用這個比較器。
// TreeSet<WuGui> wuGuis = new TreeSet<>();//這樣不行,沒有通過構造方法傳遞一個比較器進去。
// 給構造方法傳遞一個比較器。
//TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
// 大家可以使用匿名內部類的方式(這個類沒有名字。直接new接口。)
TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.age - o2.age;
}
});
wuGuis.add(new WuGui(1000));
wuGuis.add(new WuGui(800));
wuGuis.add(new WuGui(810));
for(WuGui wuGui : wuGuis){
System.out.println(wuGui);
}
}
}
// 烏龜
class WuGui{
int age;
public WuGui(int age){
this.age = age;
}
@Override
public String toString() {
return "小烏龜[" +
"age=" + age +
']';
}
}
// 單獨在這裏編寫一個比較器
// 比較器實現java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
/*
class WuGuiComparator implements Comparator<WuGui> {
@Override
public int compare(WuGui o1, WuGui o2) {
// 指定比較規則
// 按照年齡排序
return o1.age - o2.age;
}
}
*/
Collections工具類
java.util.Collection 集合接口
java.util.Collections 集合工具類,方便集合的操作。
代碼演示:
import java.util.*;
public class CollectionsTest {
public static void main(String[] args) {
// ArrayList集合不是線程安全的。
List<String> list = new ArrayList<>();
// 變成線程安全的
Collections.synchronizedList(list);
// 排序
list.add("abf");
list.add("abx");
list.add("abc");
list.add("abe");
Collections.sort(list);
for(String s : list){
System.out.println(s);
}
List<WuGui2> wuGuis = new ArrayList<>();
wuGuis.add(new WuGui2(1000));
wuGuis.add(new WuGui2(8000));
wuGuis.add(new WuGui2(500));
// 注意:對List集合中元素排序,需要保證List集合中的元素實現了:Comparable接口。
Collections.sort(wuGuis);
for(WuGui2 wg : wuGuis){
System.out.println(wg);
}
// 對Set集合怎麼排序呢?
Set<String> set = new HashSet<>();
set.add("king");
set.add("kingsoft");
set.add("king2");
set.add("king1");
// 將Set集合轉換成List集合
List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for(String s : myList) {
System.out.println(s);
}
// 這種方式也可以排序。
//Collections.sort(list集合, 比較器對象);
}
}
class WuGui2 implements Comparable<WuGui2>{
int age;
public WuGui2(int age){
this.age = age;
}
@Override
public int compareTo(WuGui2 o) {
return this.age - o.age;
}
@Override
public String toString() {
return "WuGui2{" +
"age=" + age +
'}';
}
}
泛型(JDK5之後)
泛型這種語法機制,旨在程序編譯階段起作用,只是給編譯器參考的。(運行階段泛型沒用)
泛型的好處:
集合中存儲的元素類型統一了。
從集合中取出來的元素類型是泛型指定的類型,不需要進行大量的“向下轉型”
泛型的缺點:
導致集合中存儲的怨怒缺乏多樣性。
大多數業務中,集合中元素的類型還是統一的,所以這種泛型特性被大家所認可。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericTest01 {
public static void main(String[] args) {
// JDK8之後引入了自動推斷機制(又稱鑽石表達式)
//以下兩種寫法均可,JDK8以後最好用第二種
//List<Animal> myList = new ArrayList<Animal>();
List<Animal> myList = new ArrayList<>();
Cat c = new Cat();
Bird b = new Bird();
myList.add(c);
myList.add(b);
//獲取迭代器,這個表示迭代器迭代的是Animal類型
Iterator<Animal> it = myList.iterator();
while (it.hasNext()){
//使用泛型之後,每一次迭代返回的數據都是Animal類型
Animal a = it.next();
//調用父類方法不需要進行強制類型轉換了,直接調用
a.move();
//調用子類方法需要進行強制類型轉換。
if(a instanceof Cat){
Cat c2 = (Cat)a;
c2.catMove();
}
if (a instanceof Bird){
Bird b2 = (Bird)a;
b2.birdmove();
}
};
}
}
class Animal{
public void move(){
System.out.println("動物在走路");
}
}
class Cat extends Animal{
public void catMove(){
System.out.println("貓在走貓步");
}
}
class Bird extends Animal{
public void birdmove(){
System.out.println("鳥兒在飛翔");
}
}
foreach使用
增強for(foreach)
語法:
for(元素數據類型 變量名 : 數組或集合){
}
代碼演示
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
集合使用foreach
*/
public class ForEachTest02 {
public static void main(String[] args) {
// 創建List集合
List<String> strList = new ArrayList<>();
// 添加元素
strList.add("hello");
strList.add("world!");
strList.add("kitty!");
// 遍歷,使用迭代器方式
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
// 使用下標方式(只針對於有下標的集合)
for(int i = 0; i < strList.size(); i++){
System.out.println(strList.get(i));
}
// 使用foreach
for(String s : strList){ // 因爲泛型使用的是String類型,所以是:String s
System.out.println(s);
}
List<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
list.add(300);
for(Integer i : list){ // i代表集合中的元素
System.out.println(i);
}
}
}
代碼大總結(所需掌握基礎描述)
主要掌握的內容:
1.每個計劃對象的創建(new)
2,向集合中添加元素
3,從集合中取出某個元素
4,遍歷集合
主要的集合類:
ArrayList
LinkedList
HashSet(HashMap中的key,存儲在HashMap集合key的元素需要同時重寫hashCode + equals)
TreeSet
HashMap
Properties
TreeMap
代碼演示:ArrayList
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
public class ArrayListTest {
public static void main(String[] args) {
//創建ArrayList集合對象
//ArrayList<String> list = new ArrayList<>();
//創建LinkedList
LinkedList<String> list = new LinkedList<>();
//往集合裏面添加元素
list.add("123123");
list.add("123asd3");
list.add("123ad3");
list.add("11e123");
//從集合中取出某個元素:list有下標,用下標取出
String s = list.get(0);
System.out.println(s);
//遍歷:forr循環遍歷
System.out.println("for循環遍歷-----");
for (int i = 0; i <list.size() ; i++) {
String s1 = list.get(i);
System.out.println(s1);
}
//迭代器遍歷
System.out.println("以下爲迭代器遍歷:——————");
Iterator<String> it = list.iterator();
while (it.hasNext()){
String s2 = it.next();
System.out.println(s2);
}
System.out.println("while循環改爲for循環遍歷----");
for (Iterator<String> it2 = list.iterator(); it.hasNext();){
System.out.println(it2.next());
}
//foreach遍歷
System.out.println("以下爲foreach遍歷---------");
for (String s3 : list){
System.out.println(s3);
}
}
}
代碼演示:HashMap
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class HashMapTest {
public static void main(String[] args) {
//創建map集合:
HashMap<Integer,String> map = new HashMap<>();
map.put(1,"asda");
map.put(56,"dsf");
map.put(21,"sdf");
map.put(13,"asda");
map.put(1,"adasd");//key重複會覆蓋
//獲取map集合的個數
System.out.println(map.size());
//獲取map集合key爲2的數據
System.out.println(map.get(2));
//Map集合遍歷
Set<Integer> keys = map.keySet();
for (Integer key : keys){
System.out.println(key + " = " +map.get(key));
}
System.out.println("-----------");
//第二種方法:將Map集合轉換爲Set集合,Set集合中每個元素是Node,Node節點中有key,value
Set<Map.Entry<Integer,String>> nodes = map.entrySet();
for (Map.Entry<Integer,String> node : nodes){
System.out.println(node.getKey() + " = " +node.getValue());
}
}
}
代碼演示:HashSet
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
/*
HashSet的特點:無序不可重複。
HashSet是HashMap的key值。
自定義類中需要重寫toString,HashCode,equals方法
* */
public class HashSetTest {
public static void main(String[] args) {
//創建一個HashSet集合
HashSet<Student> set = new HashSet<>();
//添加元素:無序不可重複
set.add(new Student(123,"a"));
set.add(new Student(235,"a"));
set.add(new Student(123,"a"));
//迭代器遍歷集合
Iterator<Student> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("---------");
//foreach遍歷集合
for (Student s : set){
System.out.println(s);
}
}
}
class Student{
int num;
String name;
public Student(int num, String name) {
this.num = num;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return num == student.num &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(num, name);
}
@Override
public String toString() {
return "Student{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
}
代碼演示:TreeSet
import java.util.Comparator;
import java.util.Iterator;
import java.util.Objects;
import java.util.TreeSet;
/*
* TreeSet集合有序不可重複
* */
public class TreeSetTst {
public static void main(String[] args) {
TreeSet<A> treeSet = new TreeSet<>();
treeSet.add(new A("1"));
treeSet.add(new A("1"));
treeSet.add(new A("7"));
treeSet.add(new A("2"));
//迭代器遍歷
Iterator<A> it = treeSet.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
System.out.println("------------");
//foreach遍歷
for (A s : treeSet){
System.out.println(s);
}
System.out.println("----------");
//new BComparator()比較器
TreeSet<B> tree = new TreeSet<>(new BComparator());
tree.add(new B(1,"asda"));
tree.add(new B(5,"asda"));
tree.add(new B(1,"ahdf"));
tree.add(new B(2,"wer"));
//迭代器遍歷
Iterator<B> iterator2 = tree.iterator();
while (iterator2.hasNext()){
System.out.println(iterator2.next());
}
System.out.println("------------");
//將它倒序:傳入比較器,匿名內部類
TreeSet<String> treeSet1 = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.valueOf(o2) - Integer.valueOf(o1);//o1-o2爲升序,o2-o1爲降序
}
});
treeSet1.add("11");
treeSet1.add("12");
treeSet1.add("19");
treeSet1.add("11");
treeSet1.add("16");
Iterator<String> it2 = treeSet1.iterator();
while (it2.hasNext()){
System.out.println(it2.next());
}
}
}
//第一種自定義類,比較器
class A implements Comparable<A>{
String name;
public A(String name) {
this.name = name;
}
@Override
public String toString() {
return "A{" +
"name='" + name + '\'' +
'}';
}
@Override
public int compareTo(A o) {
return Integer.valueOf(this.name)-Integer.valueOf(o.name);
}
}
//第二種自定義類的比較器Comparator
class B{
int i;
String name;
public B(int i, String name) {
this.i = i;
this.name = name;
}
@Override
public String toString() {
return "B{" +
"i=" + i +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
B b = (B) o;
return i == b.i &&
Objects.equals(name, b.name);
}
@Override
public int hashCode() {
return Objects.hash(i, name);
}
}
//Comparator比較器:
class BComparator implements Comparator<B>{
@Override
public int compare(B o1, B o2) {
return o1.i-o2.i;
}
}
代碼演示:ProperTies
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
public class ProperTiesTest {
public static void main(String[] args) {
Properties pro = new Properties();
//存數據:
pro.setProperty("wqe","ad");
pro.setProperty("qwe","zfa");
pro.setProperty("qwe","fhgg");
//取數據:
String username = pro.getProperty("qwe");
String password = pro.getProperty("wqe");
System.out.println(username);
System.out.println(password);
}
}