Java 基礎(chǔ) 08. Java 集合框架

一、集合概述

  • 概念:對象的容器,定義了對多個(gè)對象進(jìn)行操作的常用方法。可實(shí)現(xiàn)數(shù)組的功能。
  • 和數(shù)組的區(qū)別
    1. 數(shù)組長度 固定,集合長度 不固定。
    2. 數(shù)組可以存儲(chǔ)基本類型和引用類型,集合只能存儲(chǔ)引用類型。
      注:基本類型可以通過裝箱操作,存儲(chǔ)到集合中
  • 位置: java.util.*;

二、Collection 體系集合

三、Collection 父接口

  • 特點(diǎn):代表一組任意類型的對象,無序、無下標(biāo)、不能重復(fù)。
  • 方法
方法名 說明
boolean add(Object obj) 添加一個(gè)對象
boolean addAll(Collection c) 將一個(gè)集合中的所有對象,添加到此集合中
void clear() 清空集合中的所有對象
boolean contains(Object o) 檢查集合中是否包含 o 對象
boolean equals(Object o) 比較集合是否與指定對象相等
boolean isEmpty() 判斷集合是否為空
boolean remove(Object o) 在集合中移除 o 對象
int size() 返回集合中的元素個(gè)數(shù)
Object[] toArray() 將集合轉(zhuǎn)換成數(shù)組
  • Collection 接口的使用(一)
package com.base.demo09;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/*
 * Collection 接口的使用
 * 1.添加元素
 * 2.刪除元素
 * 3.遍歷元素
 * 4.判斷
 * */
public class Demo01 {
    public static void main(String[] args) {
        // 創(chuàng)建集合
        Collection collection = new ArrayList();
        // 1.添加元素
        collection.add("蘋果");
        collection.add("梨");
        collection.add("西瓜");
        System.out.println("元素個(gè)數(shù):" + collection.size());
        System.out.println(collection); // collection 已自動(dòng)重寫了 toString()方法
        // 2.刪除元素
//        collection.remove("西瓜");
        // 清空
//        collection.clear();
        System.out.println("刪除后元素個(gè)數(shù):" + collection.size());

        // 3.遍歷元素(重點(diǎn))
        // 3.1增強(qiáng)for(不能使用for,因?yàn)闆]有下標(biāo))
        System.out.println("--------------3.1增強(qiáng)for-------------");
        for (Object o : collection) {
            System.out.println(o);
        }
        // 3.2使用迭代器(專門用來遍歷集合的一種方式,是一個(gè)接口)
        // hasNext();   是否有下一個(gè)元素
        // next();  獲取下一個(gè)元素
        // remove();    刪除當(dāng)前元素
        System.out.println("--------------3.2使用迭代器-------------");
        Iterator it = collection.iterator();    // 接口
        while (it.hasNext()) {
            String s = (String) it.next();  // 強(qiáng)轉(zhuǎn)到String
            System.out.println(s);
            // collection.remove(s); 引發(fā)錯(cuò)誤:并發(fā)修改異常。迭代時(shí),不能使用 collection.remove(s)
//            it.remove();    // 可以通過迭代內(nèi)部方法,進(jìn)行刪除
        }
        System.out.println("元素個(gè)數(shù):" + collection.size());
        // 4.判斷
        // contains()是否包含
        System.out.println(collection.contains("西瓜"));
        // 是否為空
        System.out.println(collection.isEmpty());
    }
}
  • Collection 接口的使用(二)
package com.base.demo09;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

// Collection 的使用:保存學(xué)生信息
public class Demo02 {
    public static void main(String[] args) {
        // 1.創(chuàng)建 Collection 對象
        Collection collection = new ArrayList();
        // 創(chuàng)建學(xué)生對象
        Student s1 = new Student("張三", 20);
        Student s2 = new Student("李四", 19);
        Student s3 = new Student("王五", 21);
        // 2.添加數(shù)據(jù)
        collection.add(s1);
        collection.add(s2);
        collection.add(s3);
        System.out.println("元素個(gè)數(shù):" + collection.size());
        System.out.println(collection.toString());
        // 3.刪除
//        collection.remove(s1);
//        collection.remove(new Student("李四", 21));  需重寫 equals(this==obj) 方法,才能實(shí)現(xiàn)
        // 從集合中刪除,并沒有刪除對象
//        collection.clear();
        System.out.println("刪除之后:" + collection.size());

        // 4.遍歷
        // 4.1增強(qiáng) for
        // 遍歷后為對象類型,需要轉(zhuǎn)換
        System.out.println("-----------4.1增強(qiáng) for------------");
        for (Object o : collection) {
            Student s = (Student) o;  // 將對象類型,轉(zhuǎn)換為學(xué)生類型
            System.out.println(s.toString());
        }
        // 4.2迭代器 hasNext(); next(); remove(); 迭代過程不能使用 collection 的刪除方法
        System.out.println("-----------4.2迭代器------------");
        Iterator it = collection.iterator();
        while (it.hasNext()) {
            Student s = (Student) it.next();    // 類型轉(zhuǎn)換
            System.out.println(s);
        }
        // 5. 判斷
        // 是否包含對象
        System.out.println(collection.contains(s1));
        // 是否為空
        System.out.println(collection.isEmpty());
    }
}
  • 實(shí)例:學(xué)生類 Student
package com.base.demo09;

// 學(xué)生類
public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

四、Collection 子接口

List 集合

  • 特點(diǎn):有序、有下標(biāo)、元素可以重復(fù)。
  • 方法:繼承 Collection 的方法外,自己的部分方法
方法名 說明
void add(int index,Object o) 在 index 位置插入對象 o
boolean addAll(index,Collection c) 將一個(gè)集合中的元素,添加到此集合中的 index 位置
Object get(int index) 返回集合中,指定位置的元素
List subList(int fromIndex,int toIndex) 返回 fromIndex 和 toIndex 之間的集合元素
  • 實(shí)例:List 子接口的使用(一)
package com.base.demo09;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/*
 * List 子接口使用
 * 特點(diǎn):1.有序 2.有下標(biāo) 3.可以重復(fù)
 * 1.添加元素
 * 2.刪除元素
 * 3.遍歷元素
 * 4.判斷
 * 5.獲取位置
 * */
public class Demo03 {
    public static void main(String[] args) {
        // 創(chuàng)建對象
        List list = new ArrayList<>();
        // 1.添加元素
        list.add("一");
        list.add("二");
        list.add("三");
        System.out.println("元素個(gè)數(shù):" + list.size());
        System.out.println(list.toString());
        // 2.刪除元素
        /*
        list.remove("二");
        list.remove(0); // 下標(biāo)方式刪除
        System.out.println("刪除后:"+list.size());
        System.out.println(list.toString());
        */
        // 3.遍歷元素
        // 3.1 for 遍歷
        System.out.println("-----------3.1 for 遍歷------------");
        for (int i = 0; i < list.size(); i++) {
            // get() 獲取集合中的元素,i 為下標(biāo)
            System.out.println(i + ":" + list.get(i));
        }
        // 3.2 增強(qiáng) for 遍歷
        System.out.println("-----------3.2 增強(qiáng) for 遍歷------------");
        for (Object o : list) {
            System.out.println(o);
        }
        // 3.3 迭代器遍歷 Iterator
        System.out.println("-----------3.3 Iterator 迭代器遍歷------------");
        Iterator it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        // 3.4 列表迭代器遍歷:ListIterator 可以雙向遍歷,添加、刪除及修改元素
        System.out.println("-----------3.4 ListIterator 迭代器遍歷 從前向后------------");
        ListIterator lit = list.listIterator();
        while (lit.hasNext()) {
            System.out.println(lit.nextIndex() + ":" + lit.next());
        }
        // 3.5 列表迭代器,反向遍歷(迭代時(shí),“遍歷指針” 需指向末尾)
        System.out.println("-----------3.5 列表迭代器 從后向前------------");
        while (lit.hasPrevious()) {
            System.out.println(lit.previousIndex() + ":" + lit.previous());
        }
        // 4.判斷
        System.out.println(list.contains("二")); // 是否包含元素
        System.out.println(list.isEmpty()); // 是否為空
        // 5.獲取位置
        System.out.println(list.indexOf("一"));
    }
}
  • 實(shí)例:List 子接口的使用(二)
package com.base.demo09;

import java.util.ArrayList;
import java.util.List;

/*
 * List 使用(二)
 * */
public class Demo04 {
    public static void main(String[] args) {
        // 創(chuàng)建對象
        List list = new ArrayList();
        // 1.添加數(shù)字?jǐn)?shù)據(jù)(自動(dòng)裝箱)
        list.add(20);
        list.add(30);
        list.add(40);
        list.add(50);
        list.add(60);
        System.out.println("元素個(gè)數(shù):" + list.size());
        System.out.println(list.toString());
        // 2.刪除:remove()內(nèi)通過下標(biāo)刪除,需要將刪除的數(shù)字做類型轉(zhuǎn)換
        // list.remove(0);
        // list.remove(20);  異常,數(shù)組越界
        list.remove(new Integer(20));   // 或 list.remove((Object) 20);
        System.out.println("刪除后:" + list.size());
        System.out.println(list.toString());

        // 3.subList() 返回子集合(含頭不含尾)
        List list2 = list.subList(0, 3);
        System.out.println(list2.toString());
    }
}

五、List 實(shí)現(xiàn)類

  1. ArrayList【重點(diǎn)】
  • 數(shù)組結(jié)構(gòu) 實(shí)現(xiàn),查詢塊、增刪慢;
  • JDK1.2 版本,運(yùn)行效率快、線程不安全。
package com.base.demo09;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;

/**
 * ArrayList的使用
 * 存儲(chǔ)結(jié)構(gòu):數(shù)組;
 * 特點(diǎn):查找遍歷速度快,增刪慢。
 * 1.添加元素
 * 2.刪除元素
 * 3.遍歷元素(重點(diǎn))
 * 4.判斷
 * 5.查找
 */
public class Demo05 {
    public static void main(String[] args) {
        // 創(chuàng)建對象  (size 0, 容量 0,每次擴(kuò)容大小是原來的 1.5 倍)
        ArrayList arrayList = new ArrayList<>();
        // 創(chuàng)建學(xué)生類對象
        Student s1 = new Student("學(xué)生1", 20);
        Student s2 = new Student("學(xué)生2", 21);
        Student s3 = new Student("學(xué)生3", 19);
        // 1.添加元素
        arrayList.add(s1);
        arrayList.add(s2);
        arrayList.add(s3);
        System.out.println("元素個(gè)數(shù):" + arrayList.size());
        System.out.println(arrayList.toString());
        // 2.刪除元素
//        arrayList.remove(s1);
        // 和 s1,是兩個(gè)不同的對象,需重寫 equals(this==obj) 方法
        arrayList.remove(new Student("學(xué)生1", 20)); 
        System.out.println("刪除之后:" + arrayList.size());

        // 3.遍歷元素(重點(diǎn)): for、增強(qiáng)for、迭代器、列表迭代器
        // 3.1迭代器 Iterator
        System.out.println("---------------3.1迭代器 Iterator------------");
        Iterator it = arrayList.iterator();
        while (it.hasNext()) {
            Student s = (Student) it.next();    // 類型轉(zhuǎn)換:對象類轉(zhuǎn)換為 Student 類
            System.out.println(s.toString());
        }
        // 3.2列表迭代器 ListIterator
        System.out.println("---------------3.2列表迭代器 ListIterator------------");
        ListIterator lit = arrayList.listIterator();
        while (lit.hasNext()) {
            Student s = (Student) lit.next();
            System.out.println(s.toString());
        }
        // 3.3列表迭代器(逆序:從后往前遍歷)
        System.out.println("---------------3.3列表迭代器(逆序)------------");
        while (lit.hasPrevious()){
            Student s = (Student) lit.previous();
            System.out.println(s.toString());
        }
        // 4.判斷
        // 是否包含對象
        System.out.println(arrayList.contains(s2)); // true
        // 重寫 equals() 方法后
        System.out.println(arrayList.contains(new Student("學(xué)生2", 21))); // true
        System.out.println(arrayList.isEmpty());
        // 5.查找
        System.out.println(arrayList.indexOf(s3));
        System.out.println(arrayList.indexOf(new Student("學(xué)生2", 21)));
    }
}

注:Object 里的 equals(this==obj) 用地址和當(dāng)前對象比較,如果想實(shí)現(xiàn)代碼中的問題,可以在學(xué)生類中重寫 equals 方法:查看 equals 方法重寫

// 重寫 equals() 方法
    @Override
    public boolean equals(Object obj) {
        // 1.判斷是否是同一對象
        if (this == obj) {
            return true;
        }
        // 2.判斷是否為空
        if (obj == null) {
            return false;
        }
        // 3.判斷是否是 Student 類型
        if (obj instanceof Student) {
            Student s = (Student) obj;    // 類型轉(zhuǎn)換
            // 4.比較屬性
            if (this.name.equals(s.getName()) && this.age == s.getAge()) {
                return true;
            }
        }
        // 5.不滿足條件,返回false
        return false;
    }

//    IDEA 方法1
//    @Override  
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (!(o instanceof Student)) return false;
//
//        Student student = (Student) o;
//
//        if (getAge() != student.getAge()) return false;
//        return getName().equals(student.getName());
//    }

//    IDEA 方法2
//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (!(o instanceof Student)) return false;
//
//        Student student = (Student) o;
//
//        if (age != student.age) return false;
//        return name.equals(student.name);
//    }

ArrayList 源碼分析

  • 默認(rèn)容量大小:private static final int DEFAULT_CAPACITY = 10;
    (如果沒有向集合中添加元素,容量 0 ,添加第一個(gè)元素后,容量 10,每次擴(kuò)容大小是原來的 1.5 倍)
  • 存放元素的數(shù)組:transient Object[] elementData;
  • 實(shí)際元素個(gè)數(shù):private int size;
  • 創(chuàng)建對象時(shí),調(diào)用的無參構(gòu)造函數(shù):
//這是一個(gè)空的數(shù)組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 無參構(gòu)造
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

注:沒有向集合中添加任何元素時(shí),集合容量為 0。(默認(rèn)的 10 個(gè)容量怎么來的?)查看 add 方法源碼:

// add 方法源碼
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
  • 假設(shè) new 了一個(gè)數(shù)組,當(dāng)前容量為 0,size 當(dāng)然也為 0。這時(shí)調(diào)用 add 方法進(jìn)入到 ensureCapacityInternal(size + 1); 該方法源碼如下:
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
  • 該方法中的參數(shù) minCapacity 傳入的值為 size+1 也就是 1,接著,再進(jìn)入到 calculateCapacity(elementData, minCapacity) 里面:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
  • 上文說過,elementData 就是存放元素的數(shù)組,當(dāng)前容量為 0,if 條件成立,返回默認(rèn)容量 DEFAULT_CAPACITY 也就是 10。這個(gè)值作為參數(shù),又傳入 ensureExplicitCapacity() 方法中,進(jìn)入該方法查看源碼:
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
  • 先不要管 modCount 這個(gè)變量。因?yàn)?elementData 數(shù)組長度為 0,所以 if 條件成立,調(diào)用 grow 方法(重點(diǎn)),再次進(jìn)入到 grow 方法的源碼中:
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
  • 這個(gè)方法先聲明了一個(gè) oldCapacity 變量,將數(shù)組長度賦給它,其值為 0;又聲明了一個(gè) newCapacity 變量,其值為 oldCapacity+ 一個(gè)增量,可以發(fā)現(xiàn)這個(gè)增量,是和原數(shù)組長度有關(guān)的量,當(dāng)然在這里也為 0。第一個(gè) if 條件滿足,newCapacity 的值為 10(這就是默認(rèn)的容量)。第二個(gè) if 條件不成立,也可以不用注意,因?yàn)?MAX_ARRAY_SIZE 的定義如下:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • 這個(gè)值太大了,以至于第二個(gè) if 條件沒有了解的必要。
  • 最后一句話,就是為 elementData 數(shù)組賦予了新的長度,Arrays.copyOf() 方法,返回的數(shù)組,是新的數(shù)組對象,原數(shù)組對象不會(huì)改變,該拷貝不會(huì)影響原來的數(shù)組。copyOf() 的第二個(gè)自變量,指定要建立的新數(shù)組長度,如果新數(shù)組的長度,超過原數(shù)組的長度,則保留數(shù)組默認(rèn)值。
  • 這時(shí)候再回到 add 的方法中,接著就向下執(zhí)行 elementData[size++] = e; 。
  • 當(dāng)數(shù)組長度為 10 的時(shí)候,通過源碼可知,每次擴(kuò)容為原來的 1.5 倍。
  1. Vector(使用不多)
  • 數(shù)組結(jié)構(gòu) 實(shí)現(xiàn),查詢快、增刪慢;
  • JDK1.0 版本,運(yùn)行效率慢、線程安全。
package com.base.demo10;

import java.util.Enumeration;
import java.util.Vector;

/*
 * Vector 集合的使用
 * 存儲(chǔ)結(jié)構(gòu):數(shù)組
 * */
public class Demo01 {
    public static void main(String[] args) {
        // 創(chuàng)建對象
        Vector vector = new Vector<>();
        // 1.添加元素
        vector.add("vector01");
        vector.add("vector02");
        vector.add("vector03");
        System.out.println("元素個(gè)數(shù):" + vector.size());
        System.out.println(vector.toString());
        // 2.刪除
        // vector.remove(0);
        // vector.remove("vector02");
        // vector.clear(); // 清空
        // System.out.println("刪除后個(gè)數(shù):"+vector.size());
        // 3.遍歷 (for、增強(qiáng)for、枚舉器)
        // 枚舉器遍歷
        Enumeration en = vector.elements();
        while (en.hasMoreElements()) {
            String o = (String) en.nextElement();
            System.out.println(o);
        }
        // 4.判斷
        System.out.println(vector.contains("vector01"));
        System.out.println(vector.isEmpty());
        // 5.vector 其它方法
        System.out.println(vector.firstElement());  // 獲取第一個(gè)元素
        System.out.println(vector.lastElement());   // 獲取最后一個(gè)元素
        System.out.println(vector.elementAt(0));    // 按下標(biāo)獲取元素
        System.out.println(vector.get(0));  // 按下標(biāo)獲取元素
    }
}
  1. LinkedList(雙向鏈表)
  • 鏈表結(jié)構(gòu) 實(shí)現(xiàn),增刪快,查詢慢。
package com.base.demo10;

import com.base.demo09.Student;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

/*
 * LinkedList 的使用
 * 存儲(chǔ)結(jié)構(gòu):雙向鏈表
 * */
public class Demo02 {
    public static void main(String[] args) {
        // 創(chuàng)建集合
        LinkedList linkedList = new LinkedList<>();
        // 1.添加元素
        // 創(chuàng)建學(xué)生類對象
        Student s1 = new Student("學(xué)生1", 20);
        Student s2 = new Student("學(xué)生2", 21);
        Student s3 = new Student("學(xué)生3", 19);
        linkedList.add(s1);
        linkedList.add(s2);
        linkedList.add(s3);
        System.out.println("元素個(gè)數(shù):" + linkedList.size());
        System.out.println(linkedList.toString());
        // 2.刪除元素
        // linkedList.remove(s1);
        // linkedList.remove(new Student("學(xué)生2", 21));  // 需重寫equals()方法
        // System.out.println("刪除后:" + linkedList.size());
        // 3.遍歷
        // 3.1 for遍歷
        System.out.println("--------------3.1 for遍歷---------------");
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.println(linkedList.get(i));
        }
        // 3.2 增強(qiáng) for 遍歷
        System.out.println("--------------3.2 增強(qiáng) for 遍歷---------------");
        for (Object o : linkedList) {
            Student s = (Student) o;    // 類型轉(zhuǎn)換
            System.out.println(s.toString());
        }
        // 3.3 迭代器遍歷
        System.out.println("--------------3.3 迭代器遍歷---------------");
        Iterator it = linkedList.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        // 3.4 列表迭代器遍歷
        System.out.println("--------------3.4 列表迭代器遍歷---------------");
        ListIterator lit = linkedList.listIterator();
        while (lit.hasNext()) {
            System.out.println(lit.next());
        }
        // 3.5 列表迭代器遍歷(反向) 需集合指針指向末尾
        System.out.println("--------------3.5 列表迭代器遍歷(反向)-------------");
        while (lit.hasPrevious()) {
            System.out.println(lit.previous());
        }
        // 4.判斷
        System.out.println(linkedList.contains(s1));
        System.out.println(linkedList.isEmpty());
        // 5.獲取位置
        System.out.println(linkedList.indexOf(s3));
    }
}

LinkedList 源碼分析

  • LinkedList 三個(gè)屬性:
    • 鏈表大?。?code>transient int size = 0;
    • (指向)第一個(gè)結(jié)點(diǎn)(頭結(jié)點(diǎn)): transient Node<E> first;
    • (指向)最后一個(gè)結(jié)點(diǎn)(尾結(jié)點(diǎn)):transient Node<E> last;
  • Node 類代碼:
private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
  • item 存放的是實(shí)際數(shù)據(jù);next 指向下一個(gè)結(jié)點(diǎn),prev 指向上一個(gè)結(jié)點(diǎn)。
  • Node 帶參構(gòu)造方法的三個(gè)參數(shù),分別是前一個(gè)結(jié)點(diǎn)、存儲(chǔ)的數(shù)據(jù)、后一個(gè)結(jié)點(diǎn),調(diào)用這個(gè)構(gòu)造方法時(shí),將它們賦值給當(dāng)前對象。
  • LinkedList 添加元素,看 add 方法:
public boolean add(E e) {
    linkLast(e);
    return true;
}
  • linkLast 方法源碼(重點(diǎn))
void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
  • 假設(shè)剛開始 new 了一個(gè) LinkedList 對象,first 和 last 屬性都為空,調(diào)用 add 進(jìn)入到 linkLast 方法。
  • 首先創(chuàng)建一個(gè) Node 變量 l 將 last (此時(shí)為空)賦給它,然后 new 一個(gè) newNode 變量存儲(chǔ)數(shù)據(jù),并且它的前驅(qū)指向 l,后繼指向 null;再把 last 指向 newNode。如下圖所示:
  • 如果滿足 if 條件,說明這是添加的第一個(gè)結(jié)點(diǎn),將 first 指向 newNode:
  • 至此,LinkedList 對象的第一個(gè)數(shù)據(jù)添加完畢。假設(shè)需要再添加一個(gè)數(shù)據(jù),可以再來走一遍,圖示如下:

ArrayList 和 LinkedList 區(qū)別

  • ArrayList:必須開辟連續(xù)空間,查詢快,增刪慢。
  • LinkedList:無需開辟連續(xù)空間,查詢慢,增刪快。

六、泛型概述

  • Java 泛型是 JDK1.5 中引入的一個(gè)新特性,其本質(zhì)是 參數(shù)化類型,把類型作為參數(shù)傳遞。
  • 常見形式有 泛型類、泛型接口、泛型方法
  • 語法:<T,…> T 稱為類型占位符,表示一種引用類型。
  • 好處:
    • 提高代碼的重用性。
    • 防止類型轉(zhuǎn)換異常,提高代碼的安全性。
  1. 泛型類
/**
 * 泛型類
 * 語法:類名<T>
 * T 是類型占位符,表示一種引用類型,編寫多個(gè)使用逗號隔開 <T,E,K...>
 */
public class MyGeneric<T>{
    // 1.創(chuàng)建泛型變量
    //不能使用 new 來創(chuàng)建,因?yàn)榉盒褪遣淮_定的類型,也可能擁有私密的構(gòu)造方法。
    T t;
    // 2.泛型作為方法的參數(shù)
    public void show(T t) {
        System.out.println(t);
    }
    // 3.泛型作為方法的返回值
    public T getT() {
        return t;
    }
}
  • 泛型類使用
package com.base.demo10;

