集合类咱们平时用的挺多的,今天心血来潮想看下源代码,总结一下.数组
List、Set、Map是这个集合体系中最主要的三个接口。 List和Set继承自Collection接口。 Map也属于集合系统,但和Collection接口不一样。安全
1.Collection:数据结构
(1)Collection继承Iterable函数
int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode();
具体怎么实如今下面细说性能
(2)List(有序、可重复)this
List里存放的对象是有序的,同时也是能够重复的,List关注的是索引,拥有一系列和索引相关的方法,查询速度快。由于往list集合里插入或删除数据时,会伴随着后面数据的移动,全部插入删除数据速度慢。加密
1)ArrayList:基于动态数组(其实底层就是个数组),便于查找,不便于增删线程
ps:缘由是数据只要给定索引就能够直接获得结果,可是增删的话,就要移动后面的全部元素code
例子:增长元素:add(E e),add(int index, E element)对象
public void add(int index, E element) { rangeCheckForAdd(index);//判断是否大于数组下标或者小于0 ensureCapacityInternal(size + 1); // 修改次数,fail_fast机制 System.arraycopy(elementData, index, elementData, index + 1, size - index);//拷贝数组,把插入位置的后面数据日后推一位 参数含义(原数组,从原数组的目标位开始,目标数组,目标数组的起始位置,要copy的数据长度) elementData[index] = element;//插入数据 size++; }
2)LinkList:基于链表,便于增删,不便于查找
ps:缘由是LinkList在内存里面是离散的,不是连续的,并且每个元素都有下一个元素的引用,增删的话只要修改前一个元素的引用指向增长元素,增长元素指向下一个元素.查找的话要从第一个元素找逐个找到目标元素.
例子:
先看看链表:
//结点元素 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; } }
仍是以add为例子来说add()方法里面调用下面方法:
//在最后加 void linkLast(E e) { final Node<E> l = last;//把原来的尾元素赋值给l final Node<E> newNode = new Node<>(l, e, null);//建立一个新的结点,头元素指向原来的元素 last = newNode;//把原来的尾元素指向新建立的结点 if (l == null)//判断是否是第一次添加结点 first = newNode;//头元素就是建立的结点 else l.next = newNode;//末尾的尾元素建立的结点(末尾的尾元素指向本元素) size++; modCount++; }
//在中间加 listA.add(1,"rick"); void linkBefore(E e, Node<E> succ(index=1 所在的元素,下面简称x元素)) { // assert succ != null; final Node<E> pred = succ.prev;//拿出x元素的头元素 final Node<E> newNode = new Node<>(pred, e, succ);//建立新结点,头元素指向x元素的头元素 succ.prev = newNode;//x元素的头元素指向新建立的元素 if (pred == null)//判断是否是第一次添加 first = newNode;//头元素指向本元素 else pred.next = newNode;//头元素指向新建立的元素 size++; modCount++; }
3)Vector:ArrayList的线程安全版,可是性能较低,Vector的方法都是synchronized的,因此是线程安全的。当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍。arrayList是是增长容量的一半.
4)Stack 继承了Vector,因此他们也是基于数组的,依赖于有序得以实现
class Stack<E> extends Vector<E>
public E push(E item) {//入栈 addElement(item); return item; } public synchronized E pop() {//出栈 E obj; int len = size(); obj = peek(); removeElementAt(len - 1); return obj; } public synchronized E peek() {//获取栈顶元素 int len = size(); if (len == 0) throw new EmptyStackException(); return elementAt(len - 1); } public boolean empty() {//判断栈长度是否等于0 return size() == 0; } public synchronized int search(Object o) {//查找栈元素信息 int i = lastIndexOf(o); if (i >= 0) { return size() - i; } return -1; }
(3)Set(无序、不能重复)
Set里存放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单地把对象加入集合中.
1)HashSet
//构造方法 /** * 初始容量是16,扩张系数是0.75 */ public HashSet() { map = new HashMap<>(); }
证实:set是基于HashMap的
仍是以add方法做为例子来了解set:
//set的add()方法 private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; }
以添加元素为key,静态变量对象PRESENT为value.
由此咱们了解到:由于添加元素是key,根据map 的特性,咱们能够推出,set是无序,不能重复的.
2)TreeSet
public TreeSet() { this(new TreeMap<E,Object>()); }
TreeSet(NavigableMap<E,Object> m) { this.m = m; }
证实:TreeSet是基于TreeMap,具体咱们先去看看map,而后再返回来看这二者具体区别在哪里.
2.Map(键值对、键惟1、值不惟一)
Map集合中存储的是键值对,键不能重复,值能够重复。根据键获得值,对map集合遍历时先获得键的set集合,对set集合进行遍历,获得相应的值。
(1)Map接口:
int size(); boolean isEmpty(); boolean containsKey(Object key); boolean containsValue(Object value); V get(Object key); V put(K key, V value); V remove(Object key); void putAll(Map<? extends K, ? extends V> m); void clear(); Set<K> keySet(); Collection<V> values();
1)HashMap
//构造函数 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR;//初始容量,默认16 //DEFAULT_INITIAL_CAPACITY 是加载因子,threshold 是极限值 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); }
初始容量只是哈希表在建立时的容量。加载因子 是哈希表在其容量自动增长以前能够达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,经过调用 rehash 方法将容量翻倍。
而后来看看Entry类:
//截取部分 static class Entry<K,V> implements Map.Entry<K,V> { final K key;//键 V value;//值 Entry<K,V> next;//下一个元素 final int hash; *** }
HashMap 的底层是Entry数组,key是下标,由下列方法计算得出,value是Entry对象(就是插入的对象)
int hash = hash(key.hashCode()); int i = indexFor(hash, table.length);
static int indexFor(int h, int length) { return h & (length-1);//与操做 }
总结:Entry就是一个键值对,包含下个元素
而后咱们来说下这个hash:
hashCode方法就是根据必定的规则将与对象相关的信息(好比对象的存储地址,对象的字段等)映射成一个数值,这个数值称做为散列值.感受跟文件md5加密原理相似.
为何存在这个hashCode():(从网上看到的解释)
考虑一种状况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不容许重复的元素存在)
也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。可是若是集合中已经存在一万条数据或者更多的数据,若是采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的做用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,获得对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,若是table中没有该hashcode值,它就能够直接存进去,不用再进行任何比较了;若是存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,因此这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大下降了
总得来讲,就是为了判断实现集合不容许存在重复元素的一种比较高效的方法.
接下来咱们仍是拿put方法来看下:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode());//按照必定的规则进行运算取得hash值 int i = indexFor(hash, table.length);//进行与操做获得table(Entry的数组)的下标 //key已存在 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; }
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; //注册到table数组里面,若是下标所在位置已经有值,就把他设置为next table[bucketIndex] = new Entry<>(hash, key, value, e); //若是超过极限值.容量翻倍 if (size++ >= threshold) resize(2 * table.length); }
索性把hash()这个方法也说下吧:
static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
静态方法,h>>>20 的意思是 h/(2^20)
2)TreeMap
首先咱们先要明白一些定义:TreeMap底层采用一棵红黑树(自平衡排序二叉树,NavigableMap)来保存集合中的 Entry,每次进行增删都要经过不断循环来找到相应的元素,因此TreeMap比HashMap效率低,可是他的优点在于TreeMap 中的全部 Entry 老是按 key 根据指定排序规则保持有序状态.
构造方法:
public TreeMap() { comparator = null; }
public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
能够自定义排序规则,若是不指定,按照默认,compareTo()方法在Comparator接口定义,具体实现按照继承类不一样而不一样.
private final Comparator<? super K> comparator; final int compare(Object k1, Object k2) { return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2); }
//红黑树 static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left = null; Entry<K,V> right = null; Entry<K,V> parent; boolean color = BLACK; ... }
而后咱们仍是从增长方法来看:
private transient Entry<K,V> root = null;//根节点 public V put(K key, V value) { Entry<K,V> t = root; if (t == null) {//判断是不是第一次添加 compare(key, key); // type (and possibly null) check 类型检查(有多是空) root = new Entry<>(key, value, null);//把第一次插入的值设为根节点 size = 1; modCount++; return null; } //再次插入 int cmp; Entry<K,V> parent;//父类结点 // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { do {//循环找到增长的未知 parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key);//默认比较方法 if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry<K,V> e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e);//修复红黑树 size++; modCount++; return null; }
ps:比较HashMpa()和TreeMap()的区别:
(1)HashMap:基于哈希表实现,TreeMap基于红黑树
(2)HashMap :适用于在Map中插入、删除和定位元素。Treemap:适用于按天然顺序或自定义顺序遍历键(key)。
(可能不全,之后再发现回来补充)
HashMap一般比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在须要排序的Map时候才用TreeMap。
3)LinkedHashMap
继承HashMap
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{}
构造函数:
public LinkedHashMap() { super(); accessOrder = false;//false:基于插入顺序 true:基于访问顺序 }
private static class Entry<K,V> extends HashMap.Entry<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); } ... }
从Entry咱们能够看到,LinkedHashMap是基于链表结构,包含头元素,尾元素,默认是按照插入元素排序,若是设置accessOrder为true,排序基于访问顺序
4)HashTable
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap容许空(null)键值(key),因为非线程安全,效率上可能高于Hashtable。
HashTable 继承自Dictionary抽象类
public abstract class Dictionary<K,V> { public Dictionary() { } abstract public int size(); abstract public boolean isEmpty(); abstract public Enumeration<K> keys(); abstract public Enumeration<V> elements(); abstract public V get(Object key); abstract public V put(K key, V value); abstract public V remove(Object key); }
public synchronized V put(K key, V value) {//线程安全 if (value == null) { throw new NullPointerException(); }//值不能为空 // Makes sure the key is not already in the hashtable. Entry tab[] = table; int hash = key.hashCode();//若是key为null,会报错,ps:只有对象才有hashCode() ... }
1.HashMap容许将null做为一个entry的key或者value,而Hashtable不容许。 2.HashMap把Hashtable的contains方法去掉了,改为containsvalue和containsKey。由于contains方法容易让人引发误解。 3.Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。 4.最大的不一样是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不须要本身为它的方法实现同步,而HashMap 就必须为之提供外同步。