集合概述

集合概述


  • 概念:對(duì)象的容器,實(shí)現(xiàn)了對(duì)對(duì)象的操作。

  • 和數(shù)組的區(qū)別

    • 1.數(shù)組長度固定,集合長度不固定。
    • 2.數(shù)組可以存儲(chǔ)基本類型和引用類型,集合只能存儲(chǔ)引用類型。
  • 位置:
    java.util.*;

Collection體系


image.png

Collection父接口


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

接口 Iterator<E>

對(duì)collection進(jìn)行迭代的迭代器。

  • 迭代器允許調(diào)用者利用定義良好的語義在迭代期間從迭代器所指向的collection移除元素。

Iterator有三個(gè)方法:

  • boolean hasNext()
    如果仍有元素可以迭代,則返回 true。(換句話說,如果 next 返回了元素而不是拋出異常,則返回 true)。
    返回:如果迭代器具有多個(gè)元素,則返回 true。
  • E next()
    返回迭代的下一個(gè)元素。
  • void remove()
    從迭代器指向的 collection 中移除迭代器返回的最后一個(gè)元素(可選操作)。每次調(diào)用 next 只能調(diào)用一次此方法。如果進(jìn)行迭代時(shí)用調(diào)用此方法之外的其他方式修改了該迭代器所指向的 collection,則迭代器的行為是不確定的。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * Collection接口的使用。
 * 1.添加元素
 * 2.刪除元素
 * 3.遍歷元素
 * 4.判斷
 */
public class Demo1 {
    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);
        //2.刪除元素。
        collection.remove("榴蓮");
    //  collection.clear();清空集合中的對(duì)象。
        System.out.println("刪除元素之后"+collection.size());
        //3.遍歷元素。(重點(diǎn))
        //3.1使用加強(qiáng)for。
        //為什么不使用for,因?yàn)镃olletion是沒有下標(biāo)的。
        for (Object object:collection
             ) {
            System.out.println(object);
        }
        //3.2使用迭代器(迭代器專門用來遍歷集合的一種方式)
        //hasNext();有沒有下一個(gè)元素。
        //next();獲取下一個(gè)元素。
        //remove();刪除當(dāng)前元素。
        Iterator it =  collection.iterator();
        while (it.hasNext()){
        String   s = (String) it.next();
            System.out.println(s);
            //刪除方法:
            //collection.remove(s);會(huì)出現(xiàn)并發(fā)異常。在迭代器中不能使用colection對(duì)對(duì)象進(jìn)行修改。
            //it.remove();應(yīng)使用迭代器的方法。
        }
        //4.判斷。
        System.out.println(collection.contains("西瓜"));
        System.out.println(collection.isEmpty());
    }
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 *Collection的使用:保存學(xué)生信息
 */
public class Demo2 {
    public static void main(String[] args) {
        //新建Collection對(duì)象。
        Collection collection = new ArrayList();
        Student s1 = new Student("張三",20);
        Student s2 = new Student("張無忌",18);
        Student s3 = new Student("王二",22);
        //1.添加數(shù)據(jù)
        collection.add(s1);
        collection.add(s2);
        collection.add(s3);
        System.out.println("元素個(gè)數(shù)"+collection.size());
        System.out.println(collection.toString());
        //2。刪除。
        collection.remove(s1);
        //collection.clear();  清除只是把三個(gè)對(duì)象從集合里刪除,刪除的是對(duì)象在集合的地址,三個(gè)對(duì)象沒有消失。三個(gè)對(duì)象還在堆里面。
        System.out.println("刪除之后"+collection.size());
        //3.遍歷
        //3.1使用增強(qiáng)for遍歷
        for (Object object:
             collection) {
            Student s  = (Student) object;
            System.out.println(s.toString());
        }
        //3.2使用迭代器:hasNext(); next(); remove(); 迭代過程中不能使用collection的刪除方法。
        Iterator it =  collection.iterator();
        while (it.hasNext()){
            Student s = (Student) it.next();
            System.out.println(s.toString());
        }
        //4.判斷
        collection.contains(s2);
        collection.isEmpty();
        System.out.println(collection.contains(s2));
        System.out.println(collection.isEmpty());
    }
}
/**
 * 學(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ù)。
  • 方法
    • void add(int index,Object o); //在index位置中插入對(duì)象o。
    • boolean addAll(int index,Collection collection); //將一個(gè)集合中的元素添加到此集合中的index位置。
    • Object get(int index); //返回集合中指定元素的位置。
    • List subList(int formIndex,int toIndex); //返回formIndex和toIndex之間的集合元素。

接口 ListIterator<E>

系列表迭代器,允許程序員按任一方向遍歷列表、迭代期間修改列表,并獲得迭代器在列表中的當(dāng)前位置。ListIterator 沒有當(dāng)前元素;它的光標(biāo)位置 始終位于調(diào)用 previous() 所返回的元素和調(diào)用 next() 所返回的元素之間。

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

/**
 * List子接口的使用(一)
 * 特點(diǎn):有序,有下標(biāo),元素可以重復(fù)。
 */
