咱們知道,計算機的優點在於處理大量的數據,在編程開發中,爲處理大量的數據,必須具有相應的存儲結構,以前學習的數組能夠用來存儲並處理大量類型相同的數據,可是經過上面的課後練習,會發現數組在應用中的限制:數組長度一旦肯定,就沒法更改;除非採用創建新數組,再將原數組內容拷貝過來;數組中只能存放指定類型的數據,操做不方便。在實際開發中,爲了操做方便,JDK中提供了List集合。java
List集合與數組的用途很是類似,都是用來存儲大量數據的,不一樣處有兩點:web
1. 數組長度在使用前必須肯定,一旦肯定不能改變。而List集合長度可變,無需定義。編程
2. 數組中必須存放同一類型的數據,List集合中能夠存放不一樣類型的數據。數組
List集合是Java集合框架中的一種,另外兩種集合Set和Map會在下面介紹。List集合在JDK中被封裝稱爲接口,針對List接口,有若干種實現,經常使用的有三個子類,即ArrayList、Vector和LinkedList。這三個類的功能與用法相同,但內部實現方式不一樣。下面以ArrayList爲例介紹集合的經常使用操做,Vector和LinkedList的使用方法與ArrayList相似。安全
數組與List集合的常規操做相似,下面經過代碼對比二者的用法:框架
代碼演示:數組的基本操做性能
public class ArrayDemo {學習 public static void main(String[] args) {this String[] array = new String[3];spa for (int i = 0; i < 3; i++) { array[i] = "Hello"; } String a = array[0]; } } |
代碼演示:List集合的基本操做
import java.util.ArrayList; ① public class ListDemo { public static void main(String[] args) { ArrayList list = new ArrayList(); ② for (int i = 0; i < 3; i++) { list.add("Hello"); ③ } String a = (String)list.get(0); ④ } } |
代碼解析:
① 集合框架在java.util包中,使用前必須使用import語句引入對應的類。
② List集合的定義時不須要指定大小,也不用指定集合中保存的數據類型。
③ 向List集合中添加數據時不須要制定下標,List集合會自動生成下標。
④ 獲取List集合的元素時使用get方法並傳入下標,而後強制類型轉換爲實際類型。
代碼演示:使用集合記錄學員姓名
public static void main(String[] args) { System.out.println("請輸入班級學員姓名,輸入OVER結束"); java.util.Scanner scanner = new java.util.Scanner(System.in); ArrayList list = new ArrayList(); do { String name = scanner.next(); if (name.equalsIgnoreCase("OVER")) break; list.add(name); } while (true); System.out.println(list); ① } |
代碼解析:
① List集合重寫了toString方法,能夠將集合中的元素依次輸出。
下表列出了List集合的經常使用方法:
返回類型 |
方法名稱 |
說明 |
boolean |
add(Object obj) |
加入元素,返回是否添加成功 |
boolean |
clear() |
清除集合中的元素 |
boolean |
contains(Object obj) |
查找集合中是否存在傳入的元素 |
Object |
get(int index) |
獲取指定位置的元素 |
boolean |
isEmpty() |
判斷集合是否爲空 |
Object |
remove(int index) |
刪除制定位置的元素,並返回該元素 |
int |
size() |
獲取集合大小 |
Object[] |
toArray() |
將集合轉換成一個數組 |
表: List集合的經常使用方法
下面經過實例演示各個方法的用途:
代碼演示:List集合的經常使用方法
import java.util.ArrayList; public class Demo { public static java.util.Scanner scanner = new java.util.Scanner(System.in); public static void main(String[] args) { ArrayList listA = new ArrayList(); ArrayList listB = new ArrayList(); System.out.println("請輸入A班學員姓名,輸入OVER結束"); inputName(listA); System.out.println("請輸入B班學員姓名,輸入OVER結束"); inputName(listB); //合併集合listA與listB listA.addAll(listB); System.out.println("請輸入要查找的學員姓名"); String name = scanner.next(); int pos = listA.indexOf(name); if (pos==-1) { System.out.println("沒有找到"); } else { System.out.println("找到了,位置是:" + pos); } System.out.println("請輸入要刪除的學員姓名"); String delName = scanner.next(); if (listA.remove(delName)) { System.out.println("刪除成功!"); } else { System.out.println("沒有該學員"); } } public static void inputName(ArrayList list) { do { String name = scanner.next(); if (name.equalsIgnoreCase("OVER")) break; list.add(name); } while (true); } } |
使用List集合保存對象時,主要注意如下幾點:
1. 集合中保存的是對象的引用,觀察如下代碼:
代碼演示:使用集合保存對象
import java.util.ArrayList; class Student { String name; int age; public Student(String name, int age) { this.name = name; this.age = age; } public String toString() { return name + "/" + age; } } public class Demo { public static void main(String[] args) { ArrayList list = new ArrayList(); Student stu = new Student("Tom" , 10); for (int i = 0; i < 3; i++) { stu.age = 10 + i; list.add(stu); } System.out.println(list); } } |
上面代碼的原意是在集合中保存三個Student對象,age分別爲十、十一、12,但實際輸出的age值均爲12。這是由於list集合中保存的是stu對象的引用,而在循環中stu的引用並無變化,因此循環結束後集閤中的三個元素都指向stu對象,age的值天然也是最後的12。將代碼「Student stu = new Student("Tom" , 10);」放入循環內能夠解決這一問題。
2. 使用remove、contains、indexOf等方法時,應該重寫類的equals方法,觀察如下代碼:
代碼演示:未重寫equals方法的代碼
//省略了Student類的定義 public class Demo { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(new Student("Tom" , 11)); list.add(new Student("Jerry" , 22)); list.add(new Student("Alice" , 33)); System.out.println(list.contains(new Student("Tom" , 11))); System.out.println(list.indexOf(new Student("Jerry" , 22))); System.out.println(list.remove(new Student("Alice" , 33))?"成功":"無此項"); } } |
在上例中,咱們但願判斷學員Tom是否存在,查找學員Jerry,刪除學員Alice,可是輸出的結果倒是不存在,找不到,刪不掉。這是由於List集合會調用元素的equals方法來判斷對象是否相等,而Student類沒有重寫equals方法,默認是按引用地址比較,而每一個學員對象的地址又不相同,因此出現這個現象。經過給Student類添加equals方法能夠解決這個問題:
代碼演示:重寫equals方法後的Student類
class Student { String name; int age; public Student(String name, int age) { this.name = name; this.age = age; } public boolean equals(Object obj) { if (obj == null) return false; if (!(obj instanceof Student)) return false; Student stu = (Student) obj; return stu.name.equals(this.name) && stu.age == this.age; } } |
咱們說過,ArrayList、Vector與LinkedList的使用方法相同,內部實現方式不一樣。而內部實現方式的不一樣又決定了三種集合的適用範圍,瞭解三種集合的內部實現,才能正確的選擇使用類型。
? ArrayList與Vector比較
ArrayList與Vector的內部實現相似,Vector設計爲線程安全,ArrayList設計爲非線程安全。爲了保證線程安全,Vector在性能方面稍遜於ArrayList,目前咱們編寫的都是單線程應用程序,應選擇使用ArrayList。
? ArrayList與LinkedList
ArrayList與LinkedList均設計爲非線程安全,ArrayList內部採用數組實現(與Vector相同),LinkedList內部採用鏈表結構實現。
ArrayList採用數組保存元素,意味着當大量添加元素,數組空間不足時,依然須要經過新建數組、內存複製的方式來增長容量,效率較低;而當進行對數組進行插入、刪除操做時,又會進行循環移位操做,效率也較低;只有進行按下標查詢時(get方法),使用數組效率很高。
LinkedList採用鏈表保存元素,在添加元素時只須要進行一次簡單的內存分配便可,效率較高;進行插入、刪除操做時,只需對鏈表中相鄰的元素進行修改便可,效率也很高;但進行按下標查詢時,須要對鏈表進行遍歷,效率較低。下圖演示了鏈表結構的特性:
圖: 鏈表結構,每一個元素引用後面的元素
圖: 向鏈表中插入元素,只需修改兩處引用
圖: 刪除鏈表中的元素,也只須要修改兩處引用
能夠總結出ArrayList在進行數據的新增、插入、刪除時效率較低,按下標對數據進行查找時效率較高;LinkedList正好相反。通常來講ArrayList保存常常進行查詢操做的集合,LinkedList適用於保存經常進行修改操做的集合。
1. List集合與數組的區別。
2. List集合實際上包含了3個經常使用的集合類,即ArrayList、Vector和LinkedList。
3. List集合的經常使用操做。
4. ArrayList採用數組保存元素,意味着當大量添加元素,數組空間不足時,依然須要經過新建數組、內存複製的方式來增長容量,效率較低;而當進行對數組進行插入、刪除操做時,又會進行循環移位操做,效率也較低;只有進行按下標查詢時(get方法),使用數組效率很高。
5. ArrayList與Vector的內部實現相似,Vector設計爲線程安全,ArrayList設計爲非線程安全。爲了保證線程安全,Vector在性能方面稍遜於ArrayList,目前咱們編寫的都是單線程應用程序,應選擇使用ArrayList。
6. ArrayList與LinkedList均設計爲非線程安全,ArrayList內部採用數組實現(與Vector相同),LinkedList內部採用鏈表結構實現。
7. LinkedList採用鏈表保存元素,在添加元素時只須要進行一次簡單的內存分配便可,效率較高;進行插入、刪除操做時,只需對鏈表中相鄰的元素進行修改便可,效率也很高;但進行按下標查詢時,須要對鏈表進行遍歷,效率較低。
在上面講的List集合中,可用經過List集合提供的各類方法來對其中的元素進行操做,從而能夠方便用戶操做,可是若是要從List集合中獲取一個特定的對象,操做是比較繁瑣的。
在類Person中有cardId和name兩個屬性,分別表明編號和姓名,建立兩個Person對象並存儲到ArrayList集合中 ,若是要從集合中獲取指定的對象,則必需要經過迭代整個集合來得到,以下所示:
代碼演示:Person類
public class Person { String cardId; String name; public Person(String cardId, String name) { this.cardId = cardId; this.name = name; } } |
代碼演示:從ArrayList中獲取特定的對象
public class ArrayListTest { public static void main(String[] args) { Person personA = new Person("001", "Tom"); Person personB = new Person("002", "Jack"); ArrayList list = new ArrayList(); list.add(personA); list.add(personB); for (int i = 0; i < list.size(); i++) { Person person = (Person) list.get(i); if (person.cardId.equals("002")) { System.out.println(person.name); } } } } |
從上面的示例中,咱們看到從list集合中獲取一個對象的繁瑣,有沒有簡單的方法呢?在JDK中專門提供了Map集合來存儲上面這種一對一映射關係的對象。
Map集合用於保存具備映射關係的數據,即以鍵值對(key->value)的方式來存儲數據。所以在Map集合內部有兩個集合,一個集合用於保存Map中的key(鍵),一個集合用於保存Map中的value(值),其中key和value能夠是任意數據類型數據。
圖: Map集合
Map集合中的經常使用類有Hashtable和HashMap,兩個類的功能和用法類似,下面以HashMap爲例介紹Map集合的用法。
代碼演示:Map集合使用
public class MapTest { public static void main(String[] args) { HashMap map = new HashMap(); ① map.put("001", "Tom"); ② map.put("002", "Jack"); String name = (String) map.get("002"); ③ System.out.println(name); } } |
代碼解析:
① 建立HashMap對象。
② 利用HashMap中的put方法將鍵值對形式的對象進行存儲,put方法中的第一個參數爲映射關係中key的值,put方法的第二個參數爲映射關係中value的值。
③ 利用HashMap的get方法獲取key對應的value,而後強制類型轉換爲實際類型。get中的參數爲key,返回值爲key對應的value。
下表列出了HashMap中經常使用的方法:
返回類型 |
方法名稱 |
做用 |
Object |
put(Object key,Object value) |
加入元素,返回與此key關聯的原有的value,不存在則返回null |
void |
clear() |
從集合中移除全部的元素 |
boolean |
containsKey(Object key) |
根據key從集合中判斷key是否存在 |
boolean |
containsValue(Object value) |
根據value從集合中判斷value是否存在 |
Object |
get(Object key) |
根據key返回key對應的值 |
Set |
keySet() |
返回Map集合中包含的鍵集合 |
Object |
remove(Object key) |
從集合中刪除key對應的元素,返回與key對應的原有value,不存在則返回null |
int |
size() |
返回集合中的元素的數量 |
表: HashMap經常使用方法
Map集合的綜合示例:
代碼演示:Map集合綜合演示
import java.util.HashMap; import java.util.Scanner; public class TestMap { public static void main(String[] args) { HashMap map = new HashMap(); Scanner scanner = new Scanner(System.in); for (int i = 0; i < 5; i++) { System.out.println("請輸入身份證號:"); String id = scanner.next(); System.out.println("請輸入姓名:"); String name = scanner.next(); map.put(id, name); ① } int size = map.size(); ② System.out.println("數據輸入完畢!共" + size + "條數據!\n---------------------------------"); String answer = "no"; do { System.out.println("請輸入你要查找的用戶的身份證號:"); String id = scanner.next(); if (map.containsKey(id)) { ③ String name = (String) map.get(id); ④ System.out.println("您查找的用戶姓名爲:" + name); } else { System.out.println("您查找的用戶不存在!"); } System.out.println("您還要繼續查找嗎?(yes/no)"); answer = scanner.next(); } while ("yes".equalsIgnoreCase(answer)); System.out.println("請輸入要刪除的用戶的身份證號:"); String id = scanner.next(); if (map.containsKey(id)) { String name = (String) map.remove(id); ⑤ System.out.println("用戶" + name + "刪除成功!"); } else { System.out.println("您要刪除的用戶不存在!"); } System.out.println("要格式化系統嗎?(yes/no)"); String format = scanner.next(); if ("yes".equalsIgnoreCase(format)) { map.clear(); ⑥ System.out.println("系統格式化完畢!當前系統中數據爲" + map.size() + "條"); } System.out.println("程序運行結束!"); } } |
代碼解析:
① 使用put方法將身份證號和姓名存入Map集合中。
② 使用size方法得到集合中的映射關係條數。
③ 使用containsKey方法判斷集合是否存在與key對應的映射關係。
④ 使用get方法得到身份證號對應的姓名。
⑤ 使用remove方法刪除身份證號對應的用戶,返回身份證號對應的姓名。
⑥ 使用clear方法刪除Map集合中全部的映射關係。
來看下面的一個示例:
代碼演示:Map集合中重複key
import java.util.HashMap; public class DemoMap { public static void main(String[] args) { HashMap map = new HashMap(); map.put("001", "小美"); map.put("002", "阿聰"); ① map.put("002", "小莉"); ② String name = (String) map.get("002"); System.out.println(name); } } |
注意①和②處的代碼,在向集合中添加值的時候,使用了重複的key,可是value不一樣,在下面得到key「002」的value爲多少呢?程序運行的結果是「小莉」。從結果能夠中能夠知道,Map集合中的key不能是重複的,若是重複,那麼後面添加的映射關係會覆蓋前面的映射關係。致使這樣狀況的出現主要是由於Map集合中的key的維護是依靠Set集合(立刻會學習到)完成的。
HashMap和Hashtable的操做是相同的,他們的區別以下:
? Hashtable是線程安全的,HashMap是非線程安全的。全部HashMap比Hashtable的性能更高。
? Hashtable不容許使用使用null值做爲key或value,可是HashMap是能夠的。
Set集合和List集合的不少的用法是相同的。可是Set集合中的元素是無序的,元素也是不能重複的。Set集合中經常使用類爲HashSet。
HashSet類中經常使用的方法以下:
返回類型 |
方法名稱 |
做用 |
boolean |
add(Object obj) |
加入元素 |
void |
clear() |
移除Set集合中全部元素 |
boolean |
contains(Object obj) |
判斷Set集合中是否包含指定元素 |
boolean |
isEmpty() |
判斷Set集合是否爲空 |
Iterator |
iterator() |
返回Set集合中對元素迭代的迭代器 |
boolean |
remove(Object obj) |
從集合中刪除元素 |
Int |
size() |
返回集合中的元素數量 |
表: HashSet類經常使用方法
經過上面的表,能夠清楚的看到Set集合的用法和List集合是類似的,可是須要注意Set集合的迭代和List集合是不一樣的,List的集合的迭代能夠經過for循環得到索引來進行,可是Set集合的迭代必需要經過迭代器進行。
代碼演示:Set集合的迭代
import java.util.HashSet; import java.util.Iterator; public class SetIterator { public static void main(String[] args) { HashSet set = new HashSet(); set.add("a"); set.add("b"); set.add("c"); Iterator iter = set.iterator(); ① while (iter.hasNext()) { ② String str = (String) iter.next(); ③ System.out.println(str); } } } |
代碼解析:
① 經過Set集合的iterator()方法得到該集合的迭代器,迭代器是Iterator類的實例。
② 根據迭代器的hasNext()方法判斷集合中是否還有元素,若是有就返回true。
③ 根據迭代器的next()方法得到集合中的元素,並強制類型轉換爲目標類型。
從上面例子的運行結果,能夠看出Set中的元素是無序的。經過Set集合的迭代再來學習Map集合的迭代。
代碼演示:Map集合的迭代
import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class MapIter { public static void main(String[] args) { HashMap map = new HashMap(); map.put("001", "小美"); map.put("002", "阿聰"); map.put("003", "小黑"); HashSet keys = (HashSet) map.keySet(); ① Iterator iter = keys.iterator(); while (iter.hasNext()) { String key = (String) iter.next(); ② String value = (String) map.get(key); ③ System.out.println(key + ":" + value); } } } |
代碼解析:
① 調用Map集合的keySet方法得到Map集合中的key的集合。
② 得到key。
③ 根據key得到對應的value。
下面經過一個示例演示Set集合中不容許元素重複的特性:
代碼演示:向Set集合中添加劇復元素
import java.util.HashSet; import java.util.Iterator; public class Demo2 { public static void main(String[] args) { HashSet set = new HashSet(); set.add("a"); set.add("a"); set.add("c"); System.out.println("集合長度爲:" + set.size()); Iterator iter = set.iterator(); while (iter.hasNext()) { String str = (String) iter.next(); System.out.println(str); } } } |
上面的代碼輸出集合的長度爲2,並且集合中的元素只有一個a和c,從結果中能夠看出Set集合中的元素是不能重複的。帶着這個結論再來看下面的示例:
代碼演示:向Set集合中添加劇復元素
import java.util.HashSet; import java.util.Iterator; public class Demo3 { public static void main(String[] args) { Person personA = new Person("001", "Tom"); Person personB = new Person("001", "Tom"); HashSet set = new HashSet(); set.add(personA); set.add(personB); System.out.println("集合中元素個數:" + set.size()); Iterator iter = set.iterator(); while (iter.hasNext()) { Person p = (Person) iter.next(); System.out.println(p.cardId + ":" + p.name); } } } |
程序的運行結果以下:
集合中元素個數:2 001:Tom 001:Tom |
發現程序的運行結果是有「問題」的,由於Set集合中是不容許存放重複的元素的,可是兩個Person對象的屬性值是徹底相同的,怎麼還都能存放進去呢?要找到問題的答案,須要瞭解下Set集合的存放原理。
圖: Set集合存儲
從上圖中能夠看到,Set集合中的元素的無序性,可是Set集合是怎麼判斷每一個元素的存放的位置呢?在向Set集合中存放元素時,Set集合根據元素的hashCode()方法來獲取一個int類型的數據,而後根據這個數據來計算元素在集合中的位置。可是在存儲元素時會出現兩個元素的hashCode()方法返回值相同的狀況,好比上圖的對象C和D就出現了這種狀況,致使計算出元素在集合中的位置相同,這種狀況稱之爲「衝突」。若是發生了衝突,Set集合會根據發生衝突元素之間調用equals()方法進行比較,若是equals()返回值爲true,說明兩個元素爲相同的元素,這樣會致使添加操做無效。若是equals()返回值爲false,說明兩個元素不相同,這樣Set集合會將該元素進行偏移存儲。「衝突」發生的頻率越高,Set集合的性能就越低,要儘量的避免衝突的發生, 就要在類中重寫hashCode()方法,而且要儘量的保證hashCode()方法返回值是惟一的。在重寫hashCode()方法時有個技巧,就是讓對象中的數值屬性和一個素數相乘,並將積相加,對於對象類型,調用其hashCode()方法便可。
對於hashCode()和equals()兩個方法有這樣的規律,hashCode()方法返回值相同時,equals()方法比較並不必定相等,可是equals()方法比較相等,hashCode()方法返回值是相同的。
代碼演示:實現Person對象的equals和hashCode方法
public class Person { String cardId; String name; public Person(String cardId, String name) { this.cardId = cardId; this.name = name; } public int hashCode() { return cardId.hashCode() + name.hashCode(); } public boolean equals(Object obj) { if (obj == null){ return false; } if(obj instanceof Person){ Person other = (Person) obj; if (cardId == null) { if (other.cardId != null){ return false; } } else if (!cardId.equals(other.cardId)){ return false; } if (name == null) { if (other.name != null){ return false; } } else if (!name.equals(other.name)){ return false; } }else{ return false; } return true; } } |
再次運行Demo3的代碼,運行結果以下:
集合中元素個數:1 001:Tom |
? Map集合用於保存具備映射關係的數據,即以鍵值對(key->value)的方式來存儲數據。所以在Map集合內部有兩個集合,一個集合用於保存Map中的key(鍵),一個集合用於保存Map中的value(值),其中key和value能夠是任意數據類型數據。
? Set集合的特色。
? Set集合的使用及迭代。
? Map集合的迭代。