Java基礎(chǔ)進(jìn)階 集合框架詳解

今日任務(wù)

1、List接口介紹(掌握常用List特有方法)
2佣耐、練習(xí)
3政勃、ArrayList介紹(必須清楚集合的特征、掌握集合中的方法)
4兼砖、LinkedList介紹(必須清楚集合的特征奸远、掌握集合中的方法)
5、Vector 類(lèi)介紹(了解)
6讽挟、List下的子類(lèi)總結(jié)(掌握)
7懒叛、Set 接口介紹(掌握Set集合的特性)
8、HashSet 集合(掌握HashSet集合的應(yīng)用)

1耽梅、List接口介紹

在學(xué)習(xí)Collection接口的時(shí)候薛窥,api中告訴我們,在Collection接口的下面有2個(gè)直接的子接口眼姐,分別是Set和List诅迷。我們這里先學(xué)習(xí)List接口。

1.png

List接口:
1)是Collection接口的子接口妥凳,繼承了Collection接口中的所有方法竟贯;
2)List接口定義的所有集合中的元素都可以重復(fù),并且還可以保證存取的順序一致(存儲(chǔ)和取出的順序一致的)逝钥;
3)List接口下的所有集合全部擁有下標(biāo)屑那,List接口更像數(shù)組;
4)由于List接口規(guī)定自己的真實(shí)實(shí)現(xiàn)類(lèi)(集合)都擁有下標(biāo)艘款,因此我們?cè)诓僮鱈ist接口下的所有集合容器的時(shí)候持际,都可以通過(guò)下標(biāo)操作;
5)因此在List接口中它不僅僅繼承到Collection接口中的所有函數(shù)哗咆,同時(shí)java 還根據(jù)List的下標(biāo)的特性蜘欲,定義了適合List接口的特有函數(shù);

1.1晌柬、List的特有方法

List集合和Collection集合不同之處姥份,就是List接口中的特有方法都是圍繞集合的下標(biāo)而設(shè)計(jì)的郭脂。
List集合中的特有方法:

1)添加方法:
void add(int index,Object element ):根據(jù)指定的角標(biāo)位置,向集合中添加指定的元素對(duì)象澈歉;
index >= 0 && index <= size()展鸡;

2.png

boolean addAll(int index, Collection coll):根據(jù)指定的角標(biāo)位置,向集合中添加指定集合中所有的元素埃难;
index >= 0 && index <= size()莹弊;


3.png

分析和步驟:
1)定義一個(gè)ListDemo類(lèi),在這個(gè)類(lèi)中定義一個(gè)method_1()函數(shù)涡尘;
2)在method_1()函數(shù)中使用new關(guān)鍵字創(chuàng)建ArrayList類(lèi)的對(duì)象list忍弛,并賦值給接口List類(lèi)型;
3)使用集合對(duì)象list調(diào)用屬于Collection接口中的add(E e)函數(shù)向集合List中添加字符串”aaaa”考抄;
4)使用集合對(duì)象list調(diào)用屬于接口List中特有的函數(shù)add(int index,Object element )根據(jù)指定的下標(biāo)向集合中添加數(shù)據(jù)”abc”;

5)使用輸出語(yǔ)句輸出list對(duì)象中的值细疚;

public static void method_1() {
        // 創(chuàng)建集合對(duì)象
        List list = new ArrayList();
        List list1 = new ArrayList();
        // 向集合中添加數(shù)據(jù)
        list.add("aaaa");
        list.add("bbbb");
        list.add("cccc");
        list1.add("hhhh");
        list1.add("哈哈");
        // 使用List接口中特有的函數(shù)向接口中添加數(shù)據(jù)
        // list.add(1, "dddd");//1表示要添加數(shù)據(jù)的下標(biāo)位置
        // list.add(4,"xyz");//指定的下標(biāo)前面一定要有元素
        // 向集合list的下標(biāo)為2的位置添加集合list1中所有的數(shù)據(jù)
        list.addAll(2, list1);
        // 輸出數(shù)據(jù)
        System.out.println(list);
    }

注意:
1)指定的下標(biāo)前面一定要有數(shù)據(jù);
2)添加不是覆蓋座泳,指定的位置添加了元素后惠昔,之前存在元素就會(huì)向后移動(dòng);

2)獲取方法:
List subList(int startIndex, int endIndex): 返回集合中從指定角標(biāo)開(kāi)始(包含頭角標(biāo))到指定角標(biāo)結(jié)束(不包含尾角標(biāo))的所有元素挑势,以集合方式返回

4.png

int indexOf(Object obj)返回指定元素在集合中第一次出現(xiàn)的角標(biāo)镇防。沒(méi)有匹配元素則返回-1


5.png

分析和步驟:
1)在上述ListDemo類(lèi)中定義一個(gè)method_2()函數(shù);
2)在method_2()函數(shù)中使用new關(guān)鍵字創(chuàng)建ArrayList類(lèi)的對(duì)象list潮饱,并賦值給接口List類(lèi)型来氧;
3)使用集合對(duì)象list調(diào)用屬于Collection接口中的add(E e)函數(shù)向集合List中添加幾個(gè)字符串?dāng)?shù)據(jù);
4) 使用集合對(duì)象list調(diào)用屬于接口List中特有的函數(shù)get(int index )根據(jù)指定的下標(biāo)獲取下標(biāo)對(duì)應(yīng)的數(shù)據(jù)香拉;
5)使用輸出語(yǔ)句輸出獲得的數(shù)據(jù)obj啦扬;

public static void method_2() {
        // 創(chuàng)建集合對(duì)象
        List list = new ArrayList();
        // 向集合中添加數(shù)據(jù)
        list.add("nba");
        list.add("nba");
        list.add("cba");
        list.add("wbna");
        list.add("wcba");
        // 根據(jù)下標(biāo)獲得對(duì)應(yīng)的元素
        // Object obj = list.get(2);
        // Object obj = list.get(7);//下標(biāo)不能是空白區(qū)域
        // System.out.println(obj);
        // List subList = list.subList(1,0);//結(jié)束角標(biāo)要大于等于起始角標(biāo)
        // System.out.println(subList);
        // 返回指定元素第一次出現(xiàn)的角標(biāo)
        int index = list.indexOf("nbaa");// 沒(méi)有返回-1
        System.out.println(index);
    }

注意: 下標(biāo)的范圍:index >= 0 && index <= size()-1;

1.2凫碌、List的特有迭代器

List接口繼承了Collection接口扑毡,Collection接口中的獲取某個(gè)集合對(duì)應(yīng)的迭代器的函數(shù),List一定也繼承到了盛险。但是由于List集合有下標(biāo)瞄摊,因此Java中針對(duì)List這類(lèi)集合同時(shí)也提供了自己特有的迭代器。

ListIterator接口:


6.png

ListIterator接口:它是專(zhuān)門(mén)針對(duì)List接口而設(shè)計(jì)的苦掘,這個(gè)迭代器在遍歷List集合的時(shí)候换帜,可以對(duì)這個(gè)集合中的元素進(jìn)行增 刪 改 查操作。并且它還可以逆向遍歷鹤啡。
注意:ListIterator迭代器只能被List集合使用惯驼,其它集合都不能使用。
使用ListIterator逆向迭代的時(shí)候一定要使用帶有參數(shù)的listIterator(index)函數(shù),然后集合的長(zhǎng)度作為參數(shù)下標(biāo)祟牲,否則該迭代光標(biāo)就會(huì)從前往后迭代隙畜;

7.png

這里的index>=0&&index<=list.size();
使用list.size()作為參數(shù)。

分析和步驟:
1)定義一個(gè)ListIteratorDemo類(lèi)说贝;
2)在這個(gè)類(lèi)中使用new關(guān)鍵字創(chuàng)建ArrayList類(lèi)的對(duì)象list禾蚕,并賦值給接口List類(lèi)型;
3)使用集合對(duì)象list調(diào)用屬于Collection接口中的add(E e)函數(shù)向集合List中添加幾個(gè)字符串?dāng)?shù)據(jù)狂丝;
4)使用集合對(duì)象list調(diào)用屬于接口Collection中的iterator()函數(shù)獲得迭代器對(duì)象it,并在for循環(huán)中使用迭代器對(duì)象it調(diào)用hasNext()函數(shù)判斷是否有元素,使用迭代器對(duì)象it調(diào)用next()函數(shù)獲取list集合中的數(shù)據(jù)哗总,并打蛹秆铡;
5)使用集合對(duì)象list調(diào)用listIterator()函數(shù)生成ListIterator 類(lèi)型讯屈,并使用for循環(huán)對(duì)其迭代遍歷蛋哭;
6)同樣使用ListIterator 類(lèi)型的對(duì)象調(diào)用listIterator(list.size())函數(shù),這里需要注意涮母,由于是從集合中的最后元素開(kāi)始向前遍歷谆趾,所以這里我們對(duì)于listIterator()函數(shù)應(yīng)該給一個(gè)參數(shù),否則不會(huì)出現(xiàn)結(jié)果叛本;
7)lit.hasPrevious()判斷是否有元素沪蓬,如果有前一個(gè)元素可以迭代,則返回 true 来候,lit.previous()返回列表中的前一個(gè)元素跷叉;

package cn.xuexi.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/*
 * List接口的特有的迭代器
 */
public class ListIteratorDemo {
    public static void main(String[] args) {
        //創(chuàng)建集合對(duì)象
        List list=new ArrayList();
        //向集合中添加數(shù)據(jù)
        list.add("nba");
        list.add("aaa");
        list.add("cba");
        list.add("wnba");
        list.add("wcba");
        //使用Collection接口中的Iterator進(jìn)行迭代
        System.out.println("使用Iterator進(jìn)行迭代");
        for (Iterator it = list.iterator(); it.hasNext();) {
            //輸出數(shù)據(jù)
            System.out.println(it.next());
        }
        //使用List接口中的特有迭代器ListIterator進(jìn)行迭代
        System.out.println("使用ListIterator進(jìn)行正向迭代");
        for (ListIterator it = list.listIterator();it.hasNext();) {
            System.out.println(it.next());
        }
        System.out.println("使用ListIterator進(jìn)行逆向迭代");
        //it.hasPrevious()如果為true表示集合中從后往前迭代開(kāi)始有元素
        for (ListIterator it = list.listIterator(list.size()); it.hasPrevious();) {
            //it.previous()表示返回集合中的上一個(gè)元素
            System.out.println(it.previous());
        }
    }
}

注意:
1)使用ListIterator逆向迭代的時(shí)候一定要使用帶有參數(shù)的listIterator(index)函數(shù),然后集合的長(zhǎng)度作為參數(shù)下標(biāo)营搅,否則該迭代光標(biāo)就會(huì)從前往后迭代云挟;


8.png

這里的index>=0&&index<=list.size();

2)這里為何使用list.size()作為參數(shù)而不是list.size()-1,因?yàn)槲覀円獜募现凶詈笠粋€(gè)元素后面開(kāi)始迭代转质,如果是list.size()-1园欣,迭代光標(biāo)會(huì)從最后一個(gè)元素前面開(kāi)始,這樣會(huì)導(dǎo)致忽略了集合中的最后一個(gè)元素休蟹;

擴(kuò)展:演示在使用ListIterator迭代器遍歷List集合的時(shí)候沸枯,可以對(duì)這個(gè)集合中的元素進(jìn)行修改操作,代碼如下:

案例:List集合的特有迭代器對(duì)象鸡挠。
在使用ListIterator迭代器遍歷集合的時(shí)候是可以修改集合的辉饱;

需求:迭代取出集合中的元素的時(shí)候,判斷是否等于某個(gè)數(shù)據(jù)拣展,如果相等使用迭代器對(duì)象調(diào)用迭代器中的add()函數(shù)向集合中添加一個(gè)新的數(shù)據(jù)或者使用迭代器中的set函數(shù)更改數(shù)據(jù)彭沼。

分析和步驟:
1)在這個(gè)類(lèi)中使用new關(guān)鍵字創(chuàng)建ArrayList類(lèi)的對(duì)象list,并賦值給接口List類(lèi)型备埃;
2)使用集合對(duì)象list調(diào)用屬于Collection接口中的add(E e)函數(shù)向集合List中添加幾個(gè)字符串?dāng)?shù)據(jù)姓惑;
3)使用集合對(duì)象list調(diào)用listIterator()函數(shù)生成ListIterator 類(lèi)型的迭代器對(duì)象褐奴;
4)使用while循環(huán)遍歷集合,將獲得的數(shù)據(jù)強(qiáng)制轉(zhuǎn)換為String類(lèi)型于毙;
5)使用判斷結(jié)構(gòu)判斷找到的對(duì)象數(shù)據(jù)是否是集合中的一個(gè)數(shù)據(jù)敦冬,如果是,則使用迭代器對(duì)象調(diào)用add()函數(shù)向集合中添加一個(gè)新的字符串?dāng)?shù)據(jù)唯沮;
6)循環(huán)結(jié)束輸出集合脖旱;

9.png

補(bǔ)充:注意,使用Iterator迭代器遍歷集合的時(shí)候介蛉,在Iterator接口中只提供了remove()函數(shù)進(jìn)行修改集合萌庆;

總結(jié):(很重要,經(jīng)常犯錯(cuò)誤)
我們?cè)诘蠒r(shí)币旧,如果要對(duì)集合修改践险,一定要使用迭代器本身的功能!一定不能使用集合對(duì)象調(diào)用集合中的函數(shù)去修改集合吹菱。

Iterator中只提供了一個(gè):remove功能
ListIterator中提供了:add巍虫、remove、set功能

1.3鳍刷、List集合遍歷方法總結(jié)

說(shuō)明:
迭代List集合有多種寫(xiě)法占遥,有五種寫(xiě)法:
1)toArray(); Object[] arr = list.toArray();
2)使用for循環(huán)借助Iterator接口可以實(shí)現(xiàn);
3)使用for循環(huán)借助ListIterator接口可以實(shí)現(xiàn)倾剿;
4)使用普通for循環(huán)

for( int i=0;i<list.size();i++ ){
        System.out.println(list.get(i));
      }

5)使用foreach循環(huán):

for( Object obj : list )
{
     System.out.println(obj);
}
package cn.xuexi.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/*
 * 迭代集合的五種方法:
 * 1.toArray()
 * 2.使用Iterator迭代器遍歷集合
 * 3.使用ListIterator迭代器遍歷集合
 * 4.使用普通for循環(huán)遍歷集合
 * 5.使用foreach循環(huán)遍歷集合
 */
public class ListIteratorDemo2 {
    public static void main(String[] args) {
        //創(chuàng)建集合對(duì)象
        List list=new ArrayList();
        //向集合添加數(shù)據(jù)
        list.add("及時(shí)雨");
        list.add("白麒麟");
        list.add("黑旋風(fēng)");
        list.add("母老虎");
        //使用toArray()函數(shù)遍歷集合
        System.out.println("使用toArray()函數(shù)遍歷集合");
        Object[] arr = list.toArray();
        //遍歷數(shù)組
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("使用Iterator迭代器遍歷集合");
//使用Iterator迭代器遍歷集合
        for (Iterator it = list.iterator(); it.hasNext();) {
            System.out.println(it.next());
        }
        System.out.println("使用ListIterator迭代器遍歷集合");
        //使用ListIterator迭代器遍歷集合
        for (ListIterator it = list.listIterator(); it.hasNext();) {
            System.out.println(it.next());
        }
        System.out.println("使用普通for循環(huán)遍歷集合");
        //使用普通for循環(huán)遍歷集合
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        System.out.println("使用foreach循環(huán)遍歷集合");
        //使用foreach循環(huán)遍歷集合
        for (Object obj : list) {
            System.out.println(obj);
        }
    }
}

2筷频、練習(xí)

需求:使用List存儲(chǔ)字符串,并去除重復(fù)元素前痘,要求在同一個(gè)集合中去除凛捏。
假如有一個(gè)集合,里面有一些重復(fù)的字符串芹缔。把重復(fù)給我去除坯癣。
分析和步驟:
思路:
1):創(chuàng)建一個(gè)空的集合l2
2):循環(huán)遍歷l1,取出每個(gè)字符串
3):判斷取出的元素在l2中是否存在
是:存在最欠,舍棄
否:不存在示罗,添加到l2
4):清空L1
5):把L2的元素添加到L1

注意:這里使用for循環(huán)遍歷迭代的是l1集合,不是l2集合芝硬。

/**
 * 需求:去除集合中的重復(fù)元素蚜点。
 */
public class CollectionTest {
    public static void main(String[] args) {
        
        List l1= new ArrayList();
        
        l1.add("aaaa");
                l1.add("abc");
        l1.add("abc");
        l1.add("bbbb");
        l1.add("aaaa");
        l1.add("xyz");
        l1.add("xyz");
        l1.add("aaaa");
        
        //新建一個(gè)新的集合容器
        List l2 = new ArrayList();
        
        for (Iterator it = l1.iterator(); it.hasNext();) {
            Object obj =  it.next();
            //判斷從原始集合中取出的元素在新的集合中是否存在
            if( !l2.contains(obj) ){
                l2.add(obj);
            }
        }
        //循環(huán)結(jié)束之后,l2集合中一定保存的都是不重復(fù)的元素
        //把原始集合中的數(shù)據(jù)清空拌阴,把l2中的不重復(fù)元素添加到原始集合中
        l1.clear();
        l1.addAll(l2);
        System.out.println(l1);
    }
}

3绍绘、LinkedList介紹(掌握)

3.1 概述

List集合

|------ArrayList集合

|------LinkedList集合
10.png

LinkedList集合:它也是List接口的實(shí)現(xiàn)類(lèi),它的底層使用的鏈接列表(鏈表)數(shù)據(jù)結(jié)構(gòu)。

鏈表結(jié)構(gòu)的特點(diǎn):有頭有尾陪拘。

補(bǔ)充概念:什么是數(shù)據(jù)結(jié)構(gòu)厂镇?
數(shù)據(jù)結(jié)構(gòu):數(shù)據(jù)的存儲(chǔ)方式,不同的集合容器它們存儲(chǔ)數(shù)據(jù)的方式都不一樣左刽。而我們學(xué)習(xí)眾多的集合容器捺信,重點(diǎn)是知道每個(gè)集合存儲(chǔ)數(shù)據(jù)的方式即可。不同集合的存儲(chǔ)方式不同欠痴,導(dǎo)致集合中的數(shù)據(jù)存取迄靠,以及元素能否重復(fù)等相關(guān)操作也都不相同。
LinkedList集合它采用的是數(shù)據(jù)結(jié)構(gòu)中的鏈表結(jié)構(gòu):
鏈表結(jié)構(gòu):由一個(gè)鏈子把多個(gè)節(jié)點(diǎn)連接起來(lái)的數(shù)據(jù)結(jié)構(gòu)喇辽。
節(jié)點(diǎn):在鏈表結(jié)構(gòu)中每個(gè)可以保存數(shù)據(jù)的空間稱(chēng)為節(jié)點(diǎn)梨水,而一個(gè)鏈表結(jié)構(gòu)是由多個(gè)節(jié)點(diǎn)組成的。
也就是說(shuō)鏈表結(jié)構(gòu)使用節(jié)點(diǎn)來(lái)存儲(chǔ)數(shù)據(jù)茵臭。
每個(gè)節(jié)點(diǎn)(存儲(chǔ)數(shù)據(jù)的空間)可以分成若干部分,其中有一部分存儲(chǔ)數(shù)據(jù)舅世,另外一部分存儲(chǔ)的是其他節(jié)點(diǎn)的地址旦委。

說(shuō)明:
1)節(jié)點(diǎn):實(shí)際存儲(chǔ)自己的數(shù)據(jù)+其他節(jié)點(diǎn)的地址,而作為鏈表結(jié)構(gòu)中的最后一個(gè)節(jié)點(diǎn)的存儲(chǔ)地址的空間是null雏亚。
2)鏈表結(jié)構(gòu)查詢(xún)或遍歷時(shí)都是從頭到尾的遍歷缨硝。
3)鏈表結(jié)構(gòu)是有頭有尾的。因此LinkedList集合中定義了自己的特有的方法都是圍繞鏈表的頭和尾設(shè)計(jì)的罢低。
4)鏈表分為兩種:?jiǎn)蜗蜴湵砗碗p向鏈表查辩。
如下圖所示就是數(shù)組結(jié)構(gòu)和鏈表結(jié)構(gòu)對(duì)數(shù)據(jù)的查詢(xún)、添加网持、刪除的區(qū)別對(duì)比圖如下所示:

11.png

總結(jié):
1)數(shù)組結(jié)構(gòu)查詢(xún)快宜岛,但是增刪慢;
2)鏈表結(jié)構(gòu)查詢(xún)慢功舀,但是增刪快萍倡;
上述兩種數(shù)據(jù)結(jié)構(gòu)快慢只是相對(duì)來(lái)說(shuō)。

LinkedList集合特點(diǎn):
1辟汰、它的底層使用的鏈表結(jié)構(gòu)列敲;
2、有頭有尾帖汞,其中的方法都是圍繞頭和尾設(shè)計(jì)的戴而;
3、LinkedList集合可以根據(jù)頭尾進(jìn)行各種操作翩蘸,但它的增刪效率高所意,查詢(xún)效率低;
LinkedList集合增刪效率高是因?yàn)榈讓邮擎湵斫Y(jié)構(gòu),如果增加或者刪除只需要在增加或者刪除節(jié)點(diǎn)的位置上記住新的節(jié)點(diǎn)的地址即可扁眯,而其他節(jié)點(diǎn)不需要移動(dòng)壮莹,所以速度會(huì)快。
而查詢(xún)遍歷由于鏈表結(jié)構(gòu)的特點(diǎn)姻檀,查詢(xún)只能從頭一直遍歷到鏈表的結(jié)尾命满,所以速度會(huì)慢。
4绣版、LinkedList集合底層也是線程不安全胶台。效率高;
5杂抽、也可以存儲(chǔ)null元素诈唬;

3.2 LinkedList集合的特有方法

12.png

說(shuō)明:
這里只有兩個(gè)構(gòu)造函數(shù),不像ArrayList集合有三個(gè)構(gòu)造函數(shù)缩麸,而這里不需要給容器初始化容量大小铸磅,因?yàn)長(zhǎng)inkedList集合的特點(diǎn)就是鏈表結(jié)構(gòu),在底層如果新增加一個(gè)元素那么前一個(gè)節(jié)點(diǎn)記住新增加的節(jié)點(diǎn)地址杭朱,而新增加的節(jié)點(diǎn)記住后一個(gè)節(jié)點(diǎn)地址就可以阅仔,什么時(shí)候增加什么記住地址即可,不需要初始化容量大小弧械。
由于LinkedList是鏈表結(jié)構(gòu)八酒,而鏈表有頭有尾,所以在LinkedList集合中專(zhuān)門(mén)為鏈表結(jié)構(gòu)提供了特有的函數(shù)刃唐。

13.png
14.png
15.png
16.png

練習(xí):需求練習(xí)LinkedList集合中特有的函數(shù)羞迷。

分析和步驟:
1)使用new關(guān)鍵字創(chuàng)建LinkedList類(lèi)的對(duì)象list,并賦值為L(zhǎng)inkedList類(lèi)型画饥;
2)使用對(duì)象list調(diào)用LinkedList集合中的addFirst()函數(shù)向集合中添加字符串衔瓮;
3)利用for循環(huán)和迭代器迭代遍歷LinkedList集合;
4)使用對(duì)象list調(diào)用LinkedList類(lèi)中的getLast()函數(shù)獲取最后一個(gè)元素并輸出抖甘;
5)使用對(duì)象list調(diào)用LinkedList類(lèi)中的removeFirst()函數(shù)刪除第一個(gè)元素并輸出报辱;

17.png

3.3 隊(duì)列和堆棧結(jié)構(gòu)(面試題)

由于LinkedList集合底層使用的鏈表結(jié)構(gòu):導(dǎo)致LinkedList集合在存儲(chǔ)數(shù)據(jù)的時(shí)候可以根據(jù)頭和尾進(jìn)行增、刪单山、改碍现、查各種操作∶准椋可以使用LinkedList集合模擬常見(jiàn)的2種數(shù)據(jù)結(jié)構(gòu):
隊(duì)列結(jié)構(gòu):先進(jìn)的先出或者后進(jìn)的后出昼接。(排隊(duì)買(mǎi)票)
堆棧結(jié)構(gòu):先進(jìn)的后出或者后進(jìn)的先出。(手槍的彈夾)
經(jīng)常使用LinkedList模擬上述的2種結(jié)構(gòu):

1)案例:使用LinkedList模擬隊(duì)列結(jié)構(gòu) 悴晰。先進(jìn)的先出 (排隊(duì)買(mǎi)票)
注意:隊(duì)列的特點(diǎn)是獲取一個(gè)元素便同時(shí)將獲取的元素直接刪除慢睡;
可以理解為一個(gè)人買(mǎi)票逐工,買(mǎi)完票就不在隊(duì)伍中了;
由于隊(duì)列的特點(diǎn)漂辐,是先進(jìn)先出泪喊,所以這里可以刪除第一個(gè)元素,并返回刪除的元素髓涯;

分析和步驟:
1)新建一個(gè)模擬隊(duì)列的類(lèi)QueueDemo袒啼;
2)在這個(gè)類(lèi)Queue中創(chuàng)建LinkedList類(lèi)的集合對(duì)象list;
3)定義一個(gè)添加元素的函數(shù)addElement(Object obj),在函數(shù)體里面使用集合對(duì)象list調(diào)用LinkedList類(lèi)的集合中的addLast(obj)函數(shù)纬纪,將元素添加尾部蚓再;
4)定義一個(gè)獲取元素的函數(shù)getElement(),返回值是Object,由于元素獲取之后就要從集合中刪除包各,所以在函數(shù)體里面使用集合對(duì)象list調(diào)用LinkedList類(lèi)的集合中的removeFirst()函數(shù)摘仅,移除頭部元素并返回刪除的元素;
5)定義一個(gè)判斷隊(duì)列中是否還有元素存在的函數(shù)isNull()问畅,函數(shù)返回值是boolean,在函數(shù)體里面使用集合對(duì)象list調(diào)用LinkedList類(lèi)的集合中的isEmpty()函數(shù)娃属,不包含元素返回true,包含返回false护姆;
6)定義一個(gè)測(cè)試類(lèi)LinkedListDemo1,在這個(gè)類(lèi)中使用new關(guān)鍵字創(chuàng)建剛才創(chuàng)建好的Queue類(lèi)的對(duì)象q膳犹;
7)使用對(duì)象q調(diào)用addElement()函數(shù)向集合中添加數(shù)據(jù);
8)使用while循環(huán)依次取出集合中的數(shù)據(jù)签则,對(duì)象q調(diào)用isNull()函數(shù)判斷是否還有元素,對(duì)象q調(diào)用getElement()铐料,獲得元素輸出并打咏チ选;

package cn.xuexi.list.test;
import java.util.LinkedList;
/*
 * 模擬隊(duì)列結(jié)構(gòu) 特點(diǎn):先進(jìn)先出钠惩,類(lèi)似買(mǎi)票
 */
//創(chuàng)建模擬隊(duì)列的類(lèi)
class QueueDemo
{
    //創(chuàng)建LinkedList集合對(duì)象
    LinkedList list=new LinkedList();
    //定義函數(shù)模擬向隊(duì)列中添加元素
    public void addElement(Object obj)
    {
        //每次都向集合最后面添加數(shù)據(jù) 添加到鏈表的尾部
        list.addLast(obj);
    }
    //定義函數(shù)柒凉,讓外界獲取元素
    public Object getElement()
    {
        /*
         * 由于隊(duì)列的特點(diǎn)是獲取一個(gè)元素便同時(shí)將獲取的元素直接刪除
         * 可以理解為一個(gè)人買(mǎi)票,買(mǎi)完票就不在隊(duì)伍中了
         * 由于隊(duì)列的特點(diǎn)篓跛,是先進(jìn)先出膝捞,所以這里可以刪除第一個(gè)元素,并返回刪除的元素
         */
        return list.removeFirst();
    }
    //判斷隊(duì)列中是否還有元素存在
    public boolean isNull()
    {
        /*
         * 在LinkedList函數(shù)中雖然沒(méi)有判斷集合中是否還含有數(shù)據(jù)
         * 但是它的接口List中含有愧沟,所以我們可以使用list.isEmpty()來(lái)判斷集合中是否還含有數(shù)據(jù)
         * isEmpty()函數(shù)是判斷集合中沒(méi)有元素返回true蔬咬,有元素返回false
         */
        return list.isEmpty();
    }
}
public class LinkedListQueue {
    public static void main(String[] args) {
//創(chuàng)建模擬隊(duì)列類(lèi)的對(duì)象
        QueueDemo q=new QueueDemo();
        //向隊(duì)列中添加數(shù)據(jù)
        q.addElement("元素1");
        q.addElement("元素2");
        q.addElement("元素3");
        //判斷集合中是否還含有元素,有沐寺,則輸出數(shù)據(jù)
        while(!q.isNull())//!q.isNull()如果為true林艘,表示集合中還有數(shù)據(jù)
        {
            //說(shuō)明集合中還有元素,取出數(shù)據(jù) 輸出元素結(jié)果 "元素1" "元素2" "元素3" 
            System.out.println(q.getElement());
        }
    }
}

2)案例:使用LinkedList模擬堆棧結(jié)構(gòu)混坞。先進(jìn)的后出狐援,(手槍的彈夾)

代碼和上述相同钢坦,只是將QueueDemo類(lèi)中的getElement()函數(shù)體中的代碼改成
return list.removeLast()即可
removeLast()表示移除尾部元素并返回刪除的元素。

package cn.xuexi.list.test;
import java.util.LinkedList;
/*
 * 模擬堆棧結(jié)構(gòu) 特點(diǎn):先進(jìn)后出啥酱,類(lèi)似子彈彈夾
 */
//創(chuàng)建模擬堆棧的類(lèi)
class QueueDemo
{
    //創(chuàng)建LinkedList集合對(duì)象
    LinkedList list=new LinkedList();
    //定義函數(shù)模擬向堆棧中添加元素
    public void addElement(Object obj)
    {
        //每次都向集合最后面添加數(shù)據(jù) 添加到鏈表的尾部
        list.addLast(obj);
    }
    //定義函數(shù)爹凹,讓外界獲取元素
    public Object getElement()
    {
        /*
         * 由于堆棧的數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)是先進(jìn)后出,
         * 所以我們可以將最后添加的數(shù)據(jù)先移除
         */
        return list.removeLast();
    }
    //判斷堆棧中是否還有元素存在
    public boolean isNull()
    {
        /*
         * 在LinkedList函數(shù)中雖然沒(méi)有判斷集合中是否還含有數(shù)據(jù)
         * 但是它的接口List中含有镶殷,所以我們可以使用list.isEmpty()來(lái)判斷集合中是否還含有數(shù)據(jù)
         * isEmpty()函數(shù)是判斷集合中沒(méi)有元素返回true禾酱,有元素返回false
         */
        return list.isEmpty();
    }
}
public class LinkedListQueue {
    public static void main(String[] args) {
        //創(chuàng)建模擬堆棧類(lèi)的對(duì)象
        QueueDemo q=new QueueDemo();
        //向堆棧中添加數(shù)據(jù)
        q.addElement("元素1");
        q.addElement("元素2");
        q.addElement("元素3");
        //判斷集合中是否還含有元素,有批钠,則輸出數(shù)據(jù)
        while(!q.isNull())//!q.isNull()如果為true宇植,表示集合中還有數(shù)據(jù)
        {
            //說(shuō)明集合中還有元素,取出數(shù)據(jù) 輸出元素結(jié)果 "元素3" "元素2" "元素1" 
            System.out.println(q.getElement());
        }
    }
}

4埋心、Vector介紹(了解)

Vector集合是JDK1.0的時(shí)候出現(xiàn)的集合指郁,它在jdk1.2的時(shí)候被收編到List接口的下面。而這個(gè)集合被JDK1.2中的ArrayList集合代替拷呆。

18.png

演示Vector類(lèi)的函數(shù):

分析和步驟:
1)定義一個(gè)測(cè)試類(lèi)VectorDemo闲坎,并創(chuàng)建這個(gè)類(lèi)的對(duì)象v;
2)使用對(duì)象v調(diào)用Vector類(lèi)中的addElement()函數(shù)向Vector集合中添加字符串?dāng)?shù)據(jù)茬斧;
3)使用集合對(duì)象v調(diào)用iterator()函數(shù)獲得迭代器的對(duì)象it腰懂;
4)使用循環(huán)借助迭代器對(duì)象調(diào)用hasNext()函數(shù)和next()函數(shù)遍歷集合,并輸出项秉,可是iterator()不是Vector集合原來(lái)就開(kāi)始使用的迭代方式绣溜;
5)使用對(duì)象v調(diào)用Vector類(lèi)中的elements()函數(shù)來(lái)獲得類(lèi)似迭代器的對(duì)象en,并給Enumeration類(lèi)型娄蔼;
6)同樣使用en對(duì)象調(diào)用Enumeration中的hasMoreElements()和nextElement()函數(shù)借助集合進(jìn)行遍歷怖喻,而這種方式是最開(kāi)始誕生Vector類(lèi)使用迭代的方式;

問(wèn)題:
通過(guò)查閱API得知岁诉,Vector集合從jdk1.0版本就已經(jīng)存在了锚沸,而迭代器Iterator是從jdk1.2版本開(kāi)始才引入的,那么在jdk1.2版本之前是怎么對(duì)Vector集合進(jìn)行迭代和遍歷取出集合中的數(shù)據(jù)呢涕癣?

解釋說(shuō)明:
1)在1.2版本之前我們借助于另一個(gè)接口Enumeration來(lái)實(shí)現(xiàn)迭代的哗蜈;
2)使用接口Enumeration對(duì)集合進(jìn)行迭代,可以先使用Vector集合的對(duì)象調(diào)用Vector集合中的elements()函數(shù)來(lái)獲得接口Enumeration的對(duì)象坠韩;
3)然后使用接口Enumeration的對(duì)象調(diào)用hasMoreElements()判斷集合中是否還有元素距潘,有返回true;
4)然后使用接口Enumeration的對(duì)象調(diào)用nextElement()函數(shù)獲取集合中的元素只搁;

package cn.xuexi.vector;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;
/*
 * Vector集合的演示
 */
public class VectorDemo {
    public static void main(String[] args) {
        //創(chuàng)建Vector集合對(duì)象
        Vector v=new Vector();
        //向Vector集合中添加數(shù)據(jù)
        v.addElement("黑旋風(fēng)");
        v.addElement("劉德華");
        v.addElement("成龍");
        v.addElement("李連杰");
        //遍歷集合
        /*for (Iterator it = v.iterator(); it.hasNext();) 
        {
            //獲取集合里面的數(shù)據(jù)
            System.out.println(it.next());
        }*/
        //使用jdk1.2版本之前對(duì)Vector集合進(jìn)行遍歷
        for (Enumeration en = v.elements(); en.hasMoreElements();) {
                //輸出集合中的數(shù)據(jù)
            System.out.println(en.nextElement());
        }
    }
}

Vector集合它就是ArrayList集合绽昼,可以使用Enumeration迭代Vector集合,但是由于Enumeration迭代器中的方法的名字太長(zhǎng)须蜗,被Iterator代替硅确。后期如果需要迭代器Vector應(yīng)該優(yōu)先考慮使用Iterator迭代目溉。

Vector集合的特點(diǎn):
1)Vector是jdk1.0出現(xiàn)的集合,它的增刪菱农,查詢(xún)效率都比較低缭付;
2)由于Vector底層是線程安全的。ArrayList 的底層是不安全的循未,因此ArrayList各方面的效率都比Vector高陷猫。
3)對(duì)于Vector類(lèi)現(xiàn)在開(kāi)發(fā)中已經(jīng)幾乎不再使用。根本就不用的妖。

5绣檬、List下的子類(lèi)總結(jié)(掌握)

  Collection集合(接口)
                |----List集合(接口):可以存儲(chǔ)重復(fù)元素、可以存儲(chǔ)null嫂粟、有角標(biāo)娇未、存取有序。
                |----ArrayList集合(類(lèi)):實(shí)現(xiàn)List接口星虹。ArrayList集合中的特有方法是實(shí)現(xiàn)List
                                         底層使用可變數(shù)組結(jié)構(gòu)零抬。
                                         查詢(xún)遍歷的效率比較高、增刪的效率比較低
                                         屬于線程不安全的集合類(lèi)宽涌。執(zhí)行效率比較高
                 |----LinkedList集合(類(lèi)):實(shí)現(xiàn)List接口平夜。
                                           底層使用鏈表結(jié)構(gòu)。(鏈表:有頭有尾)
                                           LinkedList集合中的特有方法都是圍繞鏈表的頭尾設(shè)計(jì)
                                           查詢(xún)遍歷的效率比較慢卸亮、增刪的效率比較高
                                           屬于線程不安全的集合類(lèi)忽妒。執(zhí)行效率比較高
                 |----Vector集合(類(lèi)):實(shí)現(xiàn)List接口。線程安全的集合類(lèi)。
                                       底層使用可變數(shù)組結(jié)構(gòu)。查詢(xún)遍歷效率、增刪效率都比較低。

                Vector類(lèi)屬于線程安全的集合類(lèi)妻熊。效率比較慢。現(xiàn)在開(kāi)發(fā)中已經(jīng)不再使用躏将。

問(wèn)題:遇到對(duì)線程有需求的情況陌知,應(yīng)該使用哪個(gè)集合類(lèi)?
還使用LinkedList颜及、ArrayList(在后面學(xué)習(xí)過(guò)程中甩苛,可以解決LinkedList\ ArrayList線程不安全的問(wèn)題)

疑問(wèn):
1)解釋?zhuān)簽槭裁碅rrayList集合增刪效率低,而查詢(xún)速度快俏站?
因?yàn)槲覀兿蚣现刑砑釉氐臅r(shí)候讯蒲,有時(shí)會(huì)將元素添加到集合中的最前面,或者有可能刪除最前面的數(shù)據(jù)肄扎,這樣就導(dǎo)致其他數(shù)據(jù)向后移動(dòng)或者刪除時(shí)向前移動(dòng)墨林,所以效率會(huì)低赁酝。
對(duì)于查詢(xún)ArrayList集合,由于ArrayList集合是數(shù)組結(jié)構(gòu)旭等,而數(shù)組結(jié)構(gòu)是排列有序的酌呆,并且下標(biāo)是有序增加的,當(dāng)查詢(xún)ArrayList集合的時(shí)候可以按照排列順序去查詢(xún)搔耕,或者直接可以通過(guò)某個(gè)下標(biāo)去查詢(xún)隙袁,這樣就會(huì)導(dǎo)致查詢(xún)速度相對(duì)來(lái)說(shuō)會(huì)快很多。

2)解釋?zhuān)簽槭裁碙inkedList集合增刪效率快弃榨,而查詢(xún)速度慢菩收?
LinkedList集合增刪效率高是因?yàn)榈讓邮擎湵斫Y(jié)構(gòu),如果增加或者刪除只需要在增加或者刪除節(jié)點(diǎn)的位置上記住新的節(jié)點(diǎn)的地址即可鲸睛,而其他節(jié)點(diǎn)不需要移動(dòng)娜饵,所以速度會(huì)快。
而查詢(xún)遍歷由于鏈表結(jié)構(gòu)的特點(diǎn)腊凶,查詢(xún)只能從頭一直遍歷到鏈表的結(jié)尾划咐,所以速度會(huì)慢。

注意:學(xué)習(xí)了這么多集合類(lèi)钧萍,在開(kāi)發(fā)中如果不知道使用哪個(gè)集合褐缠,到底什么時(shí)候使用ArrayList集合和LinkedList集合?
1)如果對(duì)集合進(jìn)行查詢(xún)操作建議使用ArrayList集合风瘦;
2)如果對(duì)集合進(jìn)行增刪操作建議使用LinkedList集合队魏;

總結(jié):如果實(shí)在把握不好使用的時(shí)機(jī),建議大家以后在開(kāi)發(fā)中都使用ArrayList集合即可万搔,因?yàn)樵陂_(kāi)發(fā)中我們對(duì)于集合的操作幾乎都是查詢(xún)操作胡桨,很少執(zhí)行增刪操作的。

6瞬雹、Set接口

6.1昧谊、Set接口概述

Collection集合(接口)的接口下面有2個(gè)直接的子接口:
 |-----List集合(接口):可以保存重復(fù)元素,擁有下標(biāo)酗捌,存儲(chǔ)有序呢诬,可以存儲(chǔ)多個(gè)null元素。
      |-----ArrayList類(lèi):底層是可變數(shù)組胖缤,根據(jù)下標(biāo)進(jìn)行操作尚镰,查詢(xún)效率快,增刪效率低哪廓。
      |-----LinkedList類(lèi):底層是鏈表狗唉,根據(jù)鏈表的頭尾進(jìn)行操作,增刪效率快涡真,查詢(xún)效率低分俯。
 |-----Set集合(接口):不能保存重復(fù)元素肾筐,沒(méi)有下標(biāo)“钠龋可以存儲(chǔ)null但只能有一個(gè)局齿。并且不保證存取的順序,也就是說(shuō)對(duì)于集合set進(jìn)行存取操作的時(shí)候都沒(méi)有任何順序橄登,沒(méi)有任何規(guī)律而言抓歼。
      |-----HashSet類(lèi)
            |-----LinkedHashSet類(lèi)
      |-----TreeSet類(lèi)
19.png

說(shuō)明:
1)Set接口中沒(méi)有自己的特有函數(shù),所有的函數(shù)全部來(lái)自于Collection接口拢锹。
2)Set集合沒(méi)有角標(biāo)谣妻,只能通過(guò)Iterator迭代器遍歷獲取集合中的元素,Set集合不能使用ListIterator迭代器卒稳,因?yàn)長(zhǎng)istIterator只是針對(duì)List集合特有的迭代器蹋半。

6.2、Set接口的練習(xí)

需求:存儲(chǔ)字符串并遍歷充坑。
由于Set是接口不能創(chuàng)建對(duì)象减江,只能創(chuàng)建Set實(shí)現(xiàn)類(lèi)的接口,任何一個(gè)都可以捻爷,這里就創(chuàng)建HashSet類(lèi)的對(duì)象辈灼。

分析和步驟:
1)使用new關(guān)鍵字創(chuàng)建Set接口下的子類(lèi)HashSet類(lèi)的對(duì)象,并賦值給對(duì)象s也榄,類(lèi)型是Set接口類(lèi)型巡莹;
2)使用對(duì)象s調(diào)用add()函數(shù)向集合中添加字符串?dāng)?shù)據(jù);
3)循環(huán)遍歷集合甜紫;

package cn.xuexi.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/*
 * Set集合的練習(xí)
 */
public class SetDemo {
    public static void main(String[] args) {
        //創(chuàng)建集合對(duì)象
        Set s=new HashSet(); 
        //向Set集合中添加數(shù)據(jù)
        s.add("aaa");
        s.add("aaa");
        s.add("bbb");
        s.add(null);
        s.add("ccc");
        //遍歷集合
        //注意:由于Set集合中不允許包含重復(fù)的元素降宅,所以當(dāng)添加元素的時(shí)候,
        //集合對(duì)象會(huì)把重復(fù)的元素刪除囚霸,不讓保存到集合中
        for (Iterator it = s.iterator(); it.hasNext();) {
            //輸出數(shù)據(jù)
            System.out.println(it.next());
        }
    }
}

7腰根、HashSet集合(掌握)

7.1、HashSet類(lèi)的介紹和特點(diǎn)

20.png

說(shuō)明:
1)實(shí)現(xiàn)了Set接口拓型,具備了Set集合的特性额嘿;
2)不保證集合中的迭代順序(不保證元素存取一致),允許存儲(chǔ)null元素吨述;
3)底層使用哈希表結(jié)構(gòu)岩睁;

案例:HashSet集合的應(yīng)用钞脂。
分析和步驟:
1)使用new關(guān)鍵字創(chuàng)建HashSet集合類(lèi)的對(duì)象set揣云,類(lèi)型是HashSet類(lèi)型;
2)使用集合對(duì)象set調(diào)用集合中的add()函數(shù)向HashSet集合中添加字符串?dāng)?shù)據(jù)冰啃;
3)使用對(duì)象set調(diào)用iterator()函數(shù)獲取迭代器對(duì)象it邓夕;
4)使用for循環(huán)遍歷刘莹,通過(guò)迭代器對(duì)象調(diào)用hasNext()和next()函數(shù),并輸出獲取的數(shù)據(jù)焚刚;

package cn.xuexi.set;
import java.util.HashSet;
import java.util.Iterator;
/*
 * hashSet演示
 */
public class HashSetDemo {
    public static void main(String[] args) {
        //創(chuàng)建HashSet集合對(duì)象
        HashSet set=new HashSet();
        //向集合中添加數(shù)據(jù)
        set.add("JavaSe");
        set.add("JavaSE");
        set.add("JavaEE");
        set.add("AAAA");
        set.add("AAAA");
        set.add("bbbb");
        set.add("bbbb");
        //遍歷集合
        for (Iterator it = set.iterator(); it.hasNext();) {
            //輸出集合中的數(shù)據(jù)
            System.out.println(it.next());
        }
    }
}

輸出結(jié)果:


21.png

通過(guò)以上程序輸出結(jié)果得出結(jié)論:
1)HashSet集合不能存儲(chǔ)重復(fù)的元素点弯;
2)HashSet集合存儲(chǔ)元素的順序不固定;

接下我們要分析為什么HashSet集合存儲(chǔ)的數(shù)據(jù)順序不固定和為什么不支持存儲(chǔ)重復(fù)的元素矿咕?
答案肯定和HashSet集合的底層哈希表數(shù)據(jù)結(jié)構(gòu)有關(guān)系抢肛,所以接下來(lái)我們要學(xué)習(xí)什么是哈希表。

7.2碳柱、哈希表介紹(掌握)

哈希表:
它是一個(gè)數(shù)據(jù)結(jié)構(gòu)捡絮,底層依賴(lài)的是數(shù)組,只是不按照數(shù)組的下標(biāo)操作數(shù)組中的元素莲镣。需要根據(jù)數(shù)組中存儲(chǔ)的元素的哈希值進(jìn)行元素操作福稳。

哈希表的存儲(chǔ)過(guò)程:
1)哈希表底層是一個(gè)數(shù)組,我們必須知道集合中的一個(gè)對(duì)象元素到底要存儲(chǔ)到哈希表中的數(shù)組的哪個(gè)位置瑞侮,也就是需要一個(gè)下標(biāo)的圆。
2)哈希表會(huì)根據(jù)集合中的每個(gè)對(duì)象元素的內(nèi)容計(jì)算得出一個(gè)整數(shù)值。由于集合中的對(duì)象元素類(lèi)型是任意的半火,而現(xiàn)在這里使用的算法必須是任意類(lèi)型的元素都可以使用的算法越妈。能夠讓任意類(lèi)型的對(duì)象元素都可以使用的算法肯定在任意類(lèi)型的對(duì)象所屬類(lèi)的父類(lèi)中,即上帝類(lèi)Object中慈缔,這個(gè)算法就是Object類(lèi)中的hashCode()函數(shù)叮称。

結(jié)論:要給HashSet集合中保存對(duì)象,需要調(diào)用對(duì)象的hashCode函數(shù)藐鹤。

解釋說(shuō)明:
通過(guò)查閱API得知瓤檐,使用Object的任意子類(lèi)對(duì)象都可以調(diào)用Object類(lèi)中的hashCode()函數(shù)并生成任意對(duì)象的哈希碼值。

22.png

代碼演示如下:


23.png

輸出結(jié)果:
24.png

由以上結(jié)果可以看出任意對(duì)象調(diào)用Object類(lèi)中的hashCode()函數(shù)都會(huì)生成一個(gè)整數(shù)娱节。

3)hashCode算法挠蛉,得到一個(gè)整數(shù),但是這個(gè)整數(shù)太大了肄满,這個(gè)值不能直接作為數(shù)組下標(biāo)的谴古。所以底層還會(huì)對(duì)這個(gè)值結(jié)合數(shù)組的長(zhǎng)度繼續(xù)計(jì)算運(yùn)行,得到一個(gè)在0~數(shù)組長(zhǎng)度-1之間的整數(shù)稠歉,這樣就可以作為數(shù)組的下標(biāo)了掰担。

問(wèn)題1:使用hashCode函數(shù)生成的一個(gè)過(guò)大整數(shù)是用什么算法將將生成哈希碼值變成0~數(shù)組長(zhǎng)度-1之間的數(shù)字呢?
其中一種最簡(jiǎn)單的算法是可以實(shí)現(xiàn)的怒炸,舉例:假設(shè)底層哈希表中數(shù)組長(zhǎng)度是5带饱,那么下標(biāo)的范圍是0~4,
所以我們這里可以使用生成的哈希碼值(過(guò)大的整數(shù))對(duì)數(shù)組長(zhǎng)度取余數(shù),我們發(fā)現(xiàn)任何數(shù)在這里對(duì)5取余都是在 0 1 2 3 4 之間勺疼,所以這樣就可以獲取到0~數(shù)組長(zhǎng)度-1之間的下標(biāo)了教寂。

問(wèn)題2:如果數(shù)據(jù)過(guò)多,HashSet底層的數(shù)組存儲(chǔ)不下执庐,怎么辦酪耕?
hashSet集合底層數(shù)組初始容量是16,如果大小不夠轨淌,那么會(huì)繼續(xù)新創(chuàng)建一個(gè)數(shù)組迂烁,新數(shù)組大小等于原來(lái)數(shù)組的大小*0.75+原來(lái)數(shù)組的大小。

4)如果上述做法已經(jīng)計(jì)算出底層數(shù)組的下標(biāo)位置递鹉,那么就要判斷計(jì)算出的下標(biāo)位置是否已經(jīng)有元素了:
A.如果下標(biāo)對(duì)應(yīng)的位置沒(méi)有元素:直接存儲(chǔ)數(shù)據(jù)婚被;
B.如果下標(biāo)對(duì)應(yīng)的位置有元素:這時(shí)就必須調(diào)用對(duì)象的equals()函數(shù)比較兩個(gè)對(duì)象是否相同:
如果結(jié)果是true:相同,直接將要添加的數(shù)據(jù)丟棄梳虽;
如果結(jié)果是false:不相同址芯,那直接將數(shù)據(jù)存儲(chǔ)到數(shù)組當(dāng)前空間位置;
但是當(dāng)前位置已經(jīng)存在元素了窜觉,怎么將后來(lái)的數(shù)據(jù)存儲(chǔ)到數(shù)組中呢谷炸?
這里需要使用類(lèi)似鏈表的結(jié)構(gòu)了,在當(dāng)前位置上在畫(huà)出來(lái)一個(gè)空間禀挫,然后將當(dāng)前的對(duì)象數(shù)據(jù)保存到新劃出來(lái)的空間中旬陡,在原來(lái)的空間中設(shè)置一個(gè)引用變量記錄著新劃分空間的地址,如果后面還有數(shù)據(jù)要存儲(chǔ)當(dāng)前空間语婴,做法和上述相同描孟。

哈希表如下圖所示:

25.png

最終結(jié)論:哈希表底層通過(guò)hashCode和equals算法結(jié)合,來(lái)保證對(duì)象數(shù)據(jù)在HashSet集合中不重復(fù)唯一砰左,并且存儲(chǔ)的順序不固定匿醒。
哈希表:數(shù)組+hashCode函數(shù)+equals函數(shù),哈希表保證對(duì)象唯一需要依賴(lài)對(duì)象的hashCode和equals方法缠导。

面試題:哈希表如何保證元素唯一廉羔?
哈希表保證元素唯一依賴(lài)兩個(gè)方法:hashCode和equals。
哈希表底層其實(shí)還是一個(gè)數(shù)組僻造,元素在存儲(chǔ)的時(shí)候憋他,會(huì)先通過(guò)hashCode算法結(jié)合數(shù)組長(zhǎng)度得到一個(gè)索引。然后判斷該索引位置是否有元素:如果沒(méi)有髓削,不用調(diào)用equals函數(shù)竹挡,直接存儲(chǔ);如果有立膛,再調(diào)用元素的equals方法比較是否相同:相同揪罕,直接舍棄;如果不同,也存儲(chǔ)耸序。

7.3、HashSet保存自定義對(duì)象

案例需求:向HashSet集合中添加自定義對(duì)象鲁猩。

分析和步驟:
1)定義一個(gè)Person類(lèi)坎怪,在Person類(lèi)中定義兩個(gè)屬性name和age,并分別生成toString()廓握、set()搅窿、get()方法;
2)在定義一個(gè)測(cè)試類(lèi)HashSetDemo隙券,在這個(gè)類(lèi)中的main函數(shù)中男应,使用new關(guān)鍵字創(chuàng)建HashSet集合類(lèi)的對(duì)象set,類(lèi)型是HashSet類(lèi)型娱仔;
3)使用集合對(duì)象set調(diào)用集合中的add()函數(shù)向HashSet集合中添加Person類(lèi)的匿名對(duì)象沐飘,并通過(guò)創(chuàng)建Person類(lèi)的對(duì)象調(diào)用構(gòu)造函數(shù)給name和age賦值;
4)使用對(duì)象set調(diào)用iterator()函數(shù)獲取迭代器對(duì)象it牲迫;
5)使用for循環(huán)遍歷耐朴,通過(guò)迭代器對(duì)象調(diào)用hasNext()和next()函數(shù),并輸出獲取的數(shù)據(jù)盹憎;

測(cè)試類(lèi)代碼如下:

package cn.xuexi.set;
import java.util.HashSet;
import java.util.Iterator;
/*
 * 使用哈希表存儲(chǔ)自定義對(duì)象
 */
public class HashSetDemo2 {

    public static void main(String[] args) {
        //創(chuàng)建集合對(duì)象
        HashSet set=new HashSet();
        //向集合中添加自定義對(duì)象數(shù)據(jù)
        set.add(new Person("黑旋風(fēng)",19));
        set.add(new Person("助教",18));
        set.add(new Person("班導(dǎo)",28));
        set.add(new Person("班長(zhǎng)",19));
        set.add(new Person("黑旋風(fēng)",19));
        //循環(huán)遍歷集合
        for (Iterator it = set.iterator(); it.hasNext();) {
            //輸出數(shù)據(jù)
            System.out.println(it.next());
        }
    }
}

運(yùn)行結(jié)果:

26.png

上述代碼有問(wèn)題筛峭,為什么以上程序中的HashSet集合中會(huì)出現(xiàn)相同的Person類(lèi)的對(duì)象?

給哈希表中保存自定義對(duì)象時(shí)陪每,由于每次創(chuàng)建的都是新的Person對(duì)象影晓,而每個(gè)Person對(duì)象都有自己的唯一的內(nèi)存地址,就是每個(gè)對(duì)象的內(nèi)存地址都不相同檩禾。并且在給哈希表中存放這些對(duì)象的時(shí)候每個(gè)對(duì)象都需要調(diào)用自己從Object類(lèi)中繼承到的hashCode函數(shù)挂签,而hashCode函數(shù)會(huì)根據(jù)每個(gè)對(duì)象自己內(nèi)存地址計(jì)算每個(gè)對(duì)象哈希值,每個(gè)對(duì)象的內(nèi)存地址不同盼产,計(jì)算出來(lái)的哈希值也一定不同竹握,最后就會(huì)導(dǎo)致生成的底層的數(shù)組的下標(biāo)也不相同。如果下標(biāo)不一樣辆飘,根本就不會(huì)去調(diào)用equals比較是否是同一個(gè)元素啦辐,直接就會(huì)導(dǎo)致每個(gè)對(duì)象都可以正常的保存到哈希表中。

自己定義的對(duì)象蜈项,有自己所屬的類(lèi)芹关,而這個(gè)類(lèi)繼承到了Object的hashCode函數(shù),所以導(dǎo)致這個(gè)函數(shù)是根據(jù)對(duì)象內(nèi)存地址計(jì)算哈希值紧卒,而我們更希望根據(jù)對(duì)象自己的特有數(shù)據(jù)(name和age的屬性值)來(lái)計(jì)算哈希值侥衬。

問(wèn)題:那么為什么我們之前向集合中保存String類(lèi)的字符串對(duì)象時(shí)沒(méi)有出現(xiàn)這種現(xiàn)象呢?

因?yàn)樵赟tring類(lèi)中已經(jīng)復(fù)寫(xiě)了Object類(lèi)中的hashCode()和equals()函數(shù),所以存儲(chǔ)字符串對(duì)象的時(shí)候轴总,底層調(diào)用的hashCode()和equals()函數(shù)根本就不是Object類(lèi)中的而是String類(lèi)中已經(jīng)復(fù)寫(xiě)好的函數(shù)直颅。

也就是對(duì)于String類(lèi)的對(duì)象調(diào)用自己的hashCode函數(shù),根本就不是根據(jù)字符串對(duì)象的地址計(jì)算出來(lái)的哈希碼值怀樟,而是根據(jù)字符串內(nèi)容計(jì)算出來(lái)的功偿。

對(duì)于equals函數(shù),也是調(diào)用自己的函數(shù)往堡,底層根本比較不是 this==obj,而是比較的是字符串對(duì)象的內(nèi)容械荷。

解決上述問(wèn)題的辦法:

所以我們需要重寫(xiě)Object類(lèi)中的hashCode方法。這樣重寫(xiě)完之后就會(huì)根據(jù)對(duì)象特有的數(shù)據(jù)來(lái)計(jì)算哈希值了虑灰,而不再是根據(jù)對(duì)象的隨機(jī)地址生成哈希值了吨瞎。在這里:根據(jù)姓名和年齡計(jì)算。

上述辦法只重寫(xiě)hashCode函數(shù)穆咐,僅僅是保證了對(duì)象內(nèi)容相同時(shí)計(jì)算的數(shù)組下標(biāo)相同颤诀。同時(shí)即使一個(gè)人的姓名和年齡不相等,也有可能計(jì)算的下標(biāo)是相同的对湃。所以着绊,當(dāng)數(shù)組下標(biāo)相同時(shí),我們接下來(lái)還要調(diào)用equals函數(shù)來(lái)比較對(duì)象元素的內(nèi)容熟尉。根據(jù)我們之前所學(xué)的Object類(lèi)中的equals函數(shù)得知归露,Object類(lèi)中的equals函數(shù)體是比較兩個(gè)對(duì)象的地址值是否相等,那么即使兩個(gè)對(duì)象內(nèi)容的下標(biāo)相同斤儿,但是equals函數(shù)比較的不是兩個(gè)對(duì)象的內(nèi)容剧包,是地址,也會(huì)永遠(yuǎn)不相等往果,所以這里也得復(fù)寫(xiě)Object類(lèi)中的equals()函數(shù)疆液。這樣才能保證當(dāng)兩個(gè)對(duì)象內(nèi)容一致不會(huì)被保存到HashSet集合中。

所以還得重寫(xiě)equals方法陕贮。

手動(dòng)復(fù)寫(xiě)Object類(lèi)中的hashCode()和equals()函數(shù)堕油,代碼如下:

27.png

說(shuō)明:雖然上述手動(dòng)復(fù)寫(xiě)Object類(lèi)中的hashCode和equals函數(shù)可以實(shí)現(xiàn),但是在真實(shí)開(kāi)發(fā)中建議最好不要手動(dòng)去書(shū)寫(xiě)肮之,比較麻煩掉缺。

建議直接在eclipse中使用快捷鍵生成的方式,比較簡(jiǎn)單戈擒,開(kāi)發(fā)效率還快眶明。

在Person類(lèi)中使用快捷鍵生成復(fù)寫(xiě)Object類(lèi)中的hashCode()和equals()函數(shù):

package cn.xuexi.set;
/*
 * 描述人類(lèi)
 */
public class Person {
    //屬性
    String name;
    int age;
    public Person(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;
    }
    //復(fù)寫(xiě)Object類(lèi)中的toString()函數(shù)
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
    
    /*//復(fù)寫(xiě)hashCode函數(shù)
    @Override
    public int hashCode() {
        
         * 根據(jù)年齡和姓名計(jì)算一個(gè)int值 this.name.hashCode()表示調(diào)用String類(lèi)中的hashCode函數(shù)
         * 如果兩個(gè)字符串相同,生成的哈希值也會(huì)相同 如果兩個(gè)人的名字一樣筐高,則生成哈希值相同
         * this.name.hashCode()+this.age :表示如果兩個(gè)人的姓名和年齡相同搜囱,那么返回得整數(shù)值肯定也相同
         
        return this.name.hashCode()+this.age;
    }
    //復(fù)寫(xiě)equals函數(shù)
    @Override
    public boolean equals(Object obj) {
        Person p=(Person)obj;//強(qiáng)制轉(zhuǎn)換為Person類(lèi)型的對(duì)象
        return this.name.equals(p.name) && this.age==p.age;
    }*/
    
    //復(fù)寫(xiě)Object類(lèi)中的hashCode()函數(shù)
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    //復(fù)寫(xiě)Object類(lèi)中的equals()函數(shù)
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

結(jié)論:
要求以后給HashSet集合即哈希表中保存對(duì)象的時(shí)候丑瞧,要求當(dāng)前這個(gè)對(duì)象所屬的類(lèi)必須復(fù)寫(xiě)Object類(lèi)中的hashCode和equals方法。建議最好快捷鍵生成蜀肘。

7.4绊汹、HashSet總結(jié)

HashSet集合存儲(chǔ)對(duì)象的時(shí)候:
1、HashSet集合的底層使用的哈希表結(jié)構(gòu)扮宠。那么就要求存放的對(duì)象必須具備hashCode功能西乖。由于任何一個(gè)類(lèi)的父類(lèi)都是Object類(lèi),而hashCode函數(shù)定義在了Object類(lèi)中涵卵,因此所有的對(duì)象都具備hashCode功能。
2荒叼、如果我們要把一個(gè)對(duì)象可以正確的存放在HashSet集合中轿偎,這個(gè)對(duì)象所屬的類(lèi)一般都需要復(fù)寫(xiě)hashCode函數(shù)。建立本類(lèi)自己的計(jì)算哈希值的方式被廓。
3坏晦、如果在HashSet集合中要保證對(duì)象唯一,不能僅僅依靠hashCode函數(shù)嫁乘,還要依賴(lài)于對(duì)象的equals函數(shù)昆婿,當(dāng)hashCode函數(shù)計(jì)算出來(lái)的哈希值相同的時(shí)候,還要調(diào)用equals方法比較2個(gè)對(duì)象是否相同蜓斧。
4仓蛆、要求在向HashSet集合中存儲(chǔ)自己定義一個(gè)類(lèi)對(duì)象的時(shí)候,那么必須在這個(gè)自定義類(lèi)中復(fù)寫(xiě)Object類(lèi)中的hashCode和equals函數(shù)挎春。
5看疙、注意當(dāng)向HashSet集合中存儲(chǔ)數(shù)據(jù)的時(shí)候,對(duì)象一定會(huì)調(diào)用hashCode函數(shù)計(jì)算下標(biāo)直奋,但是不一定一定會(huì)調(diào)用equals函數(shù)能庆,只有當(dāng)計(jì)算的下標(biāo)相同時(shí)
才會(huì)調(diào)用equals函數(shù),否則不會(huì)調(diào)用equals函數(shù)來(lái)比較兩個(gè)對(duì)象是否相等脚线。

7.5搁胆、LinkedHashSet介紹(了解)

28.png

LinkedHashSet集合:它的底層使用的鏈表+哈希表結(jié)構(gòu)。它和HashSet集合的區(qū)別是LinkedHashSet是一個(gè)可以保證存取順序的集合邮绿,并且LinkedHashSet集合中的元素也不能重復(fù)渠旁。
特點(diǎn):
A:存取有序(底層有一個(gè)鏈接表) 鏈表記錄著存儲(chǔ)數(shù)據(jù)的順序
B:保證元素的唯一(哈希表) 哈希表是真正存儲(chǔ)數(shù)據(jù)的地方
C:線程不安全,效率高

29.png

說(shuō)明:LinkedHashSet集合沒(méi)有自己的特有函數(shù)船逮,所有的功能全部繼承父類(lèi)一死。

分析和步驟:
1)定義一個(gè)測(cè)試類(lèi)LinkedHashSetDemo;
2)在這個(gè)類(lèi)中使用new關(guān)鍵字創(chuàng)建LinkedHashSet類(lèi)的對(duì)象set傻唾,對(duì)象set的類(lèi)型是LinkedHashSet投慈;
3)使用對(duì)象set調(diào)用add()函數(shù)給集合LinkedHashSet添加字符串常量承耿;
4)使用迭代器類(lèi)Iterator進(jìn)行迭代并輸出集合中的數(shù)據(jù);

package cn.xuexi.set;
import java.util.Iterator;
import java.util.LinkedHashSet;
/*
 * LinkedHashSet集合演示
 */
public class LinkedHashSetDemo {
    public static void main(String[] args) {
        //創(chuàng)建集合對(duì)象
        LinkedHashSet set=new LinkedHashSet();
        //向集合中添加數(shù)據(jù)
        set.add("aaa");
        set.add("bbb");
        set.add("aaa");
        set.add("ccc");
        //遍歷集合
        for (Iterator it = set.iterator(); it.hasNext();) {
            //輸出集合中的數(shù)據(jù)
            System.out.println(it.next());
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伪煤,一起剝皮案震驚了整個(gè)濱河市加袋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抱既,老刑警劉巖职烧,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異防泵,居然都是意外死亡蚀之,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)捷泞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)足删,“玉大人,你說(shuō)我怎么就攤上這事锁右∈埽” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵咏瑟,是天一觀的道長(zhǎng)拂到。 經(jīng)常有香客問(wèn)我,道長(zhǎng)码泞,這世上最難降的妖魔是什么兄旬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮余寥,結(jié)果婚禮上辖试,老公的妹妹穿的比我還像新娘。我一直安慰自己劈狐,他們只是感情好罐孝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著肥缔,像睡著了一般莲兢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上续膳,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天改艇,我揣著相機(jī)與錄音,去河邊找鬼坟岔。 笑死谒兄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的社付。 我是一名探鬼主播承疲,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼邻耕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了燕鸽?” 一聲冷哼從身側(cè)響起兄世,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎啊研,沒(méi)想到半個(gè)月后御滩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡党远,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年削解,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沟娱。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氛驮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出花沉,到底是詐尸還是另有隱情柳爽,我是刑警寧澤媳握,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布碱屁,位于F島的核電站,受9級(jí)特大地震影響蛾找,放射性物質(zhì)發(fā)生泄漏娩脾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一打毛、第九天 我趴在偏房一處隱蔽的房頂上張望柿赊。 院中可真熱鬧,春花似錦幻枉、人聲如沸碰声。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)胰挑。三九已至,卻和暖如春椿肩,著一層夾襖步出監(jiān)牢的瞬間瞻颂,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工郑象, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贡这,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓厂榛,卻偏偏與公主長(zhǎng)得像盖矫,于是被迫代替她去往敵國(guó)和親丽惭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容