public class Deom3 {
    public static void main(String[] args) {
        //創(chuàng)建集合對(duì)象。
        List list = new ArrayList<>();
        //1.添加元素。
        list.add("蘋果");
        list.add("小米");
        list.add(0,"華為");
        System.out.println(list.size());
        System.out.println(list.toString());
        //2.刪除元素。
        //list.remove("蘋果"); //使用元素進(jìn)行刪除。
        //list.remove(0);//使用下標(biāo)進(jìn)行刪除。
        System.out.println(list.size());
        System.out.println(list.toString());
        //3.遍歷。
        //3.1使用for遍歷。
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        //3.2使用增強(qiáng)for。
        for (Object object:
             list) {
            System.out.println(object);
        }
        //3.3使用迭代器
        Iterator it =  list.iterator();
        while (it.hasNext()){
        Object s  =   it.next();
            System.out.println(s);
        }
        //3.4使用列表迭代器,listIterator可以雙向遍歷,添加、刪除及修改元素。
        ListIterator lit =  list.listIterator();
        System.out.println("---------3.4使用列表迭代器從前往后---------------");
        while (lit.hasNext()){
            System.out.println(lit.nextIndex()+":"+lit.next());
        }
        System.out.println("---------3.4使用列表迭代器從后往前---------------");
        //從后往前(此時(shí)“遍歷指針”已經(jīng)指向末尾)
        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("華為"));
    }

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

/**
 * List子接口的使用(二)
 */
public class Deom4 {
    public static void main(String[] args) {
        List list = new ArrayList<>();
        //1.添加數(shù)字?jǐn)?shù)據(jù)(自動(dòng)裝箱)
        list.add(20);
        list.add(30);
        list.add(40);
        list.add(50);
        System.out.println("元素個(gè)數(shù):"+list.size());
        System.out.println(list.toString());
        //2.刪除元素。
        list.remove(0);
        //list.remove(20);很明顯數(shù)組下標(biāo)越界,改成如下
        //list.remove((Object) 20);
        //list.remove(new Integer(20));
        System.out.println("元素個(gè)數(shù):"+list.size());
        System.out.println(list.toString());
        //3-5不在演示,與之前類似
        //6.補(bǔ)充方法sublist,返回子集合,含頭不含尾。
        List list1 =  list.subList(1,3);
        System.out.println(list1.size());
        System.out.println(list1.toString());
    }
}

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


ArrayList【重點(diǎn)】

  • 數(shù)組結(jié)構(gòu)實(shí)現(xiàn),查詢快,增刪慢。
  • JDK1.2版本,運(yùn)行效率快,線程不安全。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;

/**
 * ArrayList的使用。
 * 存儲(chǔ)結(jié)構(gòu):數(shù)組,查詢遍歷快,增刪慢。
 */
public class Deom5 {
    public static void main(String[] args) {
        //創(chuàng)建一個(gè)集合
        ArrayList arrayList = new ArrayList();
        //1.添加元素
        Student s1 = new Student("劉德華",22);
        Student s2 = new Student("郭富城",20);
        Student s3 = new Student("梁朝偉",18);
        arrayList.add(s1);
        arrayList.add(s2);
        arrayList.add(s3);
        System.out.println(arrayList.size());
        System.out.println(arrayList);
        //2.刪除元素。
        arrayList.remove(s1);
        //arrayList.remove("劉德華",22);
        //注:這樣可以刪除元素嗎?(不可以)顯然這是兩個(gè)不同的對(duì)象。
        //假如兩個(gè)對(duì)象屬性相同便認(rèn)為是其同一對(duì)象,那么該如何修改代碼? 我們?cè)赟tudent重寫equals方法。
        System.out.println(arrayList.size());
        System.out.println(arrayList);
        //3.遍歷元素。
        //3.1使用迭代器。
        Iterator it =  arrayList.iterator();
        while (it.hasNext()){
            Student s = (Student) it.next(); //這里不用轉(zhuǎn)化也可以,直接打印it.next()
            System.out.println(s.toString());
        }
        //3.2使用列表迭代器。
        ListIterator lit =  arrayList.listIterator();
        while (lit.hasNext()){
            Student slit = (Student) lit.next();
            System.out.println(slit.toString());
        }
        while (lit.hasPrevious()){
            Student slit = (Student) lit.previous();
            System.out.println(slit.toString());
        }
        //4.判斷
        System.out.println(arrayList.contains(s1));
        System.out.println(arrayList.isEmpty());
        //5.查找
        System.out.println(arrayList.indexOf(s1));
    }
}

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

    @Override
    public boolean equals(Object obj) {
        //1.判斷是不是同一個(gè)對(duì)象。
        if (this==obj){
            return true;
        }
        //2.判斷對(duì)象是否為空。
        if (obj==null){
            return false;
        }
        //3.判斷對(duì)象是不是Student類型
        if (obj instanceof Student){
            Student s = (Student) obj;
            //4.比較屬性。
            if (this.name.equals(s.getName()) && this.age==s.getAge()){
                return true;
            }
        }
        //5.不滿足條件返回false。
        return false;
    }

ArrayList源碼分析

  • 默認(rèn)容量大小:private static final int DEFAULT_CAPACITY = 10;
  • 存放元素的數(shù)組: transient Object[] elementData;
  • 實(shí)際元素個(gè)數(shù): private int size;
  • 創(chuàng)建對(duì)象時(shí)調(diào)用無參構(gòu)造函數(shù):
