工做了3年多,一直都沒花心思去看看jdk經常使用類的源碼。之前大學的數據結構和算法課程在剛接觸java時候感受好像無用武之地。像c語言直接使用基礎類型,可能須要去實現鏈表,棧,隊列等。jdk都已經提供了實現類。趁最近有時間看看源碼實現。java
elementData:數據存儲的數組,任何操做都是基於這個數組。
size:集合如今實際的大小。爲何不直接使用elementData.length,若是直接使用elementData,每次插入,刪除都要操做數組的空間(代價大),因此elementData並不是每一個空間都實際用到,所以不能使用length表示大小。(後面的其餘集合對象也都是同樣的道理)node
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; private transient Object[] elementData; private int size;
1.默認數組長度10
2.指定數組長度
3.傳入現用集合複製的數組算法
public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } public ArrayList() { this(10); } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
get:判斷座標是否超出SIZE,並不是數組長度,不過只判斷是否超出,沒有判斷是否小於0,這點也奇怪。而後返回讀取數組的數據。
set:同get,判斷座標,替換數組中的元素。
add(E e):判斷當前大小是否超出數組長度或者小於0,(這裏又判斷了),超出進行擴容grow(int minCapacity),擴容大小->原來長度位移1位,實際就是*1.5,而後就是往數組[size]賦值
add(int index, E element):同上,檢查位置,不過區別在於指定座標,就要把座標以後的元素所有後移一位(這就是和LinkedList的效率差異,花銷太大),使用System.arraycopy移動,最後同樣賦值。
remove:同上,檢查位置,判斷是否最後一個元素,是的狀況,置空最後一個元素。否的狀況,同樣進行位移,把座標後面的元素所有前移一位(花銷大)。數組
public E get(int index) { rangeCheck(index); return elementData(index); } public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; }
額外說明:這裏限制數組的最大長度使用了-8,按說明是爲了給vms保留字頭安全
/** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
first,last:記錄鏈表的第一個和最後一個元素,Node雙向鏈表(next,prev)
size:集合實際大小,node的個數數據結構
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { transient int size = 0; transient Node<E> first; transient Node<E> last; 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; } }
get:判斷位置是否超出size和小於0(ArrayList就沒判斷小於0),而後計算index是在size/2的前面仍是後面,若是前面就使用first進行一個個遍歷,若是大於就使用last遍歷(相似二分查找,這裏的相對於Arraylist直接使用數組座標查找的開銷就大不少)
set:同get操做獲取node,把node的值賦成新的element。(與ArrayList相同)
add(E e):直接在尾部追加,newNode.pre = last, last.next=newNode, last=newNode;(數據結構鏈表添加)
add(int index, E element):判斷位置合法性,判斷index==size,知足就直接使用last進行追加,不知足就進行查找,添加Node進行插入(newNode在前面)
remove:判斷位置合法,查找node,node.next和node.prev(node.prev.next=node.next,node.next.prev=node.prev,代碼實現還須要一些額外判斷)app
public E get(int index) { checkElementIndex(index); return node(index).item; } public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; } public boolean add(E e) { linkLast(e); return true; } public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } public E remove(int index) { checkElementIndex(index); return unlink(node(index)); }
兩個List對比,其實和數據結構課程概括相似:ArrayList勝在get,LinkedList勝在add,remove。不過查看代碼在add(index,e),也須要使用get,這裏可能就涉及到到底查找開銷大仍是數組位移開銷大。dom
因爲HashMap涉及到hash表的構造,以及哈希衝突等之前數據結構學過的知識,就先把幾種解決哈希衝突的方法說一下
1.開放定址法(線性探測再散列,二次探測再散列,僞隨機探測再散列fi(key) = (f(key)+di) MOD m (di=1,2,3,......,m-1))
2.再哈希法(有多個不一樣的Hash函數)
3.鏈地址法(HashMap使用這種)
4.創建一個公共溢出區數據結構和算法
DEFAULT_INITIAL_CAPACITY :默認容量
MAXIMUM_CAPACITY :最大容量
DEFAULT_LOAD_FACTOR:加載因子,是一個比例,當HashMap的數據大小>=容量*加載因子時,HashMap會將容量擴容(加載因子:防止衝突過多,若是加載因子太大,致使容量一直沒有擴容,元素的衝突就很是多)
table :存儲數據的數組
size:大小(同ArrayList的size)函數
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 = 1 << 30; static final float DEFAULT_LOAD_FACTOR = 0.75f; transient Entry<K,V>[] table; transient int size;
1.直接使用默認16大小,0.75加載因子初始化
2.指定大小,默認加載因子
3.指定大小,指定加載因子
順便提一個大小的問題:咱們傳入10,初始化後大小也不會是10,在 while (capacity < initialCapacity) capacity <<= 1;經過一直位移來肯定大小,因此傳入10最後會是16。
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); init(); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
get:null特殊處理,存儲位置0。其餘計算key對應的hash值(hash算法能夠研究一下),而後和table長度取餘數,最後遍歷這個座標下面的鏈表(解決哈希衝突)進行hash值,key值比較,獲得value。
put:計算hash,計算在數組的位置(同get操做),查看該位置是否存在改key,有就替換value,沒有就新增entry(在新增的時候會判斷是否擴容,原來2倍,transfer:從新計算hash的存儲位置),每次擴容都要從新計算所有元素hash,開銷很大,因此恰當的加載因子很是重要
remove:找出座標(同get操做),遍歷鏈表找出元素,而後就是單鏈表刪除操做(pre.next=next)。
final int hash(Object k) { int h = 0; if (useAltHashing) { if (k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h = hashSeed; } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); }
indexFor:獲得hash值後與長度作一個&運算,爲何會在前面說成取餘數了?在前面說過table的長度只會是2的倍數,-1以後剩餘的二進制全是1,這時候&運算就變成和取餘數如出一轍。不少地方的位運算都很神奇。
static int indexFor(int h, int length) { return h & (length-1); }
HashTable於HashMap做比較說明
loadFactor:加載因子(同HashMap)
table :存儲數據的數組
count:大小(同HashMap的size)
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { private transient Entry<K,V>[] table; private transient int count; private float loadFactor;
默認加載因子:0.75(HashMap相同)
默認大小:11(HashMap:16)
計算實際大小也沒有像HashMap那樣位移計算直接根據傳入的長度進行初始化。
public Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); useAltHashing = sun.misc.VM.isBooted() && (initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); } public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } public Hashtable() { this(11, 0.75f); } public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
get:計算hash值(在useAltHashing上的處理與HashMap有區別),直接與長度求餘數(HashMap經過&運算),遍歷鏈表,判斷key和hash值,返回value(基本等同)。少了null的特殊判斷(不支持put.null)
put:value空異常判斷,key若是爲null在hash(key)也會拋出異常(不支持key,value爲null)。計算hash值,遍歷鏈表,若是存在就替換value,返回。不存在,新增entry(在新增的時候會判斷是否擴容rehash: newCapacity = (oldCapacity << 1) + 1;)(基本等同HashMap)
remove:計算hash值(等同get),移除元素,也是鏈表移除操做(等同HashMap)
private int hash(Object k) { if (useAltHashing) { if (k.getClass() == String.class) { return sun.misc.Hashing.stringHash32((String) k); } else { int h = hashSeed ^ k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } } else { return k.hashCode(); } } public synchronized V get(Object key) { Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return e.value; } } return null; } public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { V old = e.value; e.value = value; return old; } } modCount++; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = hash(key); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. Entry<K,V> e = tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; return null; } public synchronized V remove(Object key) { Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; V oldValue = e.value; e.value = null; return oldValue; } } return null; }
總的來講HashTable:不容許null鍵值,方法都加了synchronized,默認長度11,計算hash (hash & 0x7FFFFFFF) % tab.length;
不過如今有時候爲了保證HashMap線程安全直接使用Collections.synchronizedMap(m),比較少使用HashTable。
HashSet是Collection接口的子類,HashMap是Map接口的子類,二者在繼承上沒有關聯,可是內部又使用。
map:使用HashMap,說明全部操做都是基於HashMap,也就能夠存儲null值
PRESENT:定義了一個Object實體來當作每次操做HashMap的value值
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();
所有基於HashMap的初始化方法,提供一一對應的初始化
public HashSet() { map = new HashMap<>(); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
所有基於HashMap的操做方法,進行操做。
public boolean add(E e) { return map.put(e, PRESENT)==null; } public boolean remove(Object o) { return map.remove(o)==PRESENT; }
主要的幾個經常使用的Collection,Map都大概看了源碼,其實內部實現都是之前數據結構上學過的那些經常使用的數據結構,不過在看源碼的時候發現不少地方都是使用了位運算,畢竟位運算效率高,並且不少地方都儘可能契合二進制的計算規律。因此在看源碼的時候,能夠重點抓住:數據結構的使用,位運算的使用。