工做了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都大概看了源码,其实内部实现都是之前数据结构上学过的那些经常使用的数据结构,不过在看源码的时候发现不少地方都是使用了位运算,毕竟位运算效率高,并且不少地方都尽可能契合二进制的计算规律。因此在看源码的时候,能够重点抓住:数据结构的使用,位运算的使用。