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(有序、可重复)

List里存放的对象是有序的,同时也是可以重复的,List关注的是索引,拥有一系列和索引相关的方法,查询速度快。因为往list集合里插入或删除数据时,会伴随着后面数据的移动,所有插入删除数据速度慢。

1)ArrayList:基于动态数组(其实底层就是个数组),便于查找,不便于增删

ps:原因是数据只要给定索引就可以直接得到结果,但是增删的话,就要移动后面的所有元素

例子:增加元素: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(键值对、键唯一、值不唯一)

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

 

转载于:https://my.oschina.net/u/3744319/blog/1608942

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值