Java基础学习系列--(五)【迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类】
第一章 Iterator迭代器
1.1 Iterator接口
在程序開發(fā)中,經(jīng)常需要遍歷集合中的所有元素。針對這種需求,JDK專門提供了一個(gè)接口java.util.Iterator。
想要遍歷Collection集合,那么就要獲取該集合迭代器完成迭代操作,下面介紹一下獲取迭代器的方法:
- public Iterator iterator(): 獲取集合對應(yīng)的迭代器,用來遍歷集合中的元素的。
下面介紹一下迭代的概念:
- 迭代:即Collection集合元素的通用獲取方式。在取元素之前先要判斷集合中有沒有元素,如果有,就把這個(gè)元素取出來,繼續(xù)在判斷,如果還有就再取出出來。一直把集合中的所有元素全部取出。這種取出方式專業(yè)術(shù)語稱為迭代。
Iterator接口的常用方法如下:
- public E next():返回迭代的下一個(gè)元素。
- public boolean hasNext():如果仍有元素可以迭代,則返回 true。
接下來我們通過案例學(xué)習(xí)如何使用Iterator迭代集合中元素:
public class IteratorDemo {public static void main(String[] args) {// 使用多態(tài)方式 創(chuàng)建對象Collection<String> coll = new ArrayList<String>();// 添加元素到集合coll.add("串串星人");coll.add("吐槽星人");coll.add("汪星人");//遍歷//使用迭代器 遍歷 每個(gè)集合對象都有自己的迭代器Iterator<String> it = coll.iterator();// 泛型指的是 迭代出 元素的數(shù)據(jù)類型while(it.hasNext()){ //判斷是否有迭代元素String s = it.next();//獲取迭代出的元素System.out.println(s);}} }tips:
1.2 迭代器的實(shí)現(xiàn)原理
我們在之前案例已經(jīng)完成了Iterator遍歷集合的整個(gè)過程。當(dāng)遍歷集合時(shí),首先通過調(diào)用t集合的iterator()方法獲得迭代器對象,然后使用hashNext()方法判斷集合中是否存在下一個(gè)元素,如果存在,則調(diào)用next()方法將元素取出,否則說明已到達(dá)了集合末尾,停止遍歷元素。
Iterator迭代器對象在遍歷集合時(shí),內(nèi)部采用指針的方式來跟蹤集合中的元素,為了讓初學(xué)者能更好地理解迭代器的工作原理,接下來通過一個(gè)圖例來演示Iterator對象迭代元素的過程:
在調(diào)用Iterator的next方法之前,迭代器的索引位于第一個(gè)元素之前,不指向任何元素,當(dāng)?shù)谝淮握{(diào)用迭代器的next方法后,迭代器的索引會(huì)向后移動(dòng)一位,指向第一個(gè)元素并將該元素返回,當(dāng)再次調(diào)用next方法時(shí),迭代器的索引會(huì)指向第二個(gè)元素并將該元素返回,依此類推,直到hasNext方法返回false,表示到達(dá)了集合的末尾,終止對元素的遍歷。
第二章 數(shù)據(jù)結(jié)構(gòu)
2.1 數(shù)據(jù)結(jié)構(gòu)介紹
數(shù)據(jù)結(jié)構(gòu) : 數(shù)據(jù)用什么樣的方式組合在一起。
2.2 常見數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)存儲(chǔ)的常用結(jié)構(gòu)有:棧、隊(duì)列、數(shù)組、鏈表和紅黑樹。我們分別來了解一下:
棧
- 棧:stack,又稱堆棧,它是運(yùn)算受限的線性表,其限制是僅允許在標(biāo)的一端進(jìn)行插入和刪除操作,不允許在其他任何位置進(jìn)行添加、查找、刪除等操作。
簡單的說:采用該結(jié)構(gòu)的集合,對元素的存取有如下的特點(diǎn)
- 先進(jìn)后出(即,存進(jìn)去的元素,要在后它后面的元素依次取出后,才能取出該元素)。例如,子彈壓進(jìn)彈夾,先壓進(jìn)去的子彈在下面,后壓進(jìn)去的子彈在上面,當(dāng)開槍時(shí),先彈出上面的子彈,然后才能彈出下面的子彈。
- 棧的入口、出口的都是棧的頂端位置。
這里兩個(gè)名詞需要注意:
- 壓棧:就是存元素。即,把元素存儲(chǔ)到棧的頂端位置,棧中已有元素依次向棧底方向移動(dòng)一個(gè)位置。
- 彈棧:就是取元素。即,把棧的頂端位置元素取出,棧中已有元素依次向棧頂方向移動(dòng)一個(gè)位置。
隊(duì)列
- 隊(duì)列:queue,簡稱隊(duì),它同堆棧一樣,也是一種運(yùn)算受限的線性表,其限制是僅允許在表的一端進(jìn)行插入,而在表的另一端進(jìn)行刪除。
簡單的說,采用該結(jié)構(gòu)的集合,對元素的存取有如下的特點(diǎn):
- 先進(jìn)先出(即,存進(jìn)去的元素,要在后它前面的元素依次取出后,才能取出該元素)。例如,小火車過山洞,車頭先進(jìn)去,車尾后進(jìn)去;車頭先出來,車尾后出來。
- 隊(duì)列的入口、出口各占一側(cè)。例如,下圖中的左側(cè)為入口,右側(cè)為出口。
數(shù)組
- 數(shù)組:Array,是有序的元素序列,數(shù)組是在內(nèi)存中開辟一段連續(xù)的空間,并在此空間存放元素。就像是一排出租屋,有100個(gè)房間,從001到100每個(gè)房間都有固定編號,通過編號就可以快速找到租房子的人。
簡單的說,采用該結(jié)構(gòu)的集合,對元素的存取有如下的特點(diǎn):
-
查找元素快:通過索引,可以快速訪問指定位置的元素
-
增刪元素慢
-
指定索引位置增加元素:需要?jiǎng)?chuàng)建一個(gè)新數(shù)組,將指定新元素存儲(chǔ)在指定索引位置,再把原數(shù)組元素根據(jù)索引,復(fù)制到新數(shù)組對應(yīng)索引的位置。如下圖
-
**指定索引位置刪除元素:**需要?jiǎng)?chuàng)建一個(gè)新數(shù)組,把原數(shù)組元素根據(jù)索引,復(fù)制到新數(shù)組對應(yīng)索引的位置,原數(shù)組中指定索引位置元素不復(fù)制到新數(shù)組中。如下圖
鏈表
- 鏈表:linked list,由一系列結(jié)點(diǎn)node(鏈表中每一個(gè)元素稱為結(jié)點(diǎn))組成,結(jié)點(diǎn)可以在運(yùn)行時(shí)i動(dòng)態(tài)生成。每個(gè)結(jié)點(diǎn)包括兩個(gè)部分:一個(gè)是存儲(chǔ)數(shù)據(jù)元素的數(shù)據(jù)域,另一個(gè)是存儲(chǔ)下一個(gè)結(jié)點(diǎn)地址的指針域。我們常說的鏈表結(jié)構(gòu)有單向鏈表與雙向鏈表,那么這里給大家介紹的是單向鏈表。
簡單的說,采用該結(jié)構(gòu)的集合,對元素的存取有如下的特點(diǎn):
-
多個(gè)結(jié)點(diǎn)之間,通過地址進(jìn)行連接。例如,多個(gè)人手拉手,每個(gè)人使用自己的右手拉住下個(gè)人的左手,依次類推,這樣多個(gè)人就連在一起了。
-
查找元素慢:想查找某個(gè)元素,需要通過連接的節(jié)點(diǎn),依次向后查找指定元素
-
增刪元素快:
樹是有很多節(jié)點(diǎn)組成的
2.3. 樹基本結(jié)構(gòu)介紹
樹具有的特點(diǎn):
| 節(jié)點(diǎn) | 指樹中的一個(gè)元素 |
| 節(jié)點(diǎn)的度 | 節(jié)點(diǎn)擁有的子樹的個(gè)數(shù),二叉樹的度不大于2 |
| 葉子節(jié)點(diǎn) | 度為0的節(jié)點(diǎn),也稱之為終端結(jié)點(diǎn) |
| 高度 | 葉子結(jié)點(diǎn)的高度為1,葉子結(jié)點(diǎn)的父節(jié)點(diǎn)高度為2,以此類推,根節(jié)點(diǎn)的高度最高 |
| 層 | 根節(jié)點(diǎn)在第一層,以此類推 |
| 父節(jié)點(diǎn) | 若一個(gè)節(jié)點(diǎn)含有子節(jié)點(diǎn),則這個(gè)節(jié)點(diǎn)稱之為其子節(jié)點(diǎn)的父節(jié)點(diǎn) |
| 子節(jié)點(diǎn) | 子節(jié)點(diǎn)是父節(jié)點(diǎn)的下一層節(jié)點(diǎn) |
| 兄弟節(jié)點(diǎn) | 擁有共同父節(jié)點(diǎn)的節(jié)點(diǎn)互稱為兄弟節(jié)點(diǎn) |
二叉樹
如果樹中的每個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)的個(gè)數(shù)不超過2,那么該樹就是一個(gè)二叉樹。
二叉查找樹/二叉排序樹
二叉查找樹的特點(diǎn):
案例演示(20,18,23,22,17,24,19)數(shù)據(jù)的存儲(chǔ)過程;
增刪改查的性能都很高!!!
遍歷獲取元素的時(shí)候可以按照"左中右"的順序進(jìn)行遍歷;
注意:二叉查找樹存在的問題:會(huì)出現(xiàn)"瘸子"的現(xiàn)象,影響查詢效率。
平衡二叉樹
(基于查找二叉樹,但是讓樹不要太高,盡量讓樹的元素均衡分布。這樣綜合性能就高了)
概述
為了避免出現(xiàn)"瘸子"的現(xiàn)象,減少樹的高度,提高我們的搜素效率,又存在一種樹的結(jié)構(gòu):“平衡二叉樹”
規(guī)則:它的左右兩個(gè)子樹的高度差的絕對值不超過1,并且左右兩個(gè)子樹都是一棵平衡二叉樹
如下圖所示:
如下圖所示,左圖是一棵平衡二叉樹,根節(jié)點(diǎn)10,左右兩子樹的高度差是1,而右圖,雖然根節(jié)點(diǎn)左右兩子樹高度差是0,但是右子樹15的左右子樹高度差為2,不符合定義,
所以右圖不是一棵平衡二叉樹。
旋轉(zhuǎn)
在構(gòu)建一棵平衡二叉樹的過程中,當(dāng)有新的節(jié)點(diǎn)要插入時(shí),檢查是否因插入后而破壞了樹的平衡,如果是,則需要做旋轉(zhuǎn)去改變樹的結(jié)構(gòu)。
左旋:
左旋就是將節(jié)點(diǎn)的右支往左拉,右子節(jié)點(diǎn)變成父節(jié)點(diǎn),并把晉升之后多余的左子節(jié)點(diǎn)出讓給降級節(jié)點(diǎn)的右子節(jié)點(diǎn);
右旋:
將節(jié)點(diǎn)的左支往右拉,左子節(jié)點(diǎn)變成了父節(jié)點(diǎn),并把晉升之后多余的右子節(jié)點(diǎn)出讓給降級節(jié)點(diǎn)的左子節(jié)點(diǎn)
舉個(gè)例子,像上圖是否平衡二叉樹的圖里面,左圖在沒插入前"19"節(jié)點(diǎn)前,該樹還是平衡二叉樹,但是在插入"19"后,導(dǎo)致了"15"的左右子樹失去了"平衡",
所以此時(shí)可以將"15"節(jié)點(diǎn)進(jìn)行左旋,讓"15"自身把節(jié)點(diǎn)出讓給"17"作為"17"的左樹,使得"17"節(jié)點(diǎn)左右子樹平衡,而"15"節(jié)點(diǎn)沒有子樹,左右也平衡了。如下圖,
由于在構(gòu)建平衡二叉樹的時(shí)候,當(dāng)有新節(jié)點(diǎn)插入時(shí),都會(huì)判斷插入后時(shí)候平衡,這說明了插入新節(jié)點(diǎn)前,都是平衡的,也即高度差絕對值不會(huì)超過1。當(dāng)新節(jié)點(diǎn)插入后,
有可能會(huì)有導(dǎo)致樹不平衡,這時(shí)候就需要進(jìn)行調(diào)整,而可能出現(xiàn)的情況就有4種,分別稱作左左,左右,右左,右右。
左左
左左即為在原來平衡的二叉樹上,在節(jié)點(diǎn)的左子樹的左子樹下,有新節(jié)點(diǎn)插入,導(dǎo)致節(jié)點(diǎn)的左右子樹的高度差為2,如下即為"10"節(jié)點(diǎn)的左子樹"7",的左子樹"4",插入了節(jié)點(diǎn)"5"或"3"導(dǎo)致失衡。
左左調(diào)整其實(shí)比較簡單,只需要對節(jié)點(diǎn)進(jìn)行右旋即可,如下圖,對節(jié)點(diǎn)"10"進(jìn)行右旋,
左右
左右即為在原來平衡的二叉樹上,在節(jié)點(diǎn)的左子樹的右子樹下,有新節(jié)點(diǎn)插入,導(dǎo)致節(jié)點(diǎn)的左右子樹的高度差為2,如上即為"11"節(jié)點(diǎn)的左子樹"7",的右子樹"9",
插入了節(jié)點(diǎn)"10"或"8"導(dǎo)致失衡。
左右的調(diào)整就不能像左左一樣,進(jìn)行一次旋轉(zhuǎn)就完成調(diào)整。我們不妨先試著讓左右像左左一樣對"11"節(jié)點(diǎn)進(jìn)行右旋,結(jié)果圖如下,右圖的二叉樹依然不平衡,而右圖就是接下來要
講的右左,即左右跟右左互為鏡像,左左跟右右也互為鏡像。
左右這種情況,進(jìn)行一次旋轉(zhuǎn)是不能滿足我們的條件的,正確的調(diào)整方式是,將左右進(jìn)行第一次旋轉(zhuǎn),將左右先調(diào)整成左左,然后再對左左進(jìn)行調(diào)整,從而使得二叉樹平衡。
即先對上圖的節(jié)點(diǎn)"7"進(jìn)行左旋,使得二叉樹變成了左左,之后再對"11"節(jié)點(diǎn)進(jìn)行右旋,此時(shí)二叉樹就調(diào)整完成,如下圖:
右左
右左即為在原來平衡的二叉樹上,在節(jié)點(diǎn)的右子樹的左子樹下,有新節(jié)點(diǎn)插入,導(dǎo)致節(jié)點(diǎn)的左右子樹的高度差為2,如上即為"11"節(jié)點(diǎn)的右子樹"15",的左子樹"13",
插入了節(jié)點(diǎn)"12"或"14"導(dǎo)致失衡。
前面也說了,右左跟左右其實(shí)互為鏡像,所以調(diào)整過程就反過來,先對節(jié)點(diǎn)"15"進(jìn)行右旋,使得二叉樹變成右右,之后再對"11"節(jié)點(diǎn)進(jìn)行左旋,此時(shí)二叉樹就調(diào)整完成,如下圖:
右右
右右即為在原來平衡的二叉樹上,在節(jié)點(diǎn)的右子樹的右子樹下,有新節(jié)點(diǎn)插入,導(dǎo)致節(jié)點(diǎn)的左右子樹的高度差為2,如下即為"11"節(jié)點(diǎn)的右子樹"13",的左子樹"15",插入了節(jié)點(diǎn)
"14"或"19"導(dǎo)致失衡。
右右只需對節(jié)點(diǎn)進(jìn)行一次左旋即可調(diào)整平衡,如下圖,對"11"節(jié)點(diǎn)進(jìn)行左旋。
紅黑樹
就是平衡的二叉查找樹!!
概述
紅黑樹是一種自平衡的二叉查找樹,是計(jì)算機(jī)科學(xué)中用到的一種數(shù)據(jù)結(jié)構(gòu),它是在1972年由Rudolf Bayer發(fā)明的,當(dāng)時(shí)被稱之為平衡二叉B樹,后來,在1978年被
Leoj.Guibas和Robert Sedgewick修改為如今的"紅黑樹"。它是一種特殊的二叉查找樹,紅黑樹的每一個(gè)節(jié)點(diǎn)上都有存儲(chǔ)位表示節(jié)點(diǎn)的顏色,可以是紅或者黑;
紅黑樹不是高度平衡的,它的平衡是通過"紅黑樹的特性"進(jìn)行實(shí)現(xiàn)的;
紅黑樹的特性:
如下圖所示就是一個(gè)
在進(jìn)行元素插入的時(shí)候,和之前一樣; 每一次插入完畢以后,使用黑色規(guī)則進(jìn)行校驗(yàn),如果不滿足紅黑規(guī)則,就需要通過變色,左旋和右旋來調(diào)整樹,使其滿足紅黑規(guī)則;
第三章 List接口
我們掌握了Collection接口的使用后,再來看看Collection接口中的子類,他們都具備那些特性呢?
接下來,我們一起學(xué)習(xí)Collection中的常用幾個(gè)子類(java.util.List集合、java.util.Set集合)。
3.1 List接口介紹
java.util.List接口繼承自Collection接口,是單列集合的一個(gè)重要分支,習(xí)慣性地會(huì)將實(shí)現(xiàn)了List接口的對象稱為List集合。在List集合中允許出現(xiàn)重復(fù)的元素,所有的元素是以一種線性方式進(jìn)行存儲(chǔ)的,在程序中可以通過索引來訪問集合中的指定元素。另外,List集合還有一個(gè)特點(diǎn)就是元素有序,即元素的存入順序和取出順序一致。
看完API,我們總結(jié)一下:
List接口特點(diǎn):
tips:我們在基礎(chǔ)班的時(shí)候已經(jīng)學(xué)習(xí)過List接口的子類java.util.ArrayList類,該類中的方法都是來自List中定義。
3.2 List接口中常用方法
List作為Collection集合的子接口,不但繼承了Collection接口中的全部方法,而且還增加了一些根據(jù)元素索引來操作集合的特有方法,如下:
- public void add(int index, E element): 將指定的元素,添加到該集合中的指定位置上。
- public E get(int index):返回集合中指定位置的元素。
- public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
- public E set(int index, E element):用指定元素替換集合中指定位置的元素,返回值的更新前的元素。
List集合特有的方法都是跟索引相關(guān),我們在基礎(chǔ)班都學(xué)習(xí)過。
tips:我們之前學(xué)習(xí)Colletion體系的時(shí)候,發(fā)現(xiàn)List集合下有很多集合,它們的存儲(chǔ)結(jié)構(gòu)不同,這樣就導(dǎo)致了這些集合它們有各自的特點(diǎn),供我們在不同的環(huán)境下使用,那么常見的數(shù)據(jù)結(jié)構(gòu)有哪些呢?在下一章我們來介紹:
3.3 ArrayList集合
java.util.ArrayList集合數(shù)據(jù)存儲(chǔ)的結(jié)構(gòu)是數(shù)組結(jié)構(gòu)。元素增刪慢,查找快,由于日常開發(fā)中使用最多的功能為查詢數(shù)據(jù)、遍歷數(shù)據(jù),所以ArrayList是最常用的集合。
許多程序員開發(fā)時(shí)非常隨意地使用ArrayList完成任何需求,并不嚴(yán)謹(jǐn),這種用法是不提倡的。
3.4 LinkedList集合
java.util.LinkedList集合數(shù)據(jù)存儲(chǔ)的結(jié)構(gòu)是鏈表結(jié)構(gòu)。方便元素添加、刪除的集合。
LinkedList是一個(gè)雙向鏈表,那么雙向鏈表是什么樣子的呢,我們用個(gè)圖了解下
實(shí)際開發(fā)中對一個(gè)集合元素的添加與刪除經(jīng)常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。這些方法我們作為了解即可:
- public void addFirst(E e):將指定元素插入此列表的開頭。
- public void addLast(E e):將指定元素添加到此列表的結(jié)尾。
- public E getFirst():返回此列表的第一個(gè)元素。
- public E getLast():返回此列表的最后一個(gè)元素。
- public E removeFirst():移除并返回此列表的第一個(gè)元素。
- public E removeLast():移除并返回此列表的最后一個(gè)元素。
- public E pop():從此列表所表示的堆棧處彈出一個(gè)元素。
- public void push(E e):將元素推入此列表所表示的堆棧。
- public boolean isEmpty():如果列表不包含元素,則返回true。
LinkedList是List的子類,List中的方法LinkedList都是可以使用,這里就不做詳細(xì)介紹,我們只需要了解LinkedList的特有方法即可。在開發(fā)時(shí),LinkedList集合也可以作為堆棧,隊(duì)列的結(jié)構(gòu)使用。
public class Demo04LinkedList {public static void main(String[] args) {method4();}/** void push(E e): 壓入。把元素添加到集合的第一個(gè)位置。* E pop(): 彈出。把第一個(gè)元素刪除,然后返回這個(gè)元素。*/public static void method4() {//創(chuàng)建LinkedList對象LinkedList<String> list = new LinkedList<>();//添加元素list.add("達(dá)爾文");list.add("達(dá)芬奇");list.add("達(dá)爾優(yōu)");System.out.println("list:" + list);//調(diào)用push在集合的第一個(gè)位置添加元素//list.push("愛迪生");//System.out.println("list:" + list);//[愛迪生, 達(dá)爾文, 達(dá)芬奇, 達(dá)爾優(yōu)]//E pop(): 彈出。把第一個(gè)元素刪除,然后返回這個(gè)元素。String value = list.pop();System.out.println("value:" + value);//達(dá)爾文System.out.println("list:" + list);//[達(dá)芬奇,達(dá)爾優(yōu)]}/** E removeFirst():刪除第一個(gè)元素* E removeLast():刪除最后一個(gè)元素。*/public static void method3() {//創(chuàng)建LinkedList對象LinkedList<String> list = new LinkedList<>();//添加元素list.add("達(dá)爾文");list.add("達(dá)芬奇");list.add("達(dá)爾優(yōu)");//刪除集合的第一個(gè)元素 // String value = list.removeFirst(); // System.out.println("value:" + value);//達(dá)爾文 // System.out.println("list:" + list);//[達(dá)芬奇,達(dá)爾優(yōu)]//刪除最后一個(gè)元素String value = list.removeLast();System.out.println("value:" + value);//達(dá)爾優(yōu)System.out.println("list:" + list);//[達(dá)爾文, 達(dá)芬奇]}/** E getFirst(): 獲取集合中的第一個(gè)元素* E getLast(): 獲取集合中的最后一個(gè)元素*/public static void method2() {//創(chuàng)建LinkedList對象LinkedList<String> list = new LinkedList<>();//添加元素list.add("達(dá)爾文");list.add("達(dá)芬奇");list.add("達(dá)爾優(yōu)");System.out.println("list:" + list);//獲取集合中的第一個(gè)元素System.out.println("第一個(gè)元素是:" + list.getFirst());//獲取集合中的最后一個(gè)元素怒System.out.println("最后一個(gè)元素是:" + list.getLast());} /** void addFirst(E e): 在集合的開頭位置添加元素。* void addLast(E e): 在集合的尾部添加元素。*/public static void method1() {//創(chuàng)建LinkedList對象LinkedList<String> list = new LinkedList<>();//添加元素list.add("達(dá)爾文");list.add("達(dá)芬奇");list.add("達(dá)爾優(yōu)");//打印這個(gè)集合System.out.println("list:" + list);//[達(dá)爾文, 達(dá)芬奇, 達(dá)爾優(yōu)]//調(diào)用addFirst添加元素list.addFirst("曹操");System.out.println("list:" + list);//[曹操, 達(dá)爾文, 達(dá)芬奇, 達(dá)爾優(yōu)]//調(diào)用addLast方法添加元素list.addLast("大喬");System.out.println("list:" + list);//[曹操, 達(dá)爾文, 達(dá)芬奇, 達(dá)爾優(yōu), 大喬]} }第四章 Set接口
java.util.Set接口和java.util.List接口一樣,同樣繼承自Collection接口,它與Collection接口中的方法基本一致,并沒有對Collection接口進(jìn)行功能上的擴(kuò)充,只是比Collection接口更加嚴(yán)格了。與List接口不同的是,Set接口都會(huì)以某種規(guī)則保證存入的元素不出現(xiàn)重復(fù)。
Set集合有多個(gè)子類,這里我們介紹其中的java.util.HashSet、java.util.LinkedHashSet、java.util.TreeSet這兩個(gè)集合。
tips:Set集合取出元素的方式可以采用:迭代器、增強(qiáng)for。
4.1 HashSet集合介紹
java.util.HashSet是Set接口的一個(gè)實(shí)現(xiàn)類,它所存儲(chǔ)的元素是不可重復(fù)的,并且元素都是無序的(即存取順序不能保證不一致)。java.util.HashSet底層的實(shí)現(xiàn)其實(shí)是一個(gè)java.util.HashMap支持,由于我們暫時(shí)還未學(xué)習(xí),先做了解。
HashSet是根據(jù)對象的哈希值來確定元素在集合中的存儲(chǔ)位置,因此具有良好的存儲(chǔ)和查找性能。保證元素唯一性的方式依賴于:hashCode與equals方法。
我們先來使用一下Set集合存儲(chǔ),看下現(xiàn)象,再進(jìn)行原理的講解:
public class HashSetDemo {public static void main(String[] args) {//創(chuàng)建 Set集合HashSet<String> set = new HashSet<String>();//添加元素set.add(new String("cba"));set.add("abc");set.add("bac"); set.add("cba"); //遍歷for (String name : set) {System.out.println(name);}} }輸出結(jié)果如下,說明集合中不能存儲(chǔ)重復(fù)元素:
cba abc bactips:根據(jù)結(jié)果我們發(fā)現(xiàn)字符串"cba"只存儲(chǔ)了一個(gè),也就是說重復(fù)的元素set集合不存儲(chǔ)。
4.2 HashSet集合存儲(chǔ)數(shù)據(jù)的結(jié)構(gòu)(哈希表)
什么是哈希表呢?
在JDK1.8之前,哈希表底層采用數(shù)組+鏈表實(shí)現(xiàn),即使用數(shù)組處理沖突,同一hash值的鏈表都存儲(chǔ)在一個(gè)數(shù)組里。但是當(dāng)位于一個(gè)桶中的元素較多,即hash值相等的元素較多時(shí),通過key值依次查找的效率較低。而JDK1.8中,哈希表存儲(chǔ)采用數(shù)組+鏈表+紅黑樹實(shí)現(xiàn),當(dāng)鏈表長度超過閾值(8)時(shí),將鏈表轉(zhuǎn)換為紅黑樹,這樣大大減少了查找時(shí)間。
簡單的來說,哈希表是由數(shù)組+鏈表+紅黑樹(JDK1.8增加了紅黑樹部分)實(shí)現(xiàn)的,如下圖所示。
看到這張圖就有人要問了,這個(gè)是怎么存儲(chǔ)的呢?
為了方便大家的理解我們結(jié)合一個(gè)存儲(chǔ)流程圖來說明一下:
總而言之,JDK1.8引入紅黑樹大程度優(yōu)化了HashMap的性能,那么對于我們來講保證HashSet集合元素的唯一,其實(shí)就是根據(jù)對象的hashCode和equals方法來決定的。如果我們往集合中存放自定義的對象,那么保證其唯一,就必須復(fù)寫hashCode和equals方法建立屬于當(dāng)前對象的比較方式。
4.3 HashSet存儲(chǔ)自定義類型元素
給HashSet中存放自定義類型元素時(shí),需要重寫對象中的hashCode和equals方法,建立自己的比較方式,才能保證HashSet集合中的對象唯一.
創(chuàng)建自定義Student類:
public class Student {private String name;private int age;//get/set@Overridepublic boolean equals(Object o) {if (this == o)return true;if (o == null || getClass() != o.getClass())return false;Student student = (Student) o;return age == student.age &&Objects.equals(name, student.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);} }創(chuàng)建測試類:
public class HashSetDemo2 {public static void main(String[] args) {//創(chuàng)建集合對象 該集合中存儲(chǔ) Student類型對象HashSet<Student> stuSet = new HashSet<Student>();//存儲(chǔ) Student stu = new Student("于謙", 43);stuSet.add(stu);stuSet.add(new Student("郭德綱", 44));stuSet.add(new Student("于謙", 43));stuSet.add(new Student("郭麒麟", 23));stuSet.add(stu);for (Student stu2 : stuSet) {System.out.println(stu2);}} } 執(zhí)行結(jié)果: Student [name=郭德綱, age=44] Student [name=于謙, age=43] Student [name=郭麒麟, age=23]4.4 LinkedHashSet
我們知道HashSet保證元素唯一,可是元素存放進(jìn)去是沒有順序的,那么我們要保證有序,怎么辦呢?
在HashSet下面有一個(gè)子類java.util.LinkedHashSet,它是鏈表和哈希表組合的一個(gè)數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)。
演示代碼如下:
public class LinkedHashSetDemo {public static void main(String[] args) {Set<String> set = new LinkedHashSet<String>();set.add("bbb");set.add("aaa");set.add("abc");set.add("bbc");Iterator<String> it = set.iterator();while (it.hasNext()) {System.out.println(it.next());}} } 結(jié)果:bbbaaaabcbbc4.5 TreeSet集合
1. 特點(diǎn)
TreeSet集合是Set接口的一個(gè)實(shí)現(xiàn)類,底層依賴于TreeMap,是一種基于紅黑樹的實(shí)現(xiàn),其特點(diǎn)為:
進(jìn)行排序,具體取決于使用的構(gòu)造方法:
2. 演示
案例演示自然排序(20,18,23,22,17,24,19):
public static void main(String[] args) {//無參構(gòu)造,默認(rèn)使用元素的自然順序進(jìn)行排序TreeSet<Integer> set = new TreeSet<Integer>();set.add(20);set.add(18);set.add(23);set.add(22);set.add(17);set.add(24);set.add(19);System.out.println(set); }控制臺(tái)的輸出結(jié)果為: [17, 18, 19, 20, 22, 23, 24]案例演示比較器排序(20,18,23,22,17,24,19):
public static void main(String[] args) {//有參構(gòu)造,傳入比較器,使用比較器對元素進(jìn)行排序TreeSet<Integer> set = new TreeSet<Integer>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {//元素前 - 元素后 : 升序//元素后 - 元素前 : 降序return o2 - o1;}});set.add(20);set.add(18);set.add(23);set.add(22);set.add(17);set.add(24);set.add(19);System.out.println(set); }控制臺(tái)的輸出結(jié)果為: [24, 23, 22, 20, 19, 18, 17]第五章 Collections類
5.1 Collections常用功能
-
java.utils.Collections是集合工具類,用來對集合進(jìn)行操作。
常用方法如下:
-
public static void shuffle(List<?> list):打亂集合順序。
-
public static <T> void sort(List<T> list):將集合中元素按照默認(rèn)規(guī)則排序。
-
public static <T> void sort(List<T> list,Comparator<? super T> ):將集合中元素按照指定規(guī)則排序。
代碼演示:
public class CollectionsDemo {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<Integer>();list.add(100);list.add(300);list.add(200);list.add(50);//排序方法 Collections.sort(list);System.out.println(list);} } 結(jié)果: [50,100, 200, 300]我們的集合按照默認(rèn)的自然順序進(jìn)行了排列,如果想要指定順序那該怎么辦呢?
5.2 Comparator比較器
創(chuàng)建一個(gè)學(xué)生類,存儲(chǔ)到ArrayList集合中完成指定排序操作。
Student 類
public class Student{private String name;private int age;//構(gòu)造方法//get/set//toString }測試類:
public class Demo {public static void main(String[] args) {// 創(chuàng)建四個(gè)學(xué)生對象 存儲(chǔ)到集合中ArrayList<Student> list = new ArrayList<Student>();list.add(new Student("rose",18));list.add(new Student("jack",16));list.add(new Student("abc",20));Collections.sort(list, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {return o1.getAge()-o2.getAge();//以學(xué)生的年齡升序}});for (Student student : list) {System.out.println(student);}} } Student{name='jack', age=16} Student{name='rose', age=18} Student{name='abc', age=20}5.3 可變參數(shù)
在JDK1.5之后,如果我們定義一個(gè)方法需要接受多個(gè)參數(shù),并且多個(gè)參數(shù)類型一致,我們可以對其簡化.
格式:
修飾符 返回值類型 方法名(參數(shù)類型... 形參名){ }代碼演示:
public class ChangeArgs {public static void main(String[] args) {int sum = getSum(6, 7, 2, 12, 2121);System.out.println(sum);}public static int getSum(int... arr) {int sum = 0;for (int a : arr) {sum += a;}return sum;} }注意:
? 1.一個(gè)方法只能有一個(gè)可變參數(shù)
? 2.如果方法中有多個(gè)參數(shù),可變參數(shù)要放到最后。
應(yīng)用場景: Collections
? 在Collections中也提供了添加一些元素方法:
? public static <T> boolean addAll(Collection<T> c, T... elements):往集合中添加一些元素。
代碼演示:
public class CollectionsDemo {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<Integer>();//原來寫法//list.add(12);//list.add(14);//list.add(15);//list.add(1000);//采用工具類 完成 往集合中添加元素 Collections.addAll(list, 5, 222, 1,2);System.out.println(list); }第六章 集合綜合案例
6.1 案例介紹
按照斗地主的規(guī)則,完成洗牌發(fā)牌的動(dòng)作。
具體規(guī)則:
使用54張牌打亂順序,三個(gè)玩家參與游戲,三人交替摸牌,每人17張牌,最后三張留作底牌。
6.2 案例分析
-
準(zhǔn)備牌:
牌可以設(shè)計(jì)為一個(gè)ArrayList,每個(gè)字符串為一張牌。
每張牌由花色數(shù)字兩部分組成,我們可以使用花色集合與數(shù)字集合嵌套迭代完成每張牌的組裝。
牌由Collections類的shuffle方法進(jìn)行隨機(jī)排序。 -
發(fā)牌
將每個(gè)人以及底牌設(shè)計(jì)為ArrayList,將最后3張牌直接存放于底牌,剩余牌通過對3取模依次發(fā)牌。
-
看牌
直接打印每個(gè)集合。
6.3 代碼實(shí)現(xiàn)
2 測試類
public class Demo12 {public static void main(String[] args) {// 創(chuàng)建一個(gè)ArrayList用于存放一副牌ArrayList<Poker> pokers = new ArrayList<>();pokers.add(new Poker("大王", ""));pokers.add(new Poker("小王", ""));String[] colors = new String[] {"?", "?", "?", "?"};String[] numbers = new String[] {"2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"};// 組合牌, 嵌套循環(huán)的流程:外循環(huán)一次,內(nèi)循環(huán)所有次// 2.使用嵌套循環(huán)生成一副牌for (String n : numbers) {// "2", "A"for (String c : colors) {// "?", "?", "?", "?"Poker p = new Poker(c, n);// 3.將54張牌放到集合pokers.add(p);}}// 打印 // System.out.println(pokers);// 洗牌: Collections,集合工具類// static void shuffle?(List<?> list) 將集合中元素的順序打亂Collections.shuffle(pokers);System.out.println("洗牌后:" + pokers);// 發(fā)牌// 1.創(chuàng)建3個(gè)玩家集合,創(chuàng)建底牌集合ArrayList<Poker> player01 = new ArrayList<>();ArrayList<Poker> player02 = new ArrayList<>();ArrayList<Poker> player03 = new ArrayList<>();ArrayList<Poker> diPai = new ArrayList<>();// 2.遍歷牌的集合// 0 1 2 3 4 5 6 7 8 9 10 ...51 52 53// pokers = [?5], [?4], [?8], [?A], [?7], [?2], [?6], [?J], [?A], [?7], [?6], [?5], [?7], [?10]// 玩家1: 索引0,3,6 索引 % 3 == 0// 玩家2: 索引1,4,7 索引 % 3 == 1// 玩家3: 索引2,5,8 索引 % 3 == 2// 3.根據(jù)索引將牌發(fā)給不同的玩家for (int i = 0; i < pokers.size(); i++) {// i表示索引,poker就是i索引對應(yīng)的pokerPoker poker = pokers.get(i);if (i >= 51) { // 最后3張給底牌diPai.add(poker);} else if (i % 3 == 0) { // 玩家1player01.add(poker);} else if (i % 3 == 1) { // 玩家2player02.add(poker);} else if (i % 3 == 2) { // 玩家3player03.add(poker);}}// 看牌System.out.println("玩家1: " + player01);System.out.println("玩家2: " + player02);System.out.println("玩家3: " + player03);System.out.println("底牌: " + diPai);// 還要?jiǎng)?chuàng)建一副牌// 創(chuàng)建一個(gè)ArrayList用于存放一副牌} }總結(jié)
以上是生活随笔為你收集整理的Java基础学习系列--(五)【迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 命令卸载ie11
- 下一篇: Java基础书籍推荐