這裏介紹幾個常用的集合類的數據結構(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條數據),根據機器的性能不同時間差異也不同)
結果顯而易見