//這是一個(gè)空的數(shù)組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

這段源碼說明當(dāng)你沒有向集合中添加任何元素時(shí),集合容量為0。那么默認(rèn)的10個(gè)容量怎么來的呢?
這就得看看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,接著我們?cè)龠M(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)閑lementData數(shù)組長度為0,所以if條件成立,調(diào)用grow方法,重要的部分來了,我們?cè)俅芜M(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)镸AX_ARRAY_SIZE的定義如下:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

這個(gè)值太大了以至于第二個(gè)if條件沒有了解的必要。

最后一句話就是為elementData數(shù)組賦予了新的長度,Arrays.copyOf()方法返回的數(shù)組是新的數(shù)組對(duì)象,原數(shù)組對(duì)象不會(huì)改變,該拷貝不會(huì)影響原來的數(shù)組。copyOf()的第二個(gè)自變量指定要建立的新數(shù)組長度,如果新數(shù)組的長度超過原數(shù)組的長度,則保留數(shù)組默認(rèn)值。

這時(shí)候再回到add的方法中,接著就向下執(zhí)行elementData[size++] = e;到這里為止關(guān)于ArrayList就講解得差不多了,當(dāng)數(shù)組長度為10的時(shí)候你們可以試著過一下源碼,查一下每次的增量是多少(答案是每次擴(kuò)容為原來的1.5倍)。


Vector:

  • 數(shù)組結(jié)構(gòu)實(shí)現(xiàn),查詢快,增刪慢。
  • JDK1.0版本,運(yùn)行效率慢,線程安全。
import java.util.Vector;

/**
 * Vector的演示使用
 */
public class Deon6 {
    public static void main(String[] args) {
        //創(chuàng)建Vector對(duì)象
        Vector vector = new Vector<>();
        //1.添加數(shù)據(jù)。
        vector.add("石榴");
        vector.add("藍(lán)莓");
        vector.add("草莓");
        System.out.println("元素個(gè)數(shù):"+vector.size());
        System.out.println(vector.toString());
        //2.刪除數(shù)據(jù)
//        vector.remove(0);
//        vector.remove("草莓");
        //3.遍歷
        //使用枚舉器(類似與迭代器)
        Enumeration enumeration =  vector.elements();
        while (enumeration.hasMoreElements()){
           String s=(String) enumeration.nextElement();
            System.out.println(s);
        }
        //4.判斷
        System.out.println(vector.isEmpty());
        System.out.println(vector.contains("草莓"));
        //5.Vertor其他方法
        vector.firstElement(); //返回此向量的第一個(gè)組件(位于索引 0) 處的項(xiàng))。
        vector.lastElement();  //返回此向量的最后一個(gè)組件。
        vector.elementAt(0); //返回指定索引處的組件。
    }
}

LinkedList

  • 鏈表結(jié)構(gòu)實(shí)現(xiàn),增刪快,查詢慢。
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

/**
 * LinkedList的用法
 * 存儲(chǔ)結(jié)構(gòu):雙向鏈表

 */
public class Demo7 {
    public static void main(String[] args) {
        LinkedList linkedList=new LinkedList<>();
        Student s1 = new Student("劉德華",22);
        Student s2 = new Student("郭富城",20);
        Student s3 = new Student("梁朝偉",18);
        //1.添加元素
        linkedList.add(s1);
        linkedList.add(s2);
        linkedList.add(s3);
//        linkedList.add(s3);
        System.out.println("元素個(gè)數(shù):"+linkedList.size());
        System.out.println(linkedList.toString());
        //2.刪除元素
        /*
         * Student s1 = new Student("劉德華",22);
         * System.out.println(linkedList.toString());
         */
        //3.遍歷
        //3.1 使用for
        for(int i=0;i<linkedList.size();++i) {
            System.out.println(linkedList.get(i));
        }
        //3.2 使用增強(qiáng)for
        for(Object object:linkedList) {
            Student student=(Student) object;
            System.out.println(student.toString());
        }
        //3.3 使用迭代器
        Iterator iterator =linkedList.iterator();
        while (iterator.hasNext()) {
            Student student = (Student) iterator.next();
            System.out.println(student.toString());
        }
        //3.4 使用列表迭代器
        ListIterator lit =  linkedList.listIterator();
        while (lit.hasNext()){
            Student student = (Student) lit.next();
            System.out.println(student.toString());
        }
        while (lit.hasPrevious()){
            Student student = (Student)lit.previous();
            System.out.println(student.toString());
        }
        //4. 判斷
        System.out.println(linkedList.contains(s1));
        System.out.println(linkedList.isEmpty());
        System.out.println(linkedList.indexOf(s3));
    }
}

LinkedList源碼分析

LinkedList首先有三個(gè)屬性:

  • 鏈表大小: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;

關(guān)于Node類型我們?cè)龠M(jìn)入到類里看看:

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)前對(duì)象。

LinkedList是如何添加元素的呢?先看看add方法:

public boolean add(E e) {
    linkLast(e);
    return true;
}

進(jìn)入到linkLast方法:

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對(duì)象,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。如下圖所示:


image.png

如果滿足if條件,說明這是添加的第一個(gè)結(jié)點(diǎn),將first指向newNode:


image.png

至此,LinkedList對(duì)象的第一個(gè)數(shù)據(jù)添加完畢。假設(shè)需要再添加一個(gè)數(shù)據(jù),我們可以再來走一遍,過程同上不再贅述,圖示如下:
image.png

ArrayList和LinkedList區(qū)別

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


    image.png

泛型概述

  • Java泛型是JDK1.5中引入的一個(gè)新特性,其本質(zhì)是參數(shù)化類型,把類型作為參數(shù)傳遞。
  • 常見形式有泛型類、泛型接口、泛型方法。
  • 語法:
    • <T,…> T稱為類型占位符,表示一種引用類型。
  • 好處:
    • 提高代碼的重用性。
    • 防止類型轉(zhuǎn)換異常,提高代碼的安全性。

泛型類

/**
 * 泛型類
 * 語法:類名<T>
 * T是類型占位符,表示一種引用類型,編寫多個(gè)使用逗號(hào)隔開。
 */
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 ;
    }
}
/**
 * 注意:
 * 1.泛型只能使用引用類型
 * 2.不同泛型類型的對(duì)象不能相互賦值
 */
public class TestGeneric {
    public static void main(String[] args) {
        //使用泛型類創(chuàng)建對(duì)象。
        myGeneric<String> myGeneric1 = new myGeneric<String>();
        myGeneric1.t ="劉德華";
        myGeneric1.show("劉德華唱歌");
        myGeneric1.getT();

        myGeneric<Integer> myGeneric2 = new myGeneric<>();
        myGeneric2.t = 10 ;
        myGeneric2.show(20);
        Integer integer =  myGeneric2.getT();
    }
}

泛型接口

/**
 * 泛型接口
 * 語法:接口名<T>
 * 注意:不能創(chuàng)建泛型靜態(tài)常量
 */
public interface MyInterface<T> {
    //創(chuàng)建常量
    String nameString="tang";
    
    T server(T t);
}
/**
 * 實(shí)現(xiàn)接口時(shí)確定泛型類
 */
public class MyInterfaceImpl implements MyInterface<String>{
    @Override
    public String server(String t) {
        System.out.println(t);
        return t; 
    }
}
//測試
MyInterfaceImpl myInterfaceImpl=new MyInterfaceImpl();
myInterfaceImpl.server("xxx");
//xxx
/**
 * 實(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> myInterfaceImpl2=new MyInterfaceImpl2<Integer>();
myInterfaceImpl2.server(2000);
//2000

泛型方法

/**
 * 泛型方法
 * 語法:<T> 返回類型
 */
public class MyGenericMethod {
    public <T> void show(T t) {
        System.out.println("泛型方法"+t);
    }
}
//測試
MyGenericMethod myGenericMethod=new MyGenericMethod();
myGenericMethod.show("tang");
myGenericMethod.show(200);
myGenericMethod.show(3.14);

泛型集合

  • 概念:參數(shù)化類型,類型安全的集合,強(qiáng)制集合元素的類型必須一致。
  • 特點(diǎn)
    • 編譯時(shí)即可檢查,而非運(yùn)行時(shí)拋出異常。
    • 訪問時(shí),不必類型轉(zhuǎn)換(拆箱)。
    • 不同泛型指尖引用不能相互賦值,泛型不存在多態(tài)。
      之前我們?cè)趧?chuàng)建LinkedList類型對(duì)象的時(shí)候并沒有使用泛型,但是進(jìn)到它的源碼中會(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ò)誤。


集合概述

Set子接口

  • 特點(diǎn):無序,無下標(biāo),元素不能重復(fù)

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

/**
 * 測試Set接口的使用。
 * 特點(diǎn):(1)無序,沒有下標(biāo) (2)元素不能重復(fù)。
 * 方法:全部繼承Collection的全部方法。
public class Demo1 {
    public static void main(String[] args) {
        //創(chuàng)建集合。
        Set<String> set = new HashSet<String>();
        //1.添加元素。
        set.add("蘋果");
        set.add("香蕉");
        set.add("西瓜");
        System.out.println("元素個(gè)數(shù)"+set.size());
        System.out.println(set.toString());
        //2.刪除元素
//        set.remove("西瓜");
//        System.out.println(set.toString());
        //3.遍歷(重點(diǎn))
        //使用增強(qiáng)for
        for (String string:set
             ) {
            System.out.println(string);
        }
        //使用迭代器
        Iterator<String> it =  set.iterator();
        while (it.hasNext()){
          String s =   it.next();
            System.out.println(s);
        }
        //4.判斷
        System.out.println(set.contains("蘋果"));
        System.out.println(set.isEmpty());
    }
}

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

HashSet【重點(diǎn)】

  • 基于HashCode計(jì)算元素存放位置。
  • 當(dāng)存放元素的哈希碼相同時(shí),會(huì)調(diào)用equals進(jìn)行確認(rèn),若結(jié)果為true,則拒絕后者存入。
import java.util.HashSet;
import java.util.Iterator;

/**
 *HashSet的使用(一)
 *存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
 */
public class Demo1 {
    public static void main(String[] args) {
        //創(chuàng)建集合。
        HashSet<String> hashSet = new HashSet();
        //1.添加元素。
        hashSet.add("劉德華");
        hashSet.add("周潤發(fā)");
        hashSet.add("林志玲");
        hashSet.add("梁朝偉");
        System.out.println("元素個(gè)數(shù)"+hashSet.size());
        System.out.println(hashSet.toString());
        //2.刪除元素
//        hashSet.remove("周潤發(fā)");
//        System.out.println("元素個(gè)數(shù)"+hashSet.size());
        //3.遍歷
        //3.1增強(qiáng)for
        for (String string: hashSet
             ) {
            System.out.println(string);
        }
        //3.2使用迭代器
        Iterator<String> it =  hashSet.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
        //4.判斷
        System.out.println(hashSet.contains("黃飛鴻"));
        System.out.println(hashSet.isEmpty());
    }
}
/**
 * 人類
 */
public class Person {
    private String name;
    private 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;
    }
    @Override
    public String toString() {
        return "Peerson [name=" + name + ", age=" + age + "]";
    }
}
/**
 * HashSet集合的使用
 * 存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
 * 1.添加元素
 * 2.刪除元素
 * 3.遍歷
 * 4.判斷
*/
public class Demo3 {
    public static void main(String[] args) {
        HashSet<Person> hashSet=new HashSet<>();
        Person p1=new Person("tang",21);
        Person p2=new Person("he", 22);
        Person p3=new Person("yu", 21);
        //1.添加元素
        hashSet.add(p1);
        hashSet.add(p2);
        hashSet.add(p3);
        //重復(fù),添加失敗
        hashSet.add(p3);
        //直接new一個(gè)相同屬性的對(duì)象,依然會(huì)被添加,不難理解。
        //假如相同屬性便認(rèn)為是同一個(gè)對(duì)象,怎么修改?
        hashSet.add(new Person("yu", 21));
        System.out.println(hashSet.toString());
        //2.刪除元素
        hashSet.remove(p2);
        //3.遍歷
        //3.1 增強(qiáng)for
        for (Person person : hashSet) {
            System.out.println(person);
        }
        //3.2 迭代器
        Iterator<Person> iterator=hashSet.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());        
        }
        //4.判斷
        System.out.println(hashSet.isEmpty());
        //直接new一個(gè)相同屬性的對(duì)象結(jié)果輸出是false,不難理解。
        //注:假如相同屬性便認(rèn)為是同一個(gè)對(duì)象,該怎么做?
        System.out.println(hashSet.contains(new Person("tang", 21)));
    }
}
注:hashSet存儲(chǔ)過程:
  • 根據(jù)hashCode計(jì)算保存的位置,如果位置為空,則直接保存,否則執(zhí)行第二步。
  • 執(zhí)行equals方法,如果方法返回true,則認(rèn)為是重復(fù),拒絕存儲(chǔ),否則形成鏈表。

存儲(chǔ)過程實(shí)際上就是重復(fù)依據(jù),要實(shí)現(xiàn)“注”里的問題,可以重寫hashCode和equals代碼:

/**
 * 人類
 * 重新hashCode和equals方法。
 */
public class Person {
    private String name;
    private 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;
    }
    @Override
    public String toString() {
        return "Peerson [name=" + name + ", age=" + age + "]";
    }

    @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;
    }
}
hashCode方法里為什么要使用31這個(gè)數(shù)字大概有兩個(gè)原因:
  1. 31是一個(gè)質(zhì)數(shù),這樣的數(shù)字在計(jì)算時(shí)可以盡量減少散列沖突。
  2. 可以提高執(zhí)行效率,因?yàn)?1*i=(i<<5)-i,31乘以一個(gè)數(shù)可以轉(zhuǎn)換成移位操作,這樣能快一點(diǎn);但是也有網(wǎng)上一些人對(duì)這兩點(diǎn)提出質(zhì)疑。

TreeSet

  • 基于排序序列不重復(fù)。
  • 實(shí)現(xiàn)SortedSet接口,對(duì)集合元素進(jìn)行自動(dòng)排序。
  • 元素對(duì)象必須實(shí)現(xiàn)Comparable接口,進(jìn)行指定規(guī)則排序。
  • 通過CompareTo方法確定是否為重復(fù)元素。
import java.util.TreeSet;

/**
 *使用TreeSet保存數(shù)據(jù)。
 *存儲(chǔ)結(jié)構(gòu):紅黑樹
 * 要求:元素必須實(shí)現(xiàn)Comparable接口,compareTo方法返回值為0,則認(rèn)為元素重復(fù)。
 */
public class Demo2 {
    public static void main(String[] args) {
        //創(chuàng)建集合。
        TreeSet<Person> treeSet = new TreeSet<>();
        //1.添加元素。
        Person p1=new Person("tang",21);
        Person p2=new Person("he", 22);
        Person p3=new Person("yu", 21);
        treeSet.add(p1);
        treeSet.add(p2);
        treeSet.add(p3);
        //注:直接添加會(huì)報(bào)類型轉(zhuǎn)換錯(cuò)誤,需要實(shí)現(xiàn)Comparable接口
        System.out.println("元素個(gè)數(shù):"+treeSet.size());
        System.out.println(treeSet.toString());
        //2.刪除元素。
        treeSet.remove(p2);
        treeSet.remove(new Person("tang",21));
        System.out.println(treeSet.toString());
        //3.遍歷(略)
        //4.判斷
        System.out.println(treeSet.contains(p1));
        System.out.println(treeSet.isEmpty());
    }
}

