集合概述&集合之List接口


集合與數組存儲概述

集合、數組都是對多個數據進行存儲操作的結構,簡稱Java容器。此時的存儲,主要指的是內存層面的存儲,不涉及到持久化的存儲(.txt,.jpg,.avi,數據庫中)

數組存儲的特點:

  • 數組存儲數據的特點:有序、可重復。對於無序、不可重復的需求,不能滿足。

數組存儲的弊端:

  • 一旦初始化以后,其長度就不可修改,不便於擴展。

  • 數組一旦定義好,其元素的類型也就確定了。我們也就只能操作指定類型的數據了。

  • 數組中提供的方法非常有限,對於添加、刪除、插入數據等操作,非常不便,同時效率不高。

解決數組存儲數據方面的弊端。

集合框架概述

Java 集合可分為 CollectionMap 兩種體系

  • Collection接口:單列數據,定義了存取一組對象的方法的集合
    • List:元素有序、可重復的集合
      ArrayList、LinkedList、Vector
    • Set:元素無序、不可重復的集合
      HashSet、LinkedHashSet、TreeSet
  • Map接口:雙列數據,保存具有映射關系key-value鍵值對的集合

Collection接口

  • Collection 接口是 List、Set 和 Queue 接口的父接口,該接口里定義的方法既可用於操作 Set 集合,也可用於操作 List 和 Queue 集合。

  • JDK不提供此接口的任何直接實現,而是提供更具體的子接口(如:Set和List)實現。

  • 在 Java5 之前,Java 集合會丟失容器中所有對象的數據類型,把所有對象都當成 Object 類型處理;從 JDK 5.0 增加了泛型以后,Java 集合可以記住容器中對象的數據類型

List實現類之一:ArrayList

該類是實現了List的接口,實現了可變大小的數組,隨機訪問和遍歷元素時,提供更好的性能。該類也是非同步的,在多線程的情況下不要使用。ArrayList 增長當前長度的50%,插入刪除需要移動后續元素,效率低。

import java.util.ArrayList; // 引入 ArrayList 類

ArrayList<E> objectName =new ArrayList<>();  // 初始化
方法 描述
add() 將元素插入到指定位置的 arraylist 中
addAll() 添加集合中的所有元素到 arraylist 中
clear() 刪除 arraylist 中的所有元素
clone() 復制一份 arraylist
contains() 判斷元素是否在 arraylist
get() 通過索引值獲取 arraylist 中的元素
indexOf() 返回 arraylist 中元素的索引值
removeAll() 刪除存在於指定集合中的 arraylist 里的所有元素
remove() 刪除 arraylist 里的單個元素
size() 返回 arraylist 里元素數量
isEmpty() 判斷 arraylist 是否為空
subList() 截取部分 arraylist 的元素
set() 替換 arraylist 中指定索引的元素
sort() 對 arraylist 元素進行排序
toArray() 將 arraylist 轉換為數組
toString() 將 arraylist 轉換為字符串
ensureCapacity() 設置指定容量大小的 arraylist
lastIndexOf() 返回指定元素在 arraylist 中最后一次出現的位置
retainAll() 保留 arraylist 中在指定集合中也存在的那些元素
containsAll() 查看 arraylist 是否包含指定集合中的所有元素
trimToSize() 將 arraylist 中的容量調整為數組中的元素個數
removeRange() 刪除 arraylist 中指定索引之間存在的元素
replaceAll() 將給定的操作內容替換掉數組中每一個元素
removeIf() 刪除所有滿足特定條件的 arraylist 元素
forEach() 遍歷 arraylist 中每一個元素並執行特定操作

List實現類之二:LinkedList

前言

鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,但是並不會按線性的順序存儲數據,而是在每一個節點里存到下一個節點的地址。

鏈表可分為單向鏈表雙向鏈表

一個單向鏈表包含兩個值: 當前節點的值和一個指向下一個節點的鏈接。

img

一個雙向鏈表有三個整數值: 數值、向后的節點鏈接、向前的節點鏈接。

img

Java LinkedList(鏈表) 類似於 ArrayList,是一種常用的數據容器。

與 ArrayList 相比,LinkedList 的增加和刪除對操作效率更高,而查找和修改的操作效率較低。對於頻繁的插入或刪除元素的操作,建議使用LinkedList類。

概述

LinkedList:雙向鏈表,內部沒有聲明數組,而是定義了Node類型的first和last,用於記錄首末元素。同時,定義內部類Node,作為LinkedList中保存數據的基本結構。Node除了保存數據,還定義了兩個變量:

  • prev變量記錄前一個元素的位置
  • next變量記錄下一個元素的位置
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;
	} 
}

常用方法

方法 描述
public boolean add(E e) 鏈表末尾添加元素,返回是否成功,成功為 true,失敗為 false。
public void add(int index, E element) 向指定位置插入元素。
public boolean addAll(Collection c) 將一個集合的所有元素添加到鏈表后面,返回是否成功,成功為 true,失敗為 false。
public boolean addAll(int index, Collection c) 將一個集合的所有元素添加到鏈表的指定位置后面,返回是否成功,成功為 true,失敗為 false。
public void addFirst(E e) 元素添加到頭部。
public void addLast(E e) 元素添加到尾部。
public boolean offer(E e) 向鏈表末尾添加元素,返回是否成功,成功為 true,失敗為 false。
public boolean offerFirst(E e) 頭部插入元素,返回是否成功,成功為 true,失敗為 false。
public boolean offerLast(E e) 尾部插入元素,返回是否成功,成功為 true,失敗為 false。
public void clear() 清空鏈表。
public E removeFirst() 刪除並返回第一個元素。
public E removeLast() 刪除並返回最后一個元素。
public boolean remove(Object o) 刪除某一元素,返回是否成功,成功為 true,失敗為 false。
public E remove(int index) 刪除指定位置的元素。
public E poll() 刪除並返回第一個元素。
public E remove() 刪除並返回第一個元素。
public boolean contains(Object o) 判斷是否含有某一元素。
public E get(int index) 返回指定位置的元素。
public E getFirst() 返回第一個元素。
public E getLast() 返回最后一個元素。
public int indexOf(Object o) 查找指定元素從前往后第一次出現的索引。
public int lastIndexOf(Object o) 查找指定元素最后一次出現的索引。
public E peek() 返回第一個元素。
public E element() 返回第一個元素。
public E peekFirst() 返回頭部元素。
public E peekLast() 返回尾部元素。
public E set(int index, E element) 設置指定位置的元素。
public Object clone() 克隆該列表。
public Iterator descendingIterator() 返回倒序迭代器。
public int size() 返回鏈表元素個數。
public ListIterator listIterator(int index) 返回從指定位置開始到末尾的迭代器。
public Object[] toArray() 返回一個由鏈表元素組成的數組。
public T[] toArray(T[] a) 返回一個由鏈表元素轉換類型而成的數組。

Set接口

存儲的數據特點:無序的不可重復的元素,且既有數組也有鏈表

  • 無序性:不等於隨機性。存儲的數據在底層數組中並非按照數組索引的順序添加,而是根據數據的哈希值決定的。
  • 不可重復性:保證添加的元素照equals()判斷時,不能返回true.即:相同的元素只能添加一個。

Set接口是Collection的子接口,set接口沒有提供額外的方法。Set 集合不允許包含相同的元素,如果試把兩個相同的元素加入同一個Set 集合中,則添加操作失敗。Set 判斷兩個對象是否相同不是使用 == 運算符,而是根據 equals() 方法

常用實現類

  • Set接口:存儲無序的、不可重復的數據 -->類似於高中講的“集合”
    • HashSet:作為Set接口的主要實現類;線程不安全的;可以存儲null值
      • LinkedHashSet:作為HashSet的子類;遍歷其內部數據時,可以照添加的順序遍歷。在添加數據的同時,每個據還維護了兩個引用,記錄此數據前一個數據和后一個數據。對於頻繁的遍歷操作,LinkedHashSet效率高於HashSet.
    • TreeSet:可以照添加對象的指定屬性,進行排序。

元素添加過程:(以HashSet為例)

  1. 向HashSet中添加元素a,首先調用元素a所在類的hashCode()方法,計算出元素a的哈希值

  2. 此哈希值接着通過某種散列函數計算出在HashSet底層數組中的存放位置(即索引位置),然后判斷此位置上是否已經元素:

    • 如果此位置上沒其他元素,則元素a添加成功。 --->情況1
  3. 如果此位置上有其他元素b,則比較元素a與元素b的hash值,若有多個元素則挨個比較並以鏈表形式存儲

  • 如果hash值不相同,則元素a添加成功。--->情況2
  1. 如果hash值相同,進而需要調用元素a所在類的equals()方法:
    - equals()返回true表明該元素已存在,元素a添加失敗;equals()返回false,則元素a添加成功。-->情況3

對於添加成功的情況2和情況3而言:元素a 與已經存在指定索引位置上數據以鏈表的方式存儲。
jdk 7 :元素a指向原來的元素。
jdk 8 :原來的元素指向元素a
總結:七上八下

注意

  • 向Set(主要指:HashSet、LinkedHashSet)中添加的數據,其所在的類一定要重寫hashCode()equals()
  • 重寫的hashCode()equals()盡可能保持一致性:相等的對象必須具有相等的散列碼

結論:重寫equals方法的時候一般都需要同時重寫hashCode方法。通常參與計算hashCode 的對象的屬性也應該參與到equals() 中進行計算

Set實現類之一:HashSet

  • HashSet 基於 HashMap 來實現的,是一個不允許有重復元素的集合。
  • HashSet 允許有 null 值。
  • HashSet 是無序的,即不會記錄插入的順序。
  • HashSet 不是線程安全的, 如果多個線程嘗試同時修改 HashSet,則最終結果是不確定的。 您必須在多線程訪問時顯式同步對 HashSet 的並發訪問。
  • HashSet 實現了 Set 接口。
  • HashSet 中的元素實際上是對象,一些常見的基本類型可以使用它的包裝類
變量和類型 方法 描述
boolean add(E e) 如果指定的元素尚不存在,則將其添加到此集合中。
void clear() 從該集中刪除所有元素。
Object clone() 返回此 HashSet實例的淺表副本:未克隆元素本身。
boolean contains(Object o) 如果此set包含指定的元素,則返回 true
boolean isEmpty() 如果此集合不包含任何元素,則返回 true
Iterator<E> iterator() 返回此set中元素的迭代器。
boolean remove(Object o) 如果存在,則從該集合中移除指定的元素。
int size() 返回此集合中的元素數(基數)。
Spliterator<E> spliterator() 在此集合中的元素上創建late-binding和失敗快速 Spliterator

Set實現類之二:LinkedHashSet

  • LinkedHashSet 是 HashSet 的子類,LinkedHashSet 根據元素的 hashCode 值來決定元素的存儲位置,但它同時使用雙向鏈表維護元素的次序,這使得元素看起來是以插入順序保存的。

  • LinkedHashSet插入性能略低於 HashSet,但在頻繁的地遍歷操作時有很好的性能。LinkedHashSet的存在就是為了頻繁地遍歷

Set實現類之三:TreeSet的使用

TreeSet和后面要講的TreeMap,采用紅黑樹的存儲結構

特點:有序,查詢速度比List快

TreeSet 兩種排序方法:自然排序定制排序。默認情況下,TreeSet 采用自然排序

  • 自然排序中,比較兩個對象是否相同的標准為:重寫的compareTo()返回0,不再是equals()

    • 如果試圖把一個對象添加到 TreeSet 時,則該對象的類必須實現 Comparable 接口。

    • 向 TreeSet 中添加元素時,只有第一個元素無須比較compareTo()方法,后面添加的所有元素都會調用compareTo()方法進行比較。

    • 因為只有相同類的兩個實例才會比較大小,所以向 TreeSet 中添加的應該是同

      一個類的對象。

    • 重寫該對象對應的 equals() 方法時,應保證該方法與 compareTo(Object obj) 方法有一致的結果

  • 定制排序中,比較兩個對象是否相同的標准為:compare()返回0,不再是equals()。TreeSet的自然排序要求元素所屬的類實現Comparable接口,如果元素所屬的類沒有實現Comparable接口,或不希望按照升序(默認情況)的方式排列元素或希望按照其它屬性大小進行排序,則考慮使用定制排序。

    • 利用int compare(T o1,T o2)方法,比較o1和o2的大小:如果方法返回正整數,則表

      示o1大於o2;如果返回0,表示相等;返回負整數,表示o1小於o2。

    • 要實現定制排序,需要將實現Comparator接口的實例作為形參傳遞給TreeSet的構

      造器

    • 仍然只能向TreeSet中添加類型相同的對象。否則發生ClassCastException異常

//方式一:自然排序
@Test
    public void test1(){
        TreeSet set = new TreeSet();

        //失敗:不能添加不同類的對象
//        set.add(123);
//        set.add(456);
//        set.add("AA");
//        set.add(new User("Tom",12));

            //舉例一:
//        set.add(34);
//        set.add(-34);
//        set.add(43);
//        set.add(11);
//        set.add(8);

        //舉例二:
         set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Jack",33));
        set.add(new User("Jack",56));


        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

//方式二:定制排序
    @Test
    public void test2(){
        Comparator com = new Comparator() {
            //照年齡從小到大排列
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }else{
                    throw new RuntimeException("輸入的數據類型不匹配");
                }
            }
        };

        TreeSet set = new TreeSet(com);
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Mary",33));
        set.add(new User("Jack",33));
        set.add(new User("Jack",56));


        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

遍歷Collection的兩種方式

Iterator迭代器

  • java.utils包下定義的迭代器接口:Iterator
  • Iterator對象稱為迭代器(設計模式的一種),主要用於遍歷 Collection 集合中的元素。
  • GOF給迭代器模式的定義為:提供一種方法訪問一個容器(container)對象中各個元素,而又不需暴露該對象的內部細節。迭代器模式,就是為容器而生。
Iterator iter = coll.iterator();//回到起點
//hasNext():判斷是否還有下一個元素
while(iter.hasNext()){
    //next():①指針下移 ②獲取指針所指位置的元素
	Object obj = iter.next();
	if(obj.equals("Tom")){
		iter.remove();
	} 
}

Iterator接口中的remove()方法

  • 可在遍歷的時候刪除集合中的元素
  • 不同於集合直接調用remove(),collection返回的是boolean型,即是否刪除成功
  • 如果未調用next()或已經調用過remove(),再調用remove()則會報錯IllegalStateException

foreach增強for循環

  • Java 5.0 提供了 foreach 循環迭代訪問 Collection和數組。
  • 遍歷操作不需獲取Collection或數組的長度,無需使用索引訪問元素。
  • 遍歷集合的底層調用Iterator完成操作。
  • foreach還可以用來遍歷數組。

面試題

@Test
public void testListRemove() {
    List list = new ArrayList();
    list.add(1);
    list.add(2);
    list.add(3);
    updateList(list);
    System.out.println(list);//	[1,2]
}
private static void updateList(List list) {
	list.remove(2);	//基本類型不會自動封裝,當成索引
    list.romove(new Integer(2));	//[1,3]
}
//練習:在List內去除重復數字值,要求盡量簡單
public static List duplicateList(List list) {
	HashSet set = new HashSet();
	set.addAll(list);
	return new ArrayList(set);
}
public static void main(String[] args) {
	List list = new ArrayList();
	list.add(new Integer(1));
    list.add(new Integer(2));
    list.add(new Integer(2));
    list.add(new Integer(4));
    list.add(new Integer(4));
    List list2 = duplicateList(list);
    for (Object integer : list2) {
		System.out.println(integer);
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM