【Java基礎】集合

王曉斌發表於2014-04-27

0.

Java集合類庫將介面和實現分離。


Java集合類庫的基本介面是Collection (java.util

public interface Collection<E> extends Iterable<E>

迭代器Iterator

public interface Iterator<E>  (java.util)

Methods 
Modifier and Type Method and Description
boolean hasNext()
Returns true if the iteration has more elements.
E next()
Returns the next element in the iteration.
void remove()
Removes from the underlying collection the last element returned by this iterator (optional operation).
傳統(如C++)的迭代器是基於陣列索引的,因此得到一個迭代器之後,可以進行i++操作來移動迭代器。

但是,Java迭代器並不是這樣運作的,查詢操作與位置的變更是聯絡在一起的。查詢一個元素的唯一操作就是next(),而執行該查詢的同時會使迭代器的位置向前移動

  • 迭代器模型:

應該將Java迭代器看成是位於各個元素之間的:當呼叫next()的時候,迭代器便越過下一個元素,並且返回它剛剛越過的那個元素的引用;


  • 迭代器的遍歷:

方法1:呼叫next之前應該先執行hasNext()檢測,防止next()丟擲NoSuchElementException

Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while(iter.hasNext()){
    String element = iter.next();
	... ...
}

方法2:for each

for (String element : c){
    ... ...
}

  • 移除元素:

Iterator介面的remove方法會移除上次呼叫next()方法返回的元素。

i.e. 移除第一個位置上的元素:

Iterator<String> iter = c.iterator();
iter.next();   // skip over the first element
iter.remove(); // now, remove it

如果呼叫remove()方法之前沒有呼叫next(),或者在上一次呼叫next()方法之後已經呼叫過一次remove(),則此時呼叫remove()會丟擲IllegalStateException;


Java集合類庫框架(部分)


集合類庫 - List介面

List將元素維護在特定的序列中,並且允許重複的值。

List介面提供了名為ListIterator的迭代器。

List在資料結構中表現為陣列、向量、連結串列、堆疊、佇列等。

LinkedList

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable  (java.util)

LinkedList的特點

LinkedList是基於連結串列的集合(java中的連結串列都是迴圈雙重連線的,forward/backward node):
public LinkedList() {
        header.next = header.previous = header;
    }

優點是插入和刪除較快,查詢較慢。

LinkedList的實現機制


LinkedList的使用


ArrayList

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable  (java.util)

ArrayList的特點

ArrayList封裝了一個動態再分配的物件陣列;查詢和修改速度較快,缺點是插入和刪除速度較慢。

ArrayList的實現機制

從JDK原始碼中可以看出,其帶有兩個屬性:

/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */
    private transient Object[] elementData;

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
當儲存空間不足時,重新分配空間:

public void ensureCapacity(int minCapacity) {
	modCount++;
	int oldCapacity = elementData.length;
	if (minCapacity > oldCapacity) {
	    Object oldData[] = elementData;
	    int newCapacity = (oldCapacity * 3)/2 + 1;
    	    if (newCapacity < minCapacity)
		newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
	}
    }

ArrayList的使用

Vector

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable  (java.util)

Vector的特點

同樣使用動態再分配陣列實現:
/**
     * The array buffer into which the components of the vector are
     * stored. The capacity of the vector is the length of this array buffer,
     * and is at least large enough to contain all the vector's elements.
     *
     * <p>Any array elements following the last element in the Vector are null.
     *
     * @serial
     */
    protected Object[] elementData;

    /**
     * The number of valid components in this {@code Vector} object.
     * Components {@code elementData[0]} through
     * {@code elementData[elementCount-1]} are the actual items.
     *
     * @serial
     */
    protected int elementCount;

Vector的實現機制

當儲存空間不足時,重新分配:
private void ensureCapacityHelper(int minCapacity) {
	int oldCapacity = elementData.length;
	if (minCapacity > oldCapacity) {
	    Object[] oldData = elementData;
	    int newCapacity = (capacityIncrement > 0) ?
		(oldCapacity + capacityIncrement) : (oldCapacity * 2);
    	    if (newCapacity < minCapacity) {
		newCapacity = minCapacity;
	    }
            elementData = Arrays.copyOf(elementData, newCapacity);
	}
    }

Vector中大部分方法都使用synchronized修飾,是執行緒安全的。

Vector的使用


ArrayList V.S. Vector

  • Vector的所有方法都是同步的;ArrayList的方法不是同步的;
  • 在不需要同步時,建議使用ArrayList;

Stack

public class Stack<E> extends Vector<E>  (java.util)

Stack的特點


Stack的實現機制

Stack繼承自Vector,同樣使用陣列儲存資料。

Stack的使用



集合類庫 - Set介面

public interface Set<E> extends Collection<E> (java.util)

Set集合和List集合都是存放的單個元素的序列,但是Set集合不允許有重複元素

在Set介面中沒有新增任何方法,所有方法均來自其父介面。它無法提供像List中按位存取的方法。在數學上一個集合有三個性質:確定性、互異性、無序性。

HashSet

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable  (java.util)

HashSet的特點

HashSet中存放的元素時無序的,底層是用HashMap實現的:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

其中Key時要放入的元素,value是一個Object型別的名為PRESENT的敞亮。由於使用了雜湊函式,因此其存取速度是非常快的

HashSet的實現機制


/**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
	map = new HashMap<E,Object>();
    }

    /**
     * Constructs a new set containing the elements in the specified
     * collection.  The <tt>HashMap</tt> is created with default load factor
     * (0.75) and an initial capacity sufficient to contain the elements in
     * the specified collection.
     *
     * @param c the collection whose elements are to be placed into this set
     * @throws NullPointerException if the specified collection is null
     */
    public HashSet(Collection<? extends E> c) {
	map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
	addAll(c);
    }

在HashSet中有一個負載因子Load Factor。在HashSet的預設實現中,初始容量為16,負載因子為0.75,也就是說當有75%的空間以被使用,將會在進行一次再雜湊,之前的雜湊表(陣列)將被刪除,新增加的雜湊表是之前雜湊表長度的2倍,最大值為Integer.MAX_VALUE。

  • 負載因子越高,記憶體使用率越大,元素的尋找時間越長;
  • 負載因子越低,記憶體使用率越小,元素的尋找時間越短;

當雜湊值相同時,將存放在同一個位置,使用連結串列方式依次連結下去。

HashMap中的resize方法如下(HashSet是基於HashMap實現的):

/**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry[] table;
 /**
     * Rehashes the contents of this map into a new array with a
     * larger capacity.  This method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not
     * resize the map, but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is MAXIMUM_CAPACITY (in which case value
     *        is irrelevant).
     */
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

HashSet的使用

HashSet是允許放空值的

LinkedHashSet

public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, Serializable  (java.util)

LinkedHashSet的特點

LinkedHashSet保證了按照插入順序有序

LinkedHashSet的實現機制

LinkedHashSet使用HashSet的建構函式:

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
	map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
    }
可以看出LinkedHashSet底層是使用LinkedHashMap實現的。
LinkedHashSet繼承自HashSet,並沒有提供額外的供使用的方法,所以在使用時與HashSet基本相同。

LinkedHashSet的使用


TreeSet

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable  (java.util)

TreeSet的特點

TreeSet中所放的元素是有序的,並且元素是不能重複的

基於紅黑樹

TreeSet的實現機制

TreeSet底層使用TreeMap使用,和HashSet一樣,將每個要放入的元素放到key的位置,value位置放的是一個Object型別的常量。

TreeSet的使用


集合類庫 - Map介面

public interface Map<K,V>

Map中的每個成員方法由一個關鍵字key和一個值value構成。Map介面不直接繼承自Collection介面,因為它包裝的是一組成對的“鍵-值”物件的集合,而且在Map介面的集合中也不能有重複的key出現,因為每個鍵只能與一個成員元素相對應。

HashMap

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

  • HashMap是未同步的;
  • 允許null key 和 null value;
  • 方法public V get(Object key)返回null時,即表示hashmap中沒有該key,也表示該key所對應的value是null;因此使用containsKey來判斷是否存在某個key;
  • HashMap利用key的hashCode重新計算hash值;

HashMap的特點


HashMap的實現機制

HashMap基於hash陣列實現,若key的hash值相同則使用連結串列方式進行儲存,可參見HashSet中的說明。

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry[] table;

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

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

Entry是一個節點,它持有下一個元素的引用,這樣就構成了一個連結串列。

/**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

這個方法根據hashCode記當前table的長度得到該元素應該存放的位置,即在table中的索引。

當元素超過threshold時,再雜湊的過程參看HashSet。

HashMap的使用

HashMap的存取速度快。

HashMap的遍歷:

public static void useWhileSentence(Map<Integer, String> m) {  
        Set s = (Set<Integer>)m.keySet();  
        Iterator<Integer> it = s.iterator();  
        int Key;  
        String value;  
        while(it.hasNext()) {  
            Key = it.next();  
            value = (String)m.get(Key);  
            System.out.println(Key+":\t"+value);  
        }  
    }  
      
    public static void useWhileSentence2(Map m) {  
        Set s = m.entrySet();  
        Iterator<Map.Entry<Integer, String>> it = s.iterator();  
        Map.Entry<Integer, String> entry;  
        int Key;  
        String value;  
        while(it.hasNext()) {  
            entry = it.next();  
            Key = entry.getKey();  
            value = entry.getValue();  
            System.out.println(Key+":\t"+value);  
        }  
    }  
      
    public static void useForSentence(Map<Integer, String> m) {  
        int Key;  
        String value;  
        for(Map.Entry<Integer, String> entry : m.entrySet()) {  
            Key = entry.getKey();  
            value = entry.getValue();  
            System.out.println(Key+":\t"+value);  
        }  
    }  


HashTable

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable  (java.util)

  • HashTable是同步的;
  • key或value均不能為null object;
  • HashTable直接使用做為key物件的hashCode方法,因此作為key的物件必須實現hashCodeequals方法;

HashTable的特點

HashTable實現Map介面,繼承自Dictionary類。任何非空的(key - value)均可以放入其中。

HashTable的實現機制

HashTable的部分方法使用synchronized保證執行緒安全。

HashTable的使用


HashTable V.S. HashMap

  • 如果不考慮執行緒安全,建議使用HashMap代替HashTable;如果需要執行緒安全,建議使用ConcurrentHashMap代替HashTable

LinkedHashMap

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

LinkedHashMap的特點

LinkedHashMap繼承自HashMap實現了Map介面。和HashMap一樣,LinkedHashMap允許key和value均為null。

LinkedHashMap的實現機制

LinkedHashMap與HashMap的不同之處在於:LinkedHashMap維護著執行於所有條目的雙重連線列表,此連結列表可以時插入順序或者訪問順序。

LinkedHashMap的使用


TreeMap

參見TreeSet


集合類比較


 interface 集合類 插入節點 刪除節點 查詢節點 遍歷 儲存方式 執行緒安全 其他
List                
  Vector 使用迭代器 陣列  
  ArrayList 使用迭代器 陣列  
  LinkedList 使用迭代器 連結串列  
Set                
  TreeSet       使用迭代器 紅黑樹 繼承TreeMap
  HashSet       使用迭代器 雜湊表 繼承HashMap
  LinkedHashSet       使用迭代器 雜湊表 繼承LinkedHashMap
Map                
  TreeMap       key set, EntrySet 紅黑樹  
  HashMap       key set, EntrySet 雜湊表  
  LinkedHashMap       key set, EntrySet 雜湊表  
  HashTable       key set, EntrySet 雜湊表  

集合類庫Utils

Collections(集合類包裝器)

(java.util)

public class Collections extends Object
包含一系列的靜態方法,操作或者返回一個集合類;

Arrays

(java.util)

public class Arrays extends Object
包含操縱陣列的靜態方法;


演算法


常用集合操作

刪除集合類中的特定元素

i.e. 刪除ArrayList中特定的元素:

方法1:

for (int i = 0, len = list.size(); i < len; i++){
	if (list.get(i) == XXX){
	    list.remove(i);
	    --len;
	    --i;
	}
}

方法2:

Iterator<String> sListIterator = list.iterator();
while(sListIterator.hasNext()){
	String e = sListIterator.next();
	if (e.equals("XXX")){
	    sListIterator.remove();
	}
}

推薦使用方法2.





相關文章