Java基礎16--集合框架
16-1,集合框架Vector集合
1,Vector是向量,底層為陣列結構,可以通過其elements()方法獲取一個Enumeration物件,這個物件的功能於Iterator的功能相同,Iterator比他多了移除的操作,所以現在一般不用Vector,都用Iterator。
通過Enumeration迭代Vector集合的元素示例:
public class VectorDemo { public static void main(String[] args) { Vector v = new Vector(); v.addElement("abc1"); v.addElement("abc2"); v.addElement("abc3"); v.addElement("abc4"); Enumeration en = v.elements(); while(en.hasMoreElements()) { System.out.println("nextElements:" + en.nextElement()); } Iterator it = v.iterator(); while(it.hasNext()) { System.out.println("next:" + it.next()); } } }
用Iterator替代Enumeration是因為Enumeration和裡面的方法名字太長,簡化書寫。
16-2,LinkedList集合
1,基本操作
addFirst():向頭部新增一個元素。
getFirst():獲取第一個元素但不刪除。
removeFirst():獲取第一個元素並刪除。
2,示例:
public class Demo { public static void main(String[] args) { LinkedList link = new LinkedList(); link.addFirst("abc1"); link.addFirst("abc2"); link.addFirst("abc3"); link.addFirst("abc4"); System.out.println(link); System.out.println(link.getFirst());//abc4 System.out.println(link.getFirst());//abc4,不刪除 //System.out.println(link.removeFirst());//abc4 //System.out.println(link.removeFirst());//abc3 while(!link.isEmpty()) { System.out.println(link.removeLast());//abc1,abc2,abc3,abc4 } } }
16-3,LinkedList練習-模擬棧和佇列
需求:請使用LinkedList來模擬一個堆疊或者佇列資料結構。
堆疊:先進後出。
佇列:先進先出。
我們應該描述一個這樣的容器,給使用者提供一個容器物件完成這兩種結構中的一種。
public class LinkedTest { public static void main(String[] args) { DuiLie d1 = new DuiLie(); d1.myAdd("abc1"); d1.myAdd("abc2"); d1.myAdd("abc3"); d1.myAdd("abc4"); while(!d1.isNull()) { System.out.println(d1.myGet()); } } } class Duilie { private LinkedList list; public DuiLie() { list = new LinkedList(); } //佇列的新增元素功能 public void myAdd(Object obj) { list.addLast(obj); } public Object myGet() { return list.removeFirst(); } public boolean isNull() { return list.isEmpty(); } }
實現了佇列。
16-4,ArrayList儲存自定義物件
例如儲存Person物件。
Person類:
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
ArrayList儲存自定義物件Person類。
public class ArrayListTest {
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add(new Person("lisi1",21));
al.add(new Person("lisi2",22));
al.add(new Person("lisi3",23));
al.add(new Person("lisi4",24));
Iterator it = al.iterator();
while(it.hasNext()) {
//System.out.println(((Person)it.next()).getName()+"::"+((Person)it.next()).getAge());
/*
上面這種方法會出現錯誤結果,在SOP中,每next一次,就想下走一個物件,也就是說,
第一個next讀的是lisi1的name,然後跳轉至下一個物件,後面的next讀的是lisi2的age,
所以出現lisi1,22,lisi3,24的結果;
下面的方法,先建立一個物件把讀取的物件賦給p,再SOP就不會出錯了。
*/
Person p = (Person)it.next();
System.out.println(p.getName()+"---"+p.getAge());
}
}
}
16-5,HashSet集合
1,Set集合:元素不可以重複,取出元素的順序和存入的順序不一定一致,是無序的。無序是因為HashSet中的元素的地址使用系統的雜湊演算法算出來的,雜湊演算法算出的位置是無序的,故無序。
Set介面中的方法和Collection一致。
HashSet內部資料結構是雜湊表,是不同步的,集合中的元素是唯一的。
示例:
public class HashSetDemo {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add("hehe");
hs.add("heihei");
hs.add("haha");
hs.add("xixi");
//hs.add("hehe");//如果有兩個hehe,則最後只打印一個,因為不許重複
Iterator it = hs.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
此程式的輸出結果是無序的。
16-6,雜湊表
1,雜湊表是雜湊演算法,是核心的一種儲存資料的演算法,查詢起來非常快,其底層為陣列結構,雜湊演算法可以根據資料的屬性來算出相應的在陣列中的位置,直接將資料儲存進去,取得時候不用遍歷,將這個資料呼叫hashCode方法,算出位置,去相應的位置有沒有該元素,就不用找其他位置的元素是否匹配了。
2,雜湊表確定元素是否相同的步驟:
(1)判斷的是兩個元素的雜湊值是否相同,如果相同,再判斷兩個物件的內容是否相同。
(2)判斷雜湊值相同,其實判斷的是物件的hashCode方法,判斷內容相同,用的是equals方法。
注意:如果雜湊值不同,是不需要判斷equals的。
16-7,HashSet儲存自定義物件
1,如何保證HashSet的元素的唯一性呢?
是通過物件的hashCode方法和equals方法來完成確定物件的唯一性的。
如果物件的hashCode值不同,那麼不用判斷equals方法,就直接儲存到雜湊表中。
如果物件的hashCode值相同,那麼要再次判斷物件的equals方法是否為true,如果為true,視為相同元素,就進行儲存。
記住:如果元素要儲存到HashSet集合中,必須覆蓋hashCode方法和equals方法。
一般情況下,如果定義的類會產生很多物件,比如:人,學生,書,通常都需要覆蓋equals,hashCode方法。
這是判斷物件是否相同的依據。
2,儲存自定義物件例項:
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
//重寫hashCode和equals方法
@Override
public int hashCode() {
return name.hashCode() + age * 27;
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(!(obj instanceof Person)) {
throw new ClassCastException("型別錯誤");
}
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
儲存自定義物件類:
//往HashSet集合中儲存Person物件,如果姓名和年齡相同,視為同一個人,相同元素。
public class HashSetTest {
public static void main(String[] args) {
HashSet hs = new HashSet();
/*
HashSet結合資料結構是雜湊表,所以儲存元素的時候,
使用元素的hashCode方法來確定位置,如果位置相同,
再通過元素的equals方法來確定是否相同。
*/
hs.add(new Person("lisi4",24));
hs.add(new Person("lisi7",27));
hs.add(new Person("lisi1",21));
hs.add(new Person("lisi9",29));
hs.add(new Person("lisi7",27));//若不重寫hashCode和equals方法,這裡也會輸出lisi7,27,不保證唯一性
Iterator it = hs.iterator();
while(it.hasNext()) {
Person p = (Person)it.next();
System.out.println(p.getName()+"..."+p.getAge());
}
}
}
16-8,集合框架-練習
需求:取出ArrayList中的重複元素。
public class ArrayListTest{
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add("abc1");
al.add("abc2");
al.add("abc2");
al.add("abc1");
al.add("abc");
System.out.println(al);
al = getSingleElement(al);
System.out.println(al);
}
public static ArrayList getSingleElement(ArrayList al) {
//定義一個臨時容器
ArrayList temp = new ArrayList();
//迭代al集合
Iterator it = al.iterator();
while(it.hasNext()) {
Object obj = it.next();
//判斷被迭代到的元素是否在臨時容器中存在。
if(!temp.contains(obj)) {
temp.add(obj);
}
}
return temp;
}
}
結果:abc1,abc2,abc2,abc1,abc
abc1,abc2,abc
16-9,LinkedHashSet集合
特點:具有可迭代順序的Set介面的雜湊表,和連結列表實現。
說白了,此集合可以保證物件唯一且有序。而HashSet是無序的。
例如:
public class LinkedHashSetDemo {
public static void main(String[] args) {
HashSet hs = new LinkedHashSet();
hs.add("haha");
hs.add("hehe");
hs.add("heihei");
hs.add("xixi");
Iterator it = hs.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
結果:haha hehe heihei xixi
輸入和輸出順序是相同的。
16-10,TreeSet集合
1,這個集合可以對Set集合中的元素進行指定順序的排序(按自然順序進行排序)。
例如:
TreeSet ts = new TreeSet();
ts.add("zhangsan",22);
ts.add("lisi",23);
ts.add("wangwu",27);
迭代...
發現第二個ts.add報錯。
原來,TreeSet要進行排序,就必須要具備排序這個功能,就Person來說,排序功能如何實現呢?
應該用java.lang中的Comparable介面,Person要想具備排序功能,實現Comparable介面即可。
Comparable介面的描述:
此介面強行對實現它的每一個類的物件進行整體排序,這種排序被稱為類的自然排序,類的compareTo方法被稱為他的自然比較方法。
2,TreeSet判斷物件唯一性的方式,就是根據比較方法的返回結果是否為0,是0,就是相同元素,不存進去。在示例中,如果Person實現了Comparable介面並重寫了compareTo方法,並且其返回0,則再執行時不會報錯,但是隻會打印出存的第一個元素,即:zhangsan,22,因為要存第二個的時候,會呼叫compareTo方法判斷是否與上一個元素相同,由於返回結果是0,即表示相同,所以後面的元素就都不往裡存了,所以只打印第一個元素。
3,以Person物件的年齡進行從小到大的排序。
將上面的Person類實現Comparable介面,然後在Person中覆寫compareTo方法。
//若要按照從大到小排序,把1變為-1,把-1變為1即可,或把this變為p,把p變為this.
public int compareTo(Object obj) {
Person p = (Person)obj;
if(this.age > p.age)
return 1;
if(this.age < p.age)
return -1;
return 0;
}
但是如果年齡相同,則後一個就打印不出來了,即:列印時少了一個Person。
這時應該加條件,如果主要條件相同,則應該比較次要條件。
修改後的compareTo方法:
public int compareTo(Object obj) {
Person p = (Person)obj;
if(this.age > p.age)
return 1;
if(this.age < p.age)
return -1;
else
/*
String類中的compareTo方法也來自於Comparable介面,
String類實現了Comparable介面
*/
return this.name.compareTo(p.name);
}
但是這麼寫爛透了...,優化:
public int compareTo(Object obj) {
Person p = (Person)obj;
int temp = this.age - p.age;
return temp == 0 ? this.name.compareTo(p.name) : temp;
}
之前在TreeSet中存String類的物件,可以進行排序,是因為String類實現了Comparable介面。
總結:TreeSet對元素進行排序的方式之一:
讓元素自身具備比較功能,元素需要實現Comparable介面,覆蓋compareTo方法。
16-11,TreeSet集合,Comparator比較器
1,如果不要按照物件中具備的自然順序,如果物件中不具備自然順序,怎麼辦?
可以使用TreeSet的第二種排序方式:讓集合自身具備比較功能。
2,TreeSet的構造器中有一個帶有Comparator引數的構造器,在建立TreeSet物件時傳入此Comparator的物件,該TreeSet可按指定的Comparator比較器進行排序。
3,java.util
介面:Comparator<T>
強行對某個物件Collection進行整體排序的比較函式。
4,單獨建立一個Java檔案,建立一個根據Person類的name進行排序的比較器。
public class ComparatorsByName implements Comparator {
public int compare(Object o1,Object o2) {
Person p1 = (Person)o1;
Person p2 = (Person)o2;
int temp = p1.getName().compareTo(p2.getName());
return temp == 0 ? p1.getAge() - p2.getAge() : temp;
}
}
然後,在建立TreeSet集合的時候,直接:
TreeSet ts = new TreeSet(new ComparatorsByName());
即可。
5,如果Person物件本身實現了Comparable介面,覆蓋了compareTo方法,而且把Person物件存入了已經實現了比較器的TreeSet中,那麼以誰的比較功能為主呢?
以比較器的功能為主,且實際開發中比較器比較常用。
實際上Java中的很多類都實現了Comparable介面,使其物件具備比較功能。
6,使集合自身具備比較功能的步驟:
定義一個類實現Comparator介面,覆蓋compare方法,將該類物件作為引數傳遞個TreeSet集合的建構函式。
16-12,TreeSet-二叉樹
1,TreeSet排序功能在底層是如何實現的呢?
他的排序功能在底層是通過二叉樹來實現的。
以下列TreeSet的新增為例,用圖表示(根據Person的age排序),
第一個進去的元素不用進行比較,以後進去的元素都要與前面的元素比較。
步驟:
第一個28進來以後,發現前面沒有元素,不做比較,直接放在第一個位置。
21進來後,跟28比較,比28小,放在28的左邊。
29進來後先跟28比較,比28大,放在28的右邊,就不用和21比較了。
25進來後先跟28比較,比28小,到左邊,再跟21比較,比21大,則放在21的右邊。
......
在樹中,每個節點都有自己的屬性,以值為21的節點舉例:
21有三個引用,分別記錄與之連結的父節點,以及左子節點和右子節點,以記錄整個集合各個元素的位置,但28沒有父,19沒有左和右。
但是樹的規模一大,就涉及到效率問題,如何解決呢?
其實,只要樹一排完,該集合就已經是一個有序的集合了,有序集合的查詢,就可以想到用二分查詢法。
其實每次向裡面新增元素的時候,都會對已有的元素進行折半,二分查詢,以確定新元素的位置,效率較高。
2,如何做到怎麼存就怎麼取的呢?
直接在比較器中返回1就行了,這樣可以保證後進來的元素經比較後總是大於前面的元素,如果要倒序取出的話,直接返回一個-1即可。
public class ComparatorsByName implements Comparator {
public int compare(Object o1,Object o2) {
return 1;
}
}
16-13,TreeSet練習
需求:按照字串的長度排序。
public class TreeSetTest {
public static void main(String[] args) {
TreeSet ts = new TreeSet(new ComparatorsByLength());
ts.add("aaaaaa");
ts.add("zz");
ts.add("nabq");
ts.add("cba");
ts.add("abc");
Iterator it = ts.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
//比較器
public class ComparatorsByLength implements Comparator {
public int compare(Object o1,Object o2) {
String s1 = (String)o1;
String s2 = (String)o2;
int temp = s1.length() - s2.length();
return temp == 0 ? s1.compareTo(s2) : temp;
}
}