Collection

集合类咱们平时用的挺多的,今天心血来潮想看下源代码,总结一下.数组

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 就必须为之提供外同步。 

相关文章
相关标签/搜索