/**
 * 注意:
 * 1.泛型只能使用引用類型
 * 2.不同泛型類型的對象,不能相互賦值
 */
public class TestGeneric {
    public static void main(String[] args) {
        // 使用泛型類創(chuàng)建對象
//        MyGeneric<String> myGeneric = new MyGeneric<String>();    后面的類型可省略
        MyGeneric<String> myGeneric = new MyGeneric<>();
        myGeneric.t = "Hello";
        myGeneric.show("hello");
        String s = myGeneric.getT();
        System.out.println(s);

        MyGeneric<Integer> myGeneric1 = new MyGeneric<>();
        myGeneric1.t = 100;
        myGeneric1.show(200);
        Integer integer = myGeneric1.getT();
        System.out.println(integer);
    }
}
  1. 泛型接口
  • 接口:
package com.base.demo10;

/*
 * 泛型接口
 * 語法:接口名<T>
 * 注意:不能創(chuàng)建泛型靜態(tài)常量
 * */
public interface MyInterface<T> {
    String name = "Liu";

    T server(T t);
}
  • 接口實(shí)現(xiàn)類:實(shí)現(xiàn)接口時(shí),確定泛型類
package com.base.demo10;

/**
 * 實(shí)現(xiàn)接口時(shí),確定泛型類
 */
public class MyInterfaceImpl implements MyInterface<String> {
    @Override
    public String server(String t) {
        System.out.println(t);
        return t;
    }
}
  • 測試類
//測試
MyInterfaceImpl impl = new MyInterfaceImpl();
impl.server("泛型接口測試");  // 泛型接口測試
  • 接口實(shí)現(xiàn)類:實(shí)現(xiàn)接口時(shí),不確定泛型類
package com.base.demo10;

/**
 * 實(shí)現(xiàn)接口時(shí),不確定泛型類
 */
public class MyInterfaceImpl2<T> implements MyInterface<T> {

    @Override
    public T server(T t) {
        System.out.println(t);
        return t;
    }
}
  • 測試類
//測試
MyInterfaceImpl2<Integer> impl2 = new MyInterfaceImpl2<>();
impl2.server(2000);  // 2000
  1. 泛型方法
  • 方法類
package com.base.demo10;

/**
 * 泛型方法
 * 語法:<T> 返回類型
 */
public class MyGenericMethod {
    // 泛型方法
    public <T> T show(T t) {
        System.out.println("泛型方法" + t);
        return t;
    }
}
  • 測試類:調(diào)用泛型方法時(shí),類型不需要傳遞,是由傳遞的數(shù)據(jù)來決定
//測試類:泛型方法
MyGenericMethod myGenericMethod = new MyGenericMethod();
myGenericMethod.show("字符串");    // String
myGenericMethod.show(200);  // Integer
myGenericMethod.show(3.14); // Double
  1. 泛型集合
  • 概念:參數(shù)化類型、類型安全的集合,強(qiáng)制集合元素的類型必須一致。
  • 特點(diǎn):
    • 編譯時(shí)即可檢查,而非運(yùn)行時(shí)拋出異常。
    • 訪問時(shí),不必類型轉(zhuǎn)換(拆箱)。
    • 不同泛型之間,引用不能相互賦值,泛型不存在多態(tài)。
  • 之前在創(chuàng)建 LinkedList 類型對象的時(shí)候,并沒有使用泛型,查看源碼會(huì)發(fā)現(xiàn):
public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    // ......
}
  • 它是一個(gè)泛型類,而之前使用的時(shí)候并沒有傳遞,說明 java 語法是允許的。這個(gè)時(shí)候傳遞的類型是 Object 類,雖然它是所有類的父類,可以存儲(chǔ)任意的類型,但是在遍歷、獲取元素時(shí),需要原來的類型,就要進(jìn)行強(qiáng)制轉(zhuǎn)換。這個(gè)時(shí)候就會(huì)出現(xiàn)一些問題,假如往鏈表里存儲(chǔ)了許多不同類型的數(shù)據(jù),在強(qiáng)轉(zhuǎn)的時(shí)候就要判斷每一個(gè)原來的類型,這樣就很容易出現(xiàn)錯(cuò)誤。
  • 實(shí)例:
package com.base.demo10;

import com.base.demo09.Student;

import java.util.ArrayList;
import java.util.Iterator;

public class Demo03 {
    public static void main(String[] args) {
//        ArrayList<String> arrayList = new ArrayList<String>();    后面的類型可不寫
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("字符串1");
        arrayList.add("字符串2");
//        arrayList.add(100);   報(bào)錯(cuò):不能添加不同類型
//        arrayList.add(3.14);
        for (String s : arrayList) {    // 直接為 String 類型,不需類型轉(zhuǎn)換
            System.out.println(s);
        }

        ArrayList<Student> arrayList1 = new ArrayList<>();
        Student s1 = new Student("學(xué)生1", 20);
        Student s2 = new Student("學(xué)生2", 21);
        Student s3 = new Student("學(xué)生3", 19);
        arrayList1.add(s1);
        arrayList1.add(s2);
        arrayList1.add(s3);
//        arrayList1.add(100); 報(bào)錯(cuò):不能添加 Student 之外的類型
        Iterator<Student> it = arrayList1.iterator();
        while (it.hasNext()) {
            Student s = it.next();  // 無需強(qiáng)制轉(zhuǎn)換
            System.out.println(s.toString());
        }
//        arrayList = arrayList1;   報(bào)錯(cuò):不同類型之間的泛型,不能相互賦值
    }
}

七、Set 集合概述

Set 子接口

  • 特點(diǎn):無序、無下標(biāo)、元素不可重復(fù)。
  • 方法:全部繼承自 Collection 中的方法。
package com.base.demo11;

import java.util.HashSet;
import java.util.Iterator;

/**
 * 測試Set接口的使用
 * 特點(diǎn):1.無序,沒有下標(biāo);2.重復(fù)
 * 1.添加數(shù)據(jù)(無序)
 * 2.刪除數(shù)據(jù)
 * 3.遍歷【重點(diǎn)】
 * 4.判斷
 */
public class Demo01 {
    public static void main(String[] args) {
        // 創(chuàng)建集合
        HashSet<String> set = new HashSet<>();
        // 1.添加數(shù)據(jù)(無序)
        set.add("字符串3");
        set.add("字符串1");
        set.add("字符串2");
//        set.add("字符串2");  不能重復(fù)
        System.out.println("數(shù)據(jù)個(gè)數(shù):" + set.size());
        System.out.println(set.toString());
        // 2.刪除數(shù)據(jù)
//        set.remove("字符串1"); // 無下標(biāo),不能通過下標(biāo)刪除
//        System.out.println(set.toString());
        // 3.遍歷 (兩種:增強(qiáng) for、迭代器)
        // 3.1增強(qiáng) for 遍歷
        System.out.println("------------3.1增強(qiáng) for 遍歷----------");
        for (String s : set) {
            System.out.println(s);
        }
        // 3.2迭代器遍歷
        System.out.println("------------3.2迭代器遍歷----------");
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        // 4.判斷
        System.out.println(set.contains("字符串1"));
        System.out.println(set.isEmpty());
    }
}

八、Set 實(shí)現(xiàn)類

  1. HashSet【重點(diǎn)】
  • 基于 HashCode 計(jì)算元素存放位置。
  • 存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
  • 存儲(chǔ)過程(重復(fù)依據(jù)):當(dāng)存入元素的哈希碼相同時(shí),會(huì)調(diào)用 equals 進(jìn)行確認(rèn),如結(jié)果為 true,則拒絕后者存入(不同對象,相同屬性值,需要重寫 hashCode 和 equals 方法來實(shí)現(xiàn))。
  • 實(shí)例 1:
package com.base.demo11;

import java.util.HashSet;
import java.util.Iterator;

/**
 * HashSet集合的使用
 * 存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
 * 1.添加元素(無序)
 * 2.刪除元素
 * 3.遍歷
 * 4.判斷
 */
public class Demo02 {
    public static void main(String[] args) {
        // 創(chuàng)建集合
        // JDK 1.8 后,后面的泛型類型,可以不用寫
        HashSet<String> hashSet = new HashSet<>();  
        // 1.添加元素
        hashSet.add("字符串1");
        hashSet.add("字符串2");
        hashSet.add("字符串3");
        hashSet.add("字符串4");
//        hashSet.add("字符串4"); 不能添加重復(fù)元素
        System.out.println("元素個(gè)數(shù):" + hashSet.size());
        System.out.println(hashSet.toString()); // 無序
        // 2.刪除元素
//        hashSet.remove("字符串2");
//        System.out.println("刪除后:"+hashSet.size());
        // 3.遍歷(兩種:增強(qiáng)for、迭代器)
        // 3.1增強(qiáng) for
        System.out.println("-----------3.1增強(qiáng) for----------");
        for (String s : hashSet) {
            System.out.println(s);
        }
        // 3.2迭代器
        System.out.println("-----------3.2迭代器----------");
        Iterator<String> it = hashSet.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        // 4.判斷
        System.out.println(hashSet.contains("字符串3"));
        System.out.println(hashSet.isEmpty());
    }
}
  • 創(chuàng)建 Person 類
package com.base.demo11;

// 創(chuàng)建 Person 類
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    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;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 實(shí)例 2:
package com.base.demo11;

import java.util.HashSet;
import java.util.Iterator;

/**
 * HashSet 集合的使用
 * 存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
 * 1.添加元素(無序)
 * 2.刪除元素
 * 3.遍歷
 * 4.判斷
 */
public class Demo03 {
    public static void main(String[] args) {
        // 創(chuàng)建集合
        HashSet<Person> persons = new HashSet<>();
        // 創(chuàng)建 Person 對象
        Person p1 = new Person("張三", 25);
        Person p2 = new Person("李四", 23);
        Person p3 = new Person("王五", 26);
        // 1.添加元素
        persons.add(p1);
        persons.add(p2);
        persons.add(p3);
//        person.add(p3); 重復(fù),不能添加
        // 不同對象,相同屬性值,需要重寫 hashCode 和 equals 方法
        // 來實(shí)現(xiàn)(否則不視為重復(fù),可以添加)
//        person.add(new Person("王五", 26));
        System.out.println("元素個(gè)數(shù):" + persons.size());
        System.out.println(persons.toString());
        // 2.刪除元素
//        persons.remove(p1);
        // 未重寫 hashCode 和 equals 方法時(shí),不能刪除
//        person.remove(new Person("張三", 25));

        System.out.println("刪除后:" + persons.size());
        // 3.遍歷
        // 3.1增強(qiáng)for
        System.out.println("----------3.1增強(qiáng)for-----------");
        for (Person person : persons) {
            System.out.println(person.toString());
        }
        // 3.2迭代器
        System.out.println("----------3.2迭代器-----------");
        Iterator<Person> it = persons.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        // 4.判斷
        System.out.println(persons.contains(p1));
        // 與 p1 屬性值相同,未重寫 hashCode 和 equals 方法時(shí),結(jié)果為 false
        System.out.println(persons.contains(new Person("張三", 25)));
        System.out.println(persons.isEmpty());
    }
}

注:HashSet 存儲(chǔ)過程:(重復(fù)依據(jù))

  • 第一步:根據(jù) hashCode 計(jì)算保存的位置,如果位置為空,則直接保存,否則執(zhí)行第二步。
  • 第二步:執(zhí)行 equals 方法,如果方法返回 true,則認(rèn)為是重復(fù),拒絕存儲(chǔ),否則形成鏈表。
  • HashSet 存儲(chǔ)過程,實(shí)際上就是,判斷重復(fù)的依據(jù),要實(shí)現(xiàn) 相同屬性值,視為同一個(gè)對象,則不能添加 的問題,需重寫 hashCode 和 equals 代碼:
// IDEA 快捷生成 Alt + Insert
@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

HashSet 補(bǔ)充:hashCode 方法里,使用數(shù)字 31 的原因:

  1. 31 是一個(gè)質(zhì)數(shù)(只能被 1 和它自身整除),這樣的數(shù)字在計(jì)算時(shí),可以盡量減少散列沖突(hashCode 值盡量不一樣)。
  2. 可以提高執(zhí)行效率,31 * i = ( i << 5 ) - i。31 乘以一個(gè)數(shù),可以轉(zhuǎn)換成移位操作,這樣計(jì)算更快。

2.TreeSet

  • 基于排序順序,實(shí)現(xiàn)不重復(fù)。
  • 存儲(chǔ)結(jié)構(gòu):紅黑樹。
  • 實(shí)現(xiàn)了 SortedSet 接口,對集合元素 自動(dòng)排序
  • 元素對象的類型,必須實(shí)現(xiàn) Comparable 接口,指定排序規(guī)則。
  • 通過 CompareTo 方法,確定是否為 重復(fù)元素
  • 實(shí)例 1:
package com.base.demo11;

import java.util.Iterator;
import java.util.TreeSet;

/**
 * TreeSet 使用
 * 存儲(chǔ)結(jié)構(gòu):紅黑樹
 */
public class Demo04 {
    public static void main(String[] args) {
        // 創(chuàng)建集合
        TreeSet<String> treeSet = new TreeSet<>();
        // 1.添加元素(自動(dòng)排序)
        treeSet.add("xyz");
        treeSet.add("abc");
        treeSet.add("hello");
//        treeSet.add("xyz"); 不能重復(fù)
        System.out.println("元素個(gè)數(shù):" + treeSet.size());
        System.out.println(treeSet.toString()); // 結(jié)果自動(dòng)排序
        // 2.刪除元素
//        treeSet.remove("hello");
//        System.out.println("刪除后:" + treeSet.size());
        // 3.遍歷(增強(qiáng)for、迭代器)
        // 3.1增強(qiáng)for
        System.out.println("-----------3.1增強(qiáng)for----------");
        for (String s : treeSet) {
            System.out.println(s);
        }
        // 3.2迭代器
        System.out.println("-----------3.2迭代器----------");
        Iterator<String> it = treeSet.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
        // 4.判斷
        System.out.println(treeSet.contains("xyz"));
        System.out.println(treeSet.isEmpty());
    }
}
  • 實(shí)例 2:用之前的 Person 類,保存數(shù)據(jù)(元素類必須實(shí)現(xiàn) Comparable 接口,及內(nèi)部抽象方法,compareTo 方法返回 0,認(rèn)為是重復(fù)元素)
package com.base.demo11;

import java.util.TreeSet;

/**
 * 使用 TreeSet 保存數(shù)據(jù)
 * 存儲(chǔ)結(jié)構(gòu):紅黑樹
 * 要求:元素類必須實(shí)現(xiàn)Comparable接口,compareTo方法返回 0,認(rèn)為是重復(fù)元素
 */
public class Demo05 {
    public static void main(String[] args) {
        // 創(chuàng)建集合
        TreeSet<Person> persons = new TreeSet<>();
        // 創(chuàng)建 Person 對象
        Person p1 = new Person("張三", 25);
        Person p2 = new Person("李四", 23);
        Person p3 = new Person("王五", 26);
        Person p4 = new Person("王五", 23);
//        Person p5 = new Person("王五", 23);   相同屬性值 不能添加
        // 1.添加元素(元素類必須實(shí)現(xiàn) Comparable 接口,及內(nèi)部抽象方法)
        persons.add(p1);
        persons.add(p2);
        persons.add(p3);
        // 位置在 p3 之前:通過重寫 compareTo 方法排序
        persons.add(p4);
        System.out.println("元素個(gè)數(shù):" + persons.size());
        System.out.println(persons.toString());
        // 2.刪除元素
        persons.remove(p1);
        // 可以刪除:重寫 compareTo 方法后,比較的是屬性值是否相同
        persons.remove(new Person("李四", 23));
        System.out.println("刪除后:" + persons.size());
        // 3.遍歷(略)
        // 4.判斷
        System.out.println(persons.contains(p3));
        // true:重寫 compareTo 方法后,比較的是屬性值是否相同
        System.out.println(persons.contains(new Person("王五", 26)));
    }
}
  • 查看 Comparable 接口源碼,只有 compareTo 抽象方法,在 Person 類中實(shí)現(xiàn)它:
public class Person implements Comparable<Person> {
    // 內(nèi)部代碼 略......
    @Override
    public int compareTo(Person o) {
        // 先比較 name(相同為 0)
        int n1 = this.name.compareTo(o.getName());
        // 再比較 age
        int n2 = this.age - o.getAge();
        // name 相同的話,返回 n2,否則返回 n1
        return n1 == 0 ? n2 : n1;
    }
}

Comparator:實(shí)現(xiàn)定制比較(比較器)

  • 除了實(shí)現(xiàn) Comparable 接口里的比較方法,TreeSet 也提供了一個(gè)帶比較器 Comparator 的構(gòu)造方法,使用匿名內(nèi)部類來實(shí)現(xiàn)它:
package com.base.demo11;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * TreeSet的使用
 * Comparator:實(shí)現(xiàn)定制比較(比較器)
 */
public class Demo06 {
    public static void main(String[] args) {
        // 創(chuàng)建集合,并通過匿名內(nèi)部類,指定比較規(guī)則(Comparator)
        TreeSet<Person> persons = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                // 先比較 age
                int n1 = o1.getAge() - o2.getAge();
                // 再比較 name
                int n2 = o1.getName().compareTo(o2.getName());
                return n1 == 0 ? n2 : n1;
            }
        });
        // 創(chuàng)建 Person 對象
        Person p1 = new Person("張三", 25);
        Person p2 = new Person("李四", 23);
        Person p3 = new Person("王五", 26);
        Person p4 = new Person("王五", 23);
        // 添加元素(元素類必須實(shí)現(xiàn) Comparable 接口,及內(nèi)部抽象方法)
        persons.add(p1);
        persons.add(p2);
        persons.add(p3);
        persons.add(p4);
        System.out.println("元素個(gè)數(shù):" + persons.size());
        // 結(jié)果為按 age 排序
        System.out.println(persons.toString());
    }
}
  • 案例:
package com.base.demo11;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * 要求:使用 TreeSet集合實(shí)現(xiàn)字符串按照長度進(jìn)行排序
 * helloworld zhangsan lisi wangwu beijing xian nanjing
 * Comparator 接口實(shí)現(xiàn)定制比較
 */
public class Demo07 {
    public static void main(String[] args) {
        // 創(chuàng)建集合,并通過匿名內(nèi)部類,指定比較規(guī)則
        TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                // 先比較字符串長度
                int n1 = o1.length() - o2.length();
                // 再比較字符串
                int n2 = o1.compareTo(o2);
                return n1 == 0 ? n2 : n1;
            }
        });
        // 添加數(shù)據(jù)
        treeSet.add("helloworld");
        treeSet.add("zhangsan");
        treeSet.add("lisi");
        treeSet.add("wangwu");
        treeSet.add("beijing");
        treeSet.add("xian");
        treeSet.add("nanjing");
        System.out.println(treeSet.toString());
        // [lisi, xian, wangwu, beijing, nanjing, zhangsan, helloworld]
    }
}

九、Map 體系集合

  • Map 接口的特點(diǎn):
    • 用于存儲(chǔ)任意鍵值對(Key-Value)。
    • 鍵:無序、無下標(biāo)、不允許重復(fù)(唯一)。
    • 值:無序、無下標(biāo)、允許重復(fù)。

Map 集合概述

  • 特點(diǎn):存儲(chǔ) 鍵值對Key-Value),無序、無下標(biāo),鍵不可重復(fù)。
  • 方法:
類型 方法名 說明
V put(K key,V value) 將對象存入到集合中,關(guān)聯(lián)鍵值。key 重復(fù)則覆蓋原值。
V get(Object key) 根據(jù)鍵獲取相應(yīng)的值。
Set<K> keySet() 返回所有的 key
Collection<V> values() 返回包含所有值的 Collection 集合。
Set<Map.Entry<K,V>> entrySet() 鍵值匹配的 set 集合
  • 實(shí)例:
package com.base.demo12;

import java.util.HashMap;
import java.util.Map;

/**
 * Map接口的使用
 * 特點(diǎn):1.存儲(chǔ)鍵值對 2.鍵不能重復(fù),值可以重復(fù) 3.無序
 */
public class Demo01 {
    public static void main(String[] args) {
        // 創(chuàng)建 Map 集合
        HashMap<String, String> map = new HashMap<>();
        // 1.添加元素(無序)
        map.put("cn", "中國");
        map.put("uk", "英國");
        map.put("usa", "美國");
//        map.put("cn", "China"); 相同鍵名,可以添加,但個(gè)數(shù)不增加,值會(huì)替換 China
        System.out.println("元素個(gè)數(shù):" + map.size());
        System.out.println(map.toString()); // {usa=美國, uk=英國, cn=中國}
        // 2.刪除元素
//        map.remove("usa");
//        System.out.println("刪除后:" + map.size());
        // 3.遍歷:(1.keySet() 2.entrySet() 效率高)
        // 3.1 keySet(): 返回所有的 key(Set 集合)
        System.out.println("------------3.1 keySet()-----------");
//        Set<String> keySet = map.keySet();
//        for (String key : keySet) {
        // 簡化
        for (String key : map.keySet()) {
            // map.get(key):通過鍵獲取值
            System.out.println(key + ":" + map.get(key));
        }
        // 3.2 entrySet():返回 Set 集合(Map.Entry)的鍵值對 K-V
        System.out.println("------------3.2 entrySet()-----------");
//        Set<Map.Entry<String, String>> entries = map.entrySet();
//        for (Map.Entry<String, String> entry : entries) {
        // 簡化
        for (Map.Entry<String, String> entry : map.entrySet()) {
            // getKey():獲取鍵     getValue():獲取值
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        // 4.判斷
        System.out.println(map.containsKey("cn"));
        System.out.println(map.containsValue("中國"));
    }
}

十、Map 集合的實(shí)現(xiàn)類

1. HashMap【重點(diǎn)】

  • JDK 1.2 版本,線程不安全,運(yùn)行效率快;允許用 null 作為 key 或是 value。
  • 存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
  • 使用 key 的 hashCode 和 equals 作為 重復(fù)依據(jù)(去重,需重寫兩個(gè)方法)
  • 實(shí)例:創(chuàng)建 Student 類
package com.base.demo12;

// 學(xué)生類
public class Student {
    private String name;
    private int stuNo;

    public Student() {
    }

    public Student(String name, int stuNo) {
        this.name = name;
        this.stuNo = stuNo;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getStuNo() {
        return stuNo;
    }

    public void setStuNo(int stuNo) {
        this.stuNo = stuNo;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", stuNo=" + stuNo +
                '}';
    }
}
  • 實(shí)例:HashMap 的使用
package com.base.demo12;

import java.util.HashMap;
import java.util.Map;

/**
 * HashMap 的使用
 * 存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
 */
public class Demo02 {
    public static void main(String[] args) {
        // 創(chuàng)建集合
        HashMap<Student, String> students = new HashMap<>();
        // 創(chuàng)建 Student 對象
        Student s1 = new Student("學(xué)生1", 100);
        Student s2 = new Student("學(xué)生2", 101);
        Student s3 = new Student("學(xué)生3", 102);
        // 1.添加元素(無序)
        students.put(s1, "北京");
        students.put(s2, "上海");
        students.put(s3, "杭州");
        // 添加失敗,但會(huì)更新值
//        students.put(s3, "南京");
        // 添加成功:與 s3 屬性值相同,但不是同一對象
        // 使用 key 的 hashCode 和 equals 作為重復(fù)依據(jù)(去重,需重寫兩個(gè)方法)
//        students.put(new Student("學(xué)生3", 102), "杭州");   重寫兩方法后,無法添加
        System.out.println(students.size());
        System.out.println(students.toString());
        // 2.刪除元素
//        students.remove(s1);
//        System.out.println("刪除后:"+students.size());
        // 3.遍歷(兩種:keySet、entrySet)
        // 3.1 keySet() 遍歷 (獲取 key)
        System.out.println("-----------3.1 keySet() 遍歷-----------");
        for (Student key : students.keySet()) {
            System.out.println(key.toString() + ":" + students.get(key));
        }
        // 3.2 entrySet() 遍歷 (獲取 K-V 鍵值對)
        System.out.println("-----------3.2 entrySet() 遍歷-----------");
        for (Map.Entry<Student, String> entry : students.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        // 4.判斷
        System.out.println(students.containsKey(s1));
        // 重寫方法后,結(jié)果為 true
        System.out.println(students.containsKey(new Student("學(xué)生1", 100)));
        System.out.println(students.containsValue("北京"));
    }
}
  • 注:和 HashSet 類似,重復(fù)依據(jù)是 hashCode 和 equals 方法,重寫即可
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;

        Student student = (Student) o;

        if (stuNo != student.stuNo) return false;
        return Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + stuNo;
        return result;
    }

HashMap 源碼分析

  1. 默認(rèn)初始化容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  2. 數(shù)組最大容量:static final int MAXIMUM_CAPACITY = 1 << 30;
  3. 默認(rèn)加載因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;
  4. 鏈表調(diào)整為紅黑樹的鏈表長度閾值(JDK 1.8):static final int TREEIFY_THRESHOLD = 8;
  5. 紅黑樹調(diào)整為鏈表的鏈表長度閾值(JDK 1.8):static final int UNTREEIFY_THRESHOLD = 6;
  6. 鏈表調(diào)整為紅黑樹的數(shù)組最小閾值(JDK 1.8):static final int MIN_TREEIFY_CAPACITY = 64;
  7. HashMap 存儲(chǔ)的數(shù)組:transient Node<K,V>[] table;
  8. HashMap 存儲(chǔ)的元素個(gè)數(shù):transient int size;

默認(rèn)加載因子

  • 判斷數(shù)組是否擴(kuò)容的一個(gè)因子。假如數(shù)組容量為100,如果 HashMap 的存儲(chǔ)元素個(gè)數(shù)超過了 100*0.75=75,那么就會(huì)進(jìn)行擴(kuò)容。

鏈表調(diào)整為紅黑樹的鏈表長度閾值

  • 假設(shè)在數(shù)組中,下標(biāo)為 3 的位置已經(jīng)存儲(chǔ)了數(shù)據(jù),當(dāng)新增數(shù)據(jù)時(shí)通過哈希碼,得到的存儲(chǔ)位置又是 3,那么就會(huì)在該位置形成一個(gè)鏈表,當(dāng)鏈表過長時(shí),就會(huì)轉(zhuǎn)換成紅黑樹,以提高執(zhí)行效率,這個(gè)閾值,就是鏈表轉(zhuǎn)換成紅黑樹的最短鏈表長度;

紅黑樹調(diào)整為鏈表的鏈表長度閾值

  • 當(dāng)紅黑樹的元素個(gè)數(shù),小于該閾值時(shí),就會(huì)轉(zhuǎn)換成鏈表。
  • 注:并不是只要鏈表長度大于 8,就可以轉(zhuǎn)換成紅黑樹,需同時(shí)具備(數(shù)組的容量,大于等于 64),才會(huì)進(jìn)行轉(zhuǎn)換。

HashMap 的數(shù)組 table

  • 存儲(chǔ)的是 Node<K,V> 類型,有一對鍵值,和指向 next 的指針(部分源碼):
static class Node<K,V> implements Map.Entry<K,V> {
      final K key;
      V value;
      Node<K,V> next;
  }
  • 之前的代碼中在 new 對象時(shí)調(diào)用的是 HashMap 的無參構(gòu)造方法,進(jìn)入到該構(gòu)造方法的源碼查看一下:
public HashMap() {
      this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  }
  • 只賦值了一個(gè)默認(rèn)加載因子;而且,源碼中 table 和 size 都沒有賦予初始值,說明剛創(chuàng)建的 HashMap 對象沒有分配容量,并不擁有默認(rèn)的 16 個(gè)空間大小,這樣做的目的是為了節(jié)約空間,此時(shí) table 為 null,size 為 0。
  • 當(dāng)添加元素時(shí),調(diào)用 put 方法:
public V put(K key, V value) {
      return putVal(hash(key), key, value, false, true);
  }
  • put 方法把 key 和 value 傳給了 putVal,同時(shí)還傳入了一個(gè) hash(Key) 所返回的值,這是一個(gè)產(chǎn)生哈希值的方法,查看 putVal 方法(部分源碼):
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                    boolean evict) {
      Node<K,V>[] tab; Node<K,V> p; int n, i;
      if ((tab = table) == null || (n = tab.length) == 0)
          n = (tab = resize()).length;
      if ((p = tab[i = (n - 1) & hash]) == null)
          tab[i] = newNode(hash, key, value, null);
      else{
          //略
      }
  }
  • 這里面創(chuàng)建了一個(gè) tab 數(shù)組,和一個(gè) Node 變量 p,第一個(gè) if 實(shí)際是判斷 table 是否為空,而我們現(xiàn)在只關(guān)注,剛創(chuàng)建 HashMap 對象時(shí)的狀態(tài),此時(shí) tab 和 table 都為空,滿足條件,執(zhí)行內(nèi)部代碼,這條代碼,其實(shí)就是把 resize() 所返回的結(jié)果賦給 tab,n 就是 tab 的長度,resize 顧名思義,就是重新調(diào)整大小。查看 resize() 源碼(部分):
final Node<K,V>[] resize() {
      Node<K,V>[] oldTab = table;
      int oldCap = (oldTab == null) ? 0 : oldTab.length;
      int oldThr = threshold;
      if (oldCap > 0);
      else if (oldThr > 0);
      else {               // zero initial threshold signifies using defaults
          newCap = DEFAULT_INITIAL_CAPACITY;
          newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
      } 
      @SuppressWarnings({"rawtypes","unchecked"})
      Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
      table = newTab;
      return newTab;
  }
  • 該方法把 table 及其長度,賦值給 oldTab 和 oldCap;threshold 是閾值的意思,此時(shí)為 0,所以前兩個(gè) if 先不管,最后 else 里 newCap 的值為,默認(rèn)初始化容量 16;往下創(chuàng)建了一個(gè) newCap 大小的數(shù)組,并將其賦給了 table,剛創(chuàng)建的 HashMap 對象,就在這里獲得了初始容量?;氐?putVal 方法,第二個(gè) if 就是根據(jù)哈希碼得到的 tab 中的一個(gè)位置是否為空,為空時(shí),便直接添加元素,此時(shí)數(shù)組中無元素所以直接添加。至此 HashMap 對象就完成了第一個(gè)元素的添加。當(dāng)添加的元素超過 16*0.75=12 時(shí),就會(huì)進(jìn)行擴(kuò)容:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict){
      if (++size > threshold)
          resize();
  }
  • 擴(kuò)容的代碼如下(部分):
final Node<K,V>[] resize() {
      int oldCap = (oldTab == null) ? 0 : oldTab.length;
      int newCap;
      if (oldCap > 0) {
          if (oldCap >= MAXIMUM_CAPACITY) {//略}
          else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                   oldCap >= DEFAULT_INITIAL_CAPACITY)
      }
  }
  • 核心部分是 else if 里的移位操作,也就是說每次擴(kuò)容都是原來大小的兩倍

HashMap 源碼總結(jié)

  1. HashMap 剛創(chuàng)建時(shí),table=null,目的是節(jié)省空間,當(dāng)添加第一個(gè)元素時(shí),table 容量調(diào)整為 16;
  2. 當(dāng)元素個(gè)數(shù)大于閾值(16*0.75=12)時(shí),會(huì)進(jìn)行擴(kuò)容,擴(kuò)容后大小為原來的 2 倍。目的是減少調(diào)整元素的個(gè)數(shù);
  3. JDK 1.8:當(dāng)每個(gè)鏈表長度大于 8,且數(shù)組元素個(gè)數(shù)大于等于 64 時(shí),會(huì)調(diào)整為紅黑樹,目的是提高執(zhí)行效率;
  4. JDK 1.8:當(dāng)鏈表長度小于 6 時(shí),調(diào)整成鏈表;
  5. JDK 1.8 以前,鏈表是頭插入,JDK 1.8 以后是尾插入。

HashSet 源碼分析

  • HashSet 實(shí)際上,就是調(diào)用的 HashMap(部分源碼):
public class HashSet<E>
      extends AbstractSet<E>
      implements Set<E>, Cloneable, java.io.Serializable
  {
      private transient HashMap<E,Object> map;
      private static final Object PRESENT = new Object();
      public HashSet() {
          map = new HashMap<>();
      }
  }
  • HashSet 的存儲(chǔ)結(jié)構(gòu),就是 HashMap,那它的存儲(chǔ)方式,看 add 方法:
public boolean add(E e) {
      return map.put(e, PRESENT)==null;
  }
  • 調(diào)用的是 map 的 put 方法,把元素作為 map 的 key 傳入。
  1. HashTable(了解即可,基本不用)
  • JDK 1.0 版本,線程安全,運(yùn)行效率慢;不允許 null 作為 key 或是value。
  • 初始容量 11,加載因子 0.75。
  • 這個(gè)集合在開發(fā)過程中已經(jīng)不用了,稍微了解即可。
  1. Properties:配置文件的讀?。ê土飨嚓P(guān))查看 I/O流
  • Hashtable 的子類,要求 key 和 value 都是 String。通常用于配置文件的讀取。
  • 它繼承了 Hashtable 的方法,與流關(guān)系密切,此處不詳解。

4.TreeMap

  • 實(shí)現(xiàn)了 SortedMap 接口(是 Map 的子接口),可以對 key 自動(dòng)排序。
  • 存儲(chǔ)結(jié)構(gòu):紅黑樹
package com.base.demo12;

import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

/**
 * TreeMap 的使用
 * 存儲(chǔ)結(jié)構(gòu):紅黑樹
 */
public class Demo03 {
    public static void main(String[] args) {
        // 創(chuàng)建集合
//        TreeMap<Student, String> treeMap = new TreeMap<>();
        TreeMap<Student, String> treeMap = new TreeMap<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int n1 = o1.compareTo(o2);
                return n1;
            }
        });
        // 創(chuàng)建 Student 對象
        Student s1 = new Student("學(xué)生1", 100);
        Student s2 = new Student("學(xué)生2", 101);
        Student s3 = new Student("學(xué)生3", 102);
        // 1.添加元素
        treeMap.put(s1, "北京");
        treeMap.put(s2, "上海");
        treeMap.put(s3, "杭州");
        // 添加失敗,但會(huì)更新值
//        treeMap.put(new Student("學(xué)生3", 102), "南京");
        // 不能直接打印,需要實(shí)現(xiàn) Comparable 接口,因?yàn)榧t黑樹需要比較大小
        System.out.println("元素個(gè)數(shù):" + treeMap.size());
        System.out.println(treeMap.toString());
        // 2.刪除元素
//        treeMap.remove(s1);
        // 可刪除,需實(shí)現(xiàn) Comparable 接口
//        treeMap.remove(new Student("學(xué)生3", 102));
//        System.out.println("刪除后:"+treeMap.size());
        // 3.遍歷(兩種:keySet、entrySet)
        // 3.1 keySet() 遍歷(獲取 Key)
        System.out.println("-----------3.1 keySet() 遍歷-----------");
        for (Student key : treeMap.keySet()) {
            System.out.println(key + ":" + treeMap.get(key));
        }
        // 3.2 entrySet() 遍歷(獲取 K-V 鍵值對)
        System.out.println("-----------3.2 entrySet() 遍歷-----------");
        for (Map.Entry<Student, String> entry : treeMap.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        // 4.判斷
        System.out.println(treeMap.containsKey(s1));
        System.out.println(treeMap.containsKey(new Student("學(xué)生1", 100)));
        System.out.println(treeMap.containsValue("杭州"));
    }
}
  • 在 Student 類中實(shí)現(xiàn) Comparable 接口:
public class Student implements Comparable<Student>{
    @Override
    public int compareTo(Student o) {
        int n1 = this.stuNo - o.stuNo;
        return n1;
    }
}
  • 或用比較器:
TreeMap<Student, String> treeMap = new TreeMap<>(new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        int n1 = o1.compareTo(o2);
        return n1;
    }
});

TreeSet 源碼

  • 和 HashSet 類似(部分代碼):
public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    private transient NavigableMap<E,Object> m;
    private static final Object PRESENT = new Object();
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
}
  • TreeSet 的存儲(chǔ)結(jié)構(gòu),實(shí)際上就是 TreeMap,再來看其存儲(chǔ)方式:
public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}
  • 它的 add 方法,調(diào)用的就是 TreeMap 的 put 方法,將元素作為 key傳入到存儲(chǔ)結(jié)構(gòu)中。

十一、Collections 工具類

  • 概念:集合工具類,定義了除了存取以外的集合常用方法。
  • 方法
方法名 說明
public static void reverse(List<?> list) 反轉(zhuǎn)集合中元素的順序
public static void shuffle(List<?> list) 隨機(jī)重置集合元素的順序
public static void sort(List<T> list) 升序排序(元素類型必須實(shí)現(xiàn) Comparable 接口)
package com.base.demo12;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Collections 工具類的使用
 */
public class Demo04 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(20);
        list.add(5);
        list.add(12);
        list.add(30);
        list.add(6);
        // sort 排序
        System.out.println("---------排序前--------");
        System.out.println(list.toString());
        System.out.println("---------排序后--------");
        Collections.sort(list);
        System.out.println(list.toString());
        // binarySearch 二分查找(查找位置)
        int i = Collections.binarySearch(list, 12);
        System.out.println(i);
        // copy 復(fù)制
        ArrayList<Integer> list1 = new ArrayList<>();
        // 將新的集合長度等于源集合長度
        for (int i1 = 0; i1 < list.size(); i1++) {
            list1.add(0);
        }
        // 該方法要求目標(biāo)元素容量,大于等于源目標(biāo)
        // 后者 list 為復(fù)制的集合
        Collections.copy(list1, list);
        System.out.println("復(fù)制:" + list1.toString());
        // reverse 反轉(zhuǎn)
        Collections.reverse(list);
        System.out.println("反轉(zhuǎn)后:" + list);
        // shuffle 打亂順序
        Collections.shuffle(list);
        System.out.println("打亂順序后:" + list);

        // 補(bǔ)充:1.list 轉(zhuǎn)數(shù)組
        System.out.println("------------補(bǔ)充:1.list 轉(zhuǎn)數(shù)組-----------");
        Integer[] arr = list.toArray(new Integer[0]);
        System.out.println(arr.length);
        System.out.println(Arrays.toString(arr));

        // 補(bǔ)充:2.數(shù)組轉(zhuǎn)集合
        System.out.println("------------補(bǔ)充:2.數(shù)組轉(zhuǎn)集合-----------");
        String[] names = {"張三", "李四", "王五"};
        // 數(shù)組轉(zhuǎn)集合:受限集合,不能添加和刪除
        List<String> list2 = Arrays.asList(names);
//        list2.add("aa");  錯(cuò)誤,不能添加
//        list2.remove("張三");   錯(cuò)誤,不能刪除
        System.out.println(list2);
        // 注:基本類型轉(zhuǎn)成集合時(shí),需要修改為包裝類
        Integer[] nums = {100, 200, 300, 400, 500};
        List<Integer> list3 = Arrays.asList(nums);
        System.out.println(list3);
    }
}

總結(jié):

  • 集合的概念:
    • 對象的容器,和數(shù)組類似,定義了對多個(gè)對象進(jìn)行操作的常用方法。
  • List 集合:
    • 有序、有下標(biāo)、元素可以重復(fù)。(ArrayList、LinkedList、Vector
  • Set 集合:
    • 無序、無下標(biāo),元素不可重復(fù)。(HashSet、TreeSet)
  • Map 集合:
    • 存儲(chǔ)一對數(shù)據(jù)(鍵值對),無序、無下標(biāo),鍵不可重復(fù),值可重復(fù)。(HashMap、HashTable、TreeMap)
  • Collections:
    • 集合工具類,定義了除了存取以外的集合常用方法。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容