查看Comparable接口的源碼,發(fā)現(xiàn)只有一個(gè)compareTo抽象方法,在人類中實(shí)現(xiàn)它:

public class Person implements Comparable<Person>{
    @Override
    //1.先按姓名比
    //2.再按年齡比
    public int compareTo(Person o) {
        int n1=this.getName().compareTo(o.getName());
        int n2=this.age-o.getAge();
        return n1==0?n2:n1;
    }
}
注:
  1. ? : 是一個(gè)三目運(yùn)算符,問號(hào)前面的表達(dá)式為真則返回'?'和':'中間那個(gè)值即,問號(hào)前面的表達(dá)式為假則返回':'后面那個(gè)值
  2. 當(dāng)先按姓名比時(shí),先判斷n1是否為0,若為0,則在比較n2是否為0,若不為0,則比較n1.

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

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

/**
 * TreeSet集合的使用。
 * Comparator:實(shí)制定比較()比較器
 * Comparable:可比較的。
 */
public class Demo2 {
    public static void main(String[] args) {
        //創(chuàng)建集合,并制定比較器
        TreeSet<Person> treeSet1 = new TreeSet<Person>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                int n1 =o1.getAge()-o2.getAge();
                int n2 =o1.getName().compareTo(o2.getName());
                return n1==0?n2:n1;
            }
        });
        Person p1 = new Person("xyz",22);
        Person p2 = new Person("hello",20);
        Person p3 = new Person("abc",25);
        Person p4 = new Person("lisi",22);

        treeSet1.add(p1);
        treeSet1.add(p2);
        treeSet1.add(p3);
        treeSet1.add(p4);

        System.out.println(treeSet1.toString());
    }
}

接下來我們來做一個(gè)小案例:

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

/**
 * 需求:使用TreeSet集合實(shí)現(xiàn)字符串按照長度進(jìn)行排序.
 * hellowold zhang lisi wangwu beijing  xian nanjing
 * Comparator實(shí)現(xiàn)接口制定比較。
 */
public class Demo2 {
    public static void main(String[] args) {
        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;
            }
        });
        treeSet.add("helloworld");
        treeSet.add("zhang");
        treeSet.add("lisi");
        treeSet.add("wangwu");
        treeSet.add("beijing");
        treeSet.add("xian");
        treeSet.add("nanjing");

        System.out.println(treeSet.toString());
    }
}

Map體系集合

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


      image.png

接口 Map<K,V>

  • 類型參數(shù):
    • K - 此映射所維護(hù)的鍵的類型
    • V - 映射值的類型

public interface Map<K,V>
將鍵映射到值的對(duì)象。一個(gè)映射不能包含重復(fù)的鍵;每個(gè)鍵最多只能映射到一個(gè)值。

方法摘要:
  • V put(K key,V value) //將指定的值與此映射中的指定鍵關(guān)聯(lián)(可選操作)。
  • Object get(Object key) //返回指定鍵所映射的值;如果此映射不包含該鍵的映射關(guān)系,則返回 null。
  • Set<Map.Entry<K,V>> entrySet() //返回此映射中包含的映射關(guān)系的Set視圖。
  • Collection<V> values() //返回包含所有值的Collection集合。 返回此映射中包含的映射關(guān)系的

接口 Map.Entry<K,V>

public static interface Map.Entry<K,V>
映射項(xiàng)(鍵-值對(duì))。Map.entrySet 方法返回映射的 collection 視圖,其中的元素屬于此類。獲得映射項(xiàng)引用的唯一 方法是通過此 collection 視圖的迭代器來實(shí)現(xiàn)。這些 Map.Entry 對(duì)象僅 在迭代期間有效;更確切地講,如果在迭代器返回項(xiàng)之后修改了底層映射,則某些映射項(xiàng)的行為是不確定的,除了通過 setValue 在映射項(xiàng)上執(zhí)行操作之外。

image.png
import java.util.HashMap;
import java.util.Map;

/**
 *Map接口的使用
 * 特點(diǎn):(1)存儲(chǔ)鍵值對(duì)(2)鍵不能重復(fù),值可以重復(fù)。(3)無序
 */
public class Demo2 {
    public static void main(String[] args) {
        //創(chuàng)建集合
        Map<String,String> map = new HashMap<>();
        //1.添加元素。
        map.put("cn","中國");
        map.put("uk","英國");
        map.put("usa","美國");
//      map.put("cn","zhongguo");//zhongguo 把前面的中國替換掉了

        System.out.println("元素個(gè)數(shù):"+map.size());
        System.out.println(map.toString());
        //2.刪除
//        map.remove("cn");
//        System.out.println(map.toString());
        //3.遍歷
        //3.1使用keySet()。
        for (String str1 : map.keySet()) {
            System.out.println(str1+map.get(str1));
        }
        //3.2使用entrySet方法。entry是一個(gè)映射項(xiàng)。效率較高.
        for (Map.Entry<String, String> stringStringEntry : map.entrySet()) {
            System.out.println(stringStringEntry.getKey()+stringStringEntry.getValue());
        }
        //4.判斷
        System.out.println(map.containsKey("cn"));
        System.out.println(map.containsValue("泰國"));
    }
}

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

HashMap【重點(diǎn)】

  • JDK1.2版本,線程不安全,運(yùn)行效率快;允許用null作為key或是value。
import java.util.HashMap;
import java.util.Map;

/**
 *HashMap集合的使用
 * 存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
 */
public class Demo2 {
    public static void main(String[] args) {
        //創(chuàng)建集合
        HashMap<Student,String> hashMap = new HashMap<>();
        //1.添加元素
        Student s1 = new Student("huang",1001);
        Student s2 =new Student("tang",1002);
        Student s3 = new Student("wang",10030);
        hashMap.put(s1,"北京");
        hashMap.put(s2,"上海");
        hashMap.put(s3,"南京");
        //添加失敗,但會(huì)更新值
        hashMap.put(s1,"南京");
        //注:假如相同屬性便認(rèn)為是同一個(gè)對(duì)象,怎么修改?
        hashMap.put(new Student("huang",1001),"南京");//此時(shí)元素個(gè)數(shù)是四個(gè),如果不想加進(jìn)來,可以在Student類重新hashcode和equlas方法。
        System.out.println(hashMap.size());
        System.out.println(hashMap.toString());
        //2.刪除元素
//        hashMap.remove(s1);
//        System.out.println(hashMap.size());
        //3.遍歷
        //3.1使用keySet遍歷
        for (Student student : hashMap.keySet()) {
            System.out.println(student+hashMap.get(student));
        }
        //3.2使用entrySet遍歷
        for (Map.Entry<Student, String> entry : hashMap.entrySet()) {
            System.out.println(entry.getKey()+entry.getValue());
        }
        //4.判斷
        System.out.println(hashMap.containsKey(s1));
        System.out.println(hashMap.containsValue("泰國"));
    }

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

    }

    @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 name != null ? name.equals(student.name) : student.name == null;
    }

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

HashMap源碼分析

  • 默認(rèn)初始化容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • 數(shù)組最大容量:static final int MAXIMUM_CAPACITY = 1 << 30;
  • 默認(rèn)加載因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • 鏈表調(diào)整為紅黑樹的鏈表長度閾值(JDK1.8):static final int TREEIFY_THRESHOLD = 8;
  • 紅黑樹調(diào)整為鏈表的鏈表長度閾值(JDK1.8):static final int UNTREEIFY_THRESHOLD = 6;
    *鏈表調(diào)整為紅黑樹的數(shù)組最小閾值(JDK1.8):static final int MIN_TREEIFY_CAPACITY = 64;
  • HashMap存儲(chǔ)的數(shù)組:transient Node<K,V>[] table;
  • 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)換成鏈表。

鏈表調(diào)整為紅黑樹的數(shù)組最小閾值是什么?

  • 并不是只要鏈表長度大于8就可以轉(zhuǎn)換成紅黑樹,在前者條件成立的情況下,數(shù)組的容量必須大于等于64才會(huì)進(jìn)行轉(zhuǎn)換。

HashMap的數(shù)組table存儲(chǔ)的就是一個(gè)個(gè)的Node<K,V>類型,很清晰地看到有一對(duì)鍵值,還有一個(gè)指向next的指針(以下只截取了部分源碼):

static class Node<K,V> implements Map.Entry<K,V> {
      final K key;
      V value;
      Node<K,V> next;
  }

之前的代碼中在new對(duì)象時(shí)調(diào)用的是HashMap的無參構(gòu)造方法,進(jìn)入到該構(gòu)造方法的源碼查看一下:

public HashMap() {
      this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  }

發(fā)現(xiàn)沒什么內(nèi)容,只是賦值了一個(gè)默認(rèn)加載因子;而在上文我們觀察到源碼中table和size都沒有賦予初始值,說明剛創(chuàng)建的HashMap對(duì)象沒有分配容量,并不擁有默認(rèn)的16個(gè)空間大小,這樣做的目的是為了節(jié)約空間,此時(shí)table為null,size為0。

當(dāng)我們往對(duì)象里添加元素時(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)生哈希值的方法,再進(jì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對(duì)象時(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對(duì)象就在這里獲得了初始容量。然后我們?cè)倩氐絧utVal方法,第二個(gè)if就是根據(jù)哈希碼得到的tab中的一個(gè)位置是否為空,為空便直接添加元素,此時(shí)數(shù)組中無元素所以直接添加。至此HashMap對(duì)象就完成了第一個(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ò)容都是原來大小的兩倍。

:額外說明的一點(diǎn)是在JDK1.8以前鏈表是頭插入,JDK1.8以后鏈表是尾插入。


HashSet源碼分析

了解完HashMap之后,再回過頭來看之前的HashSet源碼,為什么放在后面寫你們看一下源碼就知道了(部分):

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

很明了地發(fā)現(xiàn)它的add方法調(diào)用的就是map的put方法,把元素作為map的key傳進(jìn)去的。。


Hashtable

  • JDK1.0版本,線程安全,運(yùn)行效率慢;不允許null作為key或是value。
  • 初始容量11,加載因子0.75。
    這個(gè)集合在開發(fā)過程中已經(jīng)不用了,稍微了解即可。

Properties

  • Hashtable的子類,要求key和value都是String。通常用于配置文件的讀取。
    它繼承了Hashtable的方法,與流關(guān)系密切,此處不詳解。

TreeMap

  • 實(shí)現(xiàn)了SortedMap接口(是Map的子接口),可以對(duì)key自動(dòng)排序。
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

/**
 * TreeMap的使用
 * 存儲(chǔ)結(jié)構(gòu):紅黑樹,
 */
public class Demo2 {
    public static void main(String[] args) {
        //創(chuàng)建集合
        TreeMap<Student,Integer> treeMap = new TreeMap<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int n1 = o1.getName().compareTo(o2.getName());
                int n2 = o1.getStuNo()- o2.getStuNo();
                return n1==0?n2:n1;
            }
        });
        //1.創(chuàng)建元素
        Student s1=new Student("tang", 36);
        Student s2=new Student("yu", 101);
        Student s3=new Student("he", 10);

        treeMap.put(s1, 21);
        treeMap.put(s2, 22);
        treeMap.put(s3, 21);

        System.out.println(treeMap.toString());

        //2.刪除
//        treeMap.remove(s1);
//        treeMap.remove(s2,22);
//        System.out.println(treeMap.toString());
        //3.遍歷
        //使用keySet()遍歷
        for (Student student : treeMap.keySet()) {
            System.out.println(student +"-------"+ treeMap.get(student));//
        }
        //使用entrySet()遍歷
        for (Map.Entry<Student, Integer> entry : treeMap.entrySet()) {
            System.out.println(entry.getKey()+""+entry.getValue());
        }
        //4.判斷
        System.out.println(treeMap.containsKey(new Student("tang", 36)));
    }

TreeSet源碼

和HashSet類似,放在TreeMap之后講便一目了然(部分):

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接口)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 實(shí)現(xiàn)Collections工具類的使用。
 *
 */
public class Demo2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(20);
        list.add(5);
        list.add(12);
        list.add(30);
        list.add(6);
        //sort排序
        System.out.println("排序之前:"+list.toString());
        Collections.sort(list);
        System.out.println("排序之后:"+list.toString());
        //binarySearch(二分查找) 二分查找前提:要查找的序列必須是有序的。
        int i = Collections.binarySearch(list,12);
        System.out.println(i); //如果查找成功,i是>=0,如果查找失敗,i返回負(fù)數(shù)。
        //copy復(fù)制
        List<Integer> list2 = new ArrayList<>();//該方法要求目標(biāo)元素容量大于等于源目標(biāo),不然會(huì)報(bào)錯(cuò)。
        for (int j = 0; j < list.size(); j++) {
            list2.add(j);
        }
        Collections.copy(list2,list);
        System.out.println(list2);
        //reverse(反轉(zhuǎn))
        Collections.reverse(list);
        System.out.println(list);
        //shuffle(打亂)
        Collections.shuffle(list);
        System.out.println(list);

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

        //補(bǔ)充:數(shù)組轉(zhuǎn)成集合
        String[] nameStrings= {"tang","he","yu"};
        //受限集合,不能添加和刪除
        List<String> list3= Arrays.asList(nameStrings);
        System.out.println(list3);

        //注:基本類型轉(zhuǎn)成集合時(shí)需要修改為包裝類
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 一.集合概述 1.集合的由來和數(shù)組的區(qū)別 對(duì)象數(shù)組內(nèi)存圖解學(xué)生類圖解.png 數(shù)字長度固定,引入集合 概述:a:面...
    肝點(diǎn)啥_董曉寧閱讀 111評(píng)論 0 0
  • 前言 大部分編程語言都提供了數(shù)組來保存對(duì)象,數(shù)組是非常重要的數(shù)據(jù)結(jié)構(gòu)之一。但是數(shù)組在初始化時(shí)就已經(jīng)定義了數(shù)組長度,...
    海人為記閱讀 560評(píng)論 0 1
  • 本文主要是Java集合的概述和Set集合 1.Java集合概述 1)數(shù)組可以保存多個(gè)對(duì)象,但數(shù)組長度不可變,一旦在...
    廖111閱讀 488評(píng)論 0 2
  • 概述 我們都知道,數(shù)組的長度是不可變的, 當(dāng)元素的個(gè)數(shù)超過數(shù)組的長度之后, 我們就只能通過創(chuàng)建長度更長的新數(shù)組的方...
    778778閱讀 277評(píng)論 0 0
  • 面向?qū)ο笳Z言對(duì)事物的體現(xiàn)都是以對(duì)象的形式,為了方便對(duì)多個(gè)對(duì)象的操作,就要對(duì)對(duì)象進(jìn)行存儲(chǔ)。另一方面,使用Array存...
    Martain閱讀 166評(píng)論 0 1

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