Java常用集合類的數據結構

這裏介紹幾個常用的集合類的數據結構(ArrayList,LinkList,HashMap)

1.ArrayList

public class ArrayList<E> extends AbstractList<E>
  implements List<E>, RandomAccess, Cloneable, Serializable
{
  private static final long serialVersionUID = 8683452581122892189L;
  private transient Object[] elementData;
  private int size;
  private static final int MAX_ARRAY_SIZE = 2147483639;

  public ArrayList(int paramInt)
  {
    if (paramInt < 0) {
      throw new IllegalArgumentException("Illegal Capacity: " + paramInt);
    }
    this.elementData = new Object[paramInt];
  }

  public ArrayList()
  {
    this(10);
  }
可以看出ArrayList其實封裝了一個Object類型的數組(transient 表示該屬性不是對象串行化的一部分),無參的ArrayList初始化數組長度爲10;

public boolean add(E paramE)
  {
    ensureCapacityInternal(this.size + 1);
    this.elementData[(this.size++)] = paramE;
    return true;
  }

可以看到arrayList每次增加一個元素之前都會對當前數組長度去做一個檢查

private void ensureCapacityInternal(int paramInt) {
    this.modCount += 1;

    if (paramInt - this.elementData.length > 0)
      grow(paramInt);
  }

  private void grow(int paramInt)
  {
    int i = this.elementData.length;
    int j = i + (i >> 1);
    if (j - paramInt < 0)
      j = paramInt;
    if (j - 2147483639 > 0) {
      j = hugeCapacity(paramInt);
    }
    this.elementData = Arrays.copyOf(this.elementData, j);
  }

  private static int hugeCapacity(int paramInt) {
    if (paramInt < 0)
      throw new OutOfMemoryError();
    return paramInt > 2147483639 ? 2147483647 : 2147483639;
  }
從上面代碼可以看出,ArrayList當長度不足的時候會對當前數組進行擴容操作,擴容爲當前數組長度的1/2

之前碰到有人喜歡把數組轉換成ArrayList再使用ArrayList的Contains(Object o)方法(吐槽:多此一舉),其實contains方法採用的for循環去對比對象的hash值

2.linkedList

和ArrayList相比,他們除了實現了相同的接口,數據結構完全不一樣

public class LinkedList<E> extends AbstractSequentialList<E>
  implements List<E>, Deque<E>, Cloneable, Serializable
{
  transient int size = 0;
  transient Node<E> first;
  transient Node<E> last;
  private static final long serialVersionUID = 876323262645176354L;
  public LinkedList()
  {
  }
  public LinkedList(Collection<? extends E> paramCollection)
  {
    this();
    addAll(paramCollection);
  }
無參構造器什麼都沒有做,我們可以看到兩個未初始化的Node類型的成員變量,也許到這裏還不是很清楚,我們看看add方法

public boolean add(E paramE)
  {
    linkLast(paramE);
    return true;
  }
void linkLast(E paramE)
  {
    Node localNode1 = this.last;
    Node localNode2 = new Node(localNode1, paramE, null);
    this.last = localNode2;
    if (localNode1 == null)
      this.first = localNode2;
    else
      localNode1.next = localNode2;
    this.size += 1;
    this.modCount += 1;
  }
Node(Node<E> paramNode1, E paramE, Node<E> paramNode2) { 
      this.item = paramE;
      this.next = paramNode2;
      this.prev = paramNode1;
    }
從上面可以很清楚地看到,LinkedList實際上是一個雙向鏈表(每個節點有一個指向前面元素和後面元素的指針(引用)),插入元素的時候會在最後一個元素末尾插入一個元素,並把之前最後一個元素的next指針指向新插入的元素。

3.HashMap

public class HashMap<K, V> extends AbstractMap<K, V>
  implements Map<K, V>, Cloneable, Serializable
{
  static final int DEFAULT_INITIAL_CAPACITY = 16;
  static final int MAXIMUM_CAPACITY = 1073741824;
  static final float DEFAULT_LOAD_FACTOR = 0.75F;
  transient Entry<K, V>[] table;
  final transient int hashSeed = Hashing.randomHashSeed(this);
  private transient Set<Map.Entry<K, V>> entrySet = null;

  public HashMap(int paramInt, float paramFloat)
  {
    if (paramInt < 0) {
      throw new IllegalArgumentException("Illegal initial capacity: " + paramInt);
    }
    if (paramInt > 1073741824)
      paramInt = 1073741824;
    if ((paramFloat <= 0.0F) || (Float.isNaN(paramFloat))) {
      throw new IllegalArgumentException("Illegal load factor: " + paramFloat);
    }

    int i = 1;
    while (i < paramInt) {
      i <<= 1;
    }
    this.loadFactor = paramFloat;
    this.threshold = (int)Math.min(i * paramFloat, 1.073742E+009F);
    this.table = new Entry[i];
    this.useAltHashing = ((VM.isBooted()) && (i >= Holder.ALTERNATIVE_HASHING_THRESHOLD));

    init();
  }
 public HashMap()
  {
    this(16, 0.75F);
  }

其實可以看出HashMap用來存儲數據的也是一個數組(Entry),無參構造器構造一個默認長度爲16的數組,threshold取的是當前數組的75%和1.07374195E9的最小值,threshold是幹什麼的我們繼續看

HashMap增加元素:

public V put(K paramK, V paramV)
  {
    if (paramK == null)
      return putForNullKey(paramV);
    int i = hash(paramK);
    int j = indexFor(i, this.table.length);
    for (Entry localEntry = this.table[j]; localEntry != null; localEntry = localEntry.next)
    {
      Object localObject1;
      if ((localEntry.hash == i) && (((localObject1 = localEntry.key) == paramK) || (paramK.equals(localObject1)))) {
        Object localObject2 = localEntry.value;
        localEntry.value = paramV;
        localEntry.recordAccess(this);
        return localObject2;
      }
    }

    this.modCount += 1;
    addEntry(i, paramK, paramV, j);
    return null;
  }
可以看到增加元素的時候會檢查key值,如果key值存在,則覆蓋原先的value值,其實我們可以這樣理解HashMap內部維護了一張Hash表,通過它的hash算法和indexFor方法找到存儲元素的位置,如果位置上存在元素且key值相等,就替換

static int indexFor(int paramInt1, int paramInt2)
  {
    return paramInt1 & paramInt2 - 1;
  }
addEntry(沒有找到對應的key值則新增)

void addEntry(int paramInt1, K paramK, V paramV, int paramInt2)
  {
    if ((this.size >= this.threshold) && (null != this.table[paramInt2])) {
      resize(2 * this.table.length);
      paramInt1 = null != paramK ? hash(paramK) : 0;
      paramInt2 = indexFor(paramInt1, this.table.length);
    }

    createEntry(paramInt1, paramK, paramV, paramInt2);
  }
void resize(int paramInt)
  {
    Entry[] arrayOfEntry1 = this.table;
    int i = arrayOfEntry1.length;
    if (i == 1073741824) {
      this.threshold = 2147483647;
      return;
    }

    Entry[] arrayOfEntry2 = new Entry[paramInt];
    boolean bool1 = this.useAltHashing;
    this.useAltHashing |= ((VM.isBooted()) && (paramInt >= Holder.ALTERNATIVE_HASHING_THRESHOLD));

    boolean bool2 = bool1 ^ this.useAltHashing;
    transfer(arrayOfEntry2, bool2);
    this.table = arrayOfEntry2;
    this.threshold = (int)Math.min(paramInt * this.loadFactor, 1.073742E+009F);
  }
void createEntry(int paramInt1, K paramK, V paramV, int paramInt2)
  {
    Entry localEntry = this.table[paramInt2];
    this.table[paramInt2] = new Entry(paramInt1, paramK, paramV, localEntry);
    this.size += 1;
  }

 Entry(int paramInt, K paramK, V paramV, Entry<K, V> paramEntry)
    {
      this.value = paramV;
      this.next = paramEntry;
      this.key = paramK;
      this.hash = paramInt;
    }

新增元素:會判斷當前數組的長度是否大於threshold並且是否存在相同hash值得元素(都爲true,擴容),新增的Entry裏面有一個next指針,指向當前的同hash值得對象

擴容操作:擴充爲當前數組的2倍,並且會對已存在的已儲存的元素進行rehash操作

理解了上面,HashMap如何獲取指定key值得元素也就明瞭了

final Entry<K, V> getEntry(Object paramObject)
  {
    int i = paramObject == null ? 0 : hash(paramObject);
    for (Entry localEntry = this.table[indexFor(i, this.table.length)]; 
      localEntry != null; 
      localEntry = localEntry.next)
    {
      Object localObject;
      if ((localEntry.hash == i) && (((localObject = localEntry.key) == paramObject) || ((paramObject != null) && (paramObject.equals(localObject)))))
      {
        return localEntry;
      }
    }
    return null;
  }
查找操作:先通過hash值找到數組中的位置,再對比key值是否相同(不相同,Entry.next)

下面我們分別測試一下單線程情況下ArrayList,LinkedList,HashMap的增刪查效率(無圖無真相(模擬了1000w條數據),根據機器的性能不同時間差異也不同)


結果顯而易見

發佈了42 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章