WangJY's Blog
深入理解java集合框架--Collection
更新时间:2020-03-18 分类:java 发布:imrookie 阅读(282) 评论(0) 字号:  

版权声明:    
作者:WangJY    
出处:http://www.imrookie.cn
   
本文版权归作者所有,转载请指明出处,否则保留追究法律责任的权利。

下图为java集合框架的结构图:
 

本文主要介绍集合大家族中的Collection这一部分。(源码版本为1.6)

1.最顶层的Collection接口

里面定义了一些抽象方法,源码如下:

package java.util;
public interface Collection<E> extends Iterable<E> {
    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);    //判断是否包含指定collection中的所有元素
    boolean addAll(Collection<? extends E> c);    //插入指定collection中的所有元素
    boolean removeAll(Collection<?> c);    //移除此collection中那些也包含在指定collectiion中的元素
    boolean retainAll(Collection<?> c);    //仅保留此collection中那些也包含在指定collection中的元素
    void clear();    //移除所有元素
    boolean equals(Object o);    //比较此collection与指定对象是否相等
    int hashCode();    //返回此collection的hash值
}

Collection接口继承了Iterable接口,内部就一个抽象方法,用来返回迭代器,源码如下:

package java.lang;
import java.util.Iterator;
public interface Iterable<T> {
    Iterator<T> iterator();    //返回迭代器
}

迭代器接口Iterator源码如下:

package java.util;
public interface Iterator<E> {
    boolean hasNext();    //是否含有下一个元素
    E next();    //返回迭代的下一个元素
    void remove();    //从迭代器指向的collection中移除迭代器返回的最后一个元素
}

·抽象类AbstractCollection,实现了Collection接口

在AbstractCollection抽象类里,实现了下列方法:

在java的API文档里面标明可选操作的方法,在子类中如果需要则需要重写该方法,否则会报new UnsupportedOperationException();

1. boolean isEmpty()方法,源码如下,这里并没有提供size()的实现,所以需要在子类中实现size()方法

public boolean isEmpty() {
	return size() == 0;
}

2. boolean contains(Object o),在该方法中,允许传入null值,如果元素中有null,则返回true

实现原理:取迭代器,如果传入null,则循环判断是否有null元素,有则true;如果传入不为null,则遍历迭代器,利用equals()方法进行判断是否相等;同样的iterator()方法需要在子类中实现.

public boolean contains(Object o) {
	Iterator<E> e = iterator();
	if (o==null) {
	    while (e.hasNext())
		if (e.next()==null)
		    return true;
	} else {
	    while (e.hasNext())
		if (o.equals(e.next()))
		    return true;
	}
	return false;
}

3. Object[] toArray()<T> T[] toArray(T[] a)方法;

4. boolean remove(Object o) 移除某元素,实现类似于contains(Object o)方法,先找到该元素,然后调用迭代器的remove()方法,然后返回true,如果没有该元素,返回false;

5. boolean containsAll(Collection<?> c) 判断全部包含,处理过程:取形参c的迭代器,遍历该形参迭代器,循环调用contains(Object o)方法判断,有一个不包含则返回false;

6. boolean addAll(Collection<? extends E> c) ,从源码看,只要有一个插入成功就返回true

public boolean addAll(Collection<? extends E> c) {
	boolean modified = false;
	Iterator<? extends E> e = c.iterator();
	while (e.hasNext()) {
	    if (add(e.next()))
		modified = true;
	}
	return modified;
}

7. boolean removeAll(Collection<?> c) 和addAll类似,有一个移除了就返回true;

8. boolean retainAll(Collection<?> c) 和removeAll类似,有一个删除就返回true;

9. void clear() 处理过程:循环调用迭代器的remove();

10. String toString() 重写了toString()方法

2.Collection接口下的3个子接口,List,Set,Queue

2.1 List接口

List接口继承了Collection接口,源码如下(继承自Collection接口的方法此处省略):

package java.util;
public interface List<E> extends Collection<E> {
    //...继承自Collection的抽象方法省略
    E get(int index);    //返回列表中指定位置的元素
    E set(int index, E element);    //用指定元素替换列表中指定位置的元素
    void add(int index, E element);    //在列表的指定位置插入元素
    E remove(int index);    //删除指定位置上的元素
    int indexOf(Object o);    //返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1
    int lastIndexOf(Object o);    //返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1
    ListIterator<E> listIterator();    //返回此列表元素的列表迭代器(按适当顺序)
    ListIterator<E> listIterator(int index);    //返回列表中元素的列表迭代器(按适当顺序),从列表的指定位置开始
    List<E> subList(int fromIndex, int toIndex);    //返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的子列表
}

可以看到,listIterator()方法返回了list列表迭代器,来看下源码:

ListIterator接口:

ListIterator列表迭代器接口,继承了Iterator接口:

package java.util;
public interface ListIterator<E> extends Iterator<E> {
    //继承自Iterator接口的方法就不显示了
    boolean hasNext();    
    E next();
    boolean hasPrevious();    //如果以逆向遍历列表,列表迭代器有多个元素,则返回 true,也就是判断是否存在上一个元素
    E previous();    //返回上一个元素
    int nextIndex();    //下一个元素的索引
    int previousIndex();    //上一个元素的索引
    void remove();    //从列表中移除由 next 或 previous 返回的最后一个元素
    void set(E e);    //用指定元素替换next或previous返回的最后一个元素
    void add(E e);    //将指定的元素插入列表
}

抽象类AbstractList

AbstractList继承自AbstractCollection,实现了List接口

AbstractList抽象类中,实现了iterator()方法和listIterator()方法,因为很多方法基于这两个方法来实现,比如AbstractList里的indexOf和lastIndexOf方法等;

1.内部类Itr,实现了Iterator接口,一起看下源码(分析写在注释部分):

private class Itr implements Iterator<E> {
	int cursor = 0;    //当前游标位置
	int lastRet = -1;    //上一个操作的元素索引
	int expectedModCount = modCount;    //预期修改数,初始化为外部类的修改数的值,用来作并发判断
	public boolean hasNext() {
        return cursor != size();
	}
    //可以看出,iterator中并没有元素容器,他是靠cursor属性和lastRet属性来控制读取和删除操作的
	public E next() {
            checkForComodification();
	    try {
		E next = get(cursor);
		lastRet = cursor++;
		return next;
	    } catch (IndexOutOfBoundsException e) {
		checkForComodification();
		throw new NoSuchElementException();
	    }
	}
    //注意: 不可以连续使用该方法,否则会因为lastRet属性被重置为-1而报异常
	public void remove() {
	    if (lastRet == -1)
		throw new IllegalStateException();
            checkForComodification();
	    try {
		AbstractList.this.remove(lastRet);
		if (lastRet < cursor)
		    cursor--;
		lastRet = -1;
		expectedModCount = modCount;
	    } catch (IndexOutOfBoundsException e) {
		throw new ConcurrentModificationException();
	    }
	}
    //此用法用来判断外部修改数和iterator修改数是否一致,不一致会报异常
    //所以,在使用iterator期间不可以用非iterator方法对list做操作
    final void checkForComodification() {
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();
	}
}

注意: 在list中使用iterator时千万不要使用非iterator方法对list进行改动,否则会报异常:java.util.ConcurrentModificationException

2.内部类ListItr,继承了Itr,实现了ListIterator接口;

在Itr的基础上,多了一些方法,支持向前读,插入,set等操作;

提供了一个带参构造函数,设置cursor参数的值.

private class ListItr extends Itr implements ListIterator<E> {
	ListItr(int index) {
	    cursor = index;
	}
	public boolean hasPrevious() {
	    return cursor != 0;
	}
        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
	public int nextIndex() {
	    return cursor;
	}
	public int previousIndex() {
	    return cursor-1;
	}
	public void set(E e) {
	    if (lastRet == -1)
		throw new IllegalStateException();
            checkForComodification();
	    try {
		AbstractList.this.set(lastRet, e);
		expectedModCount = modCount;
	    } catch (IndexOutOfBoundsException ex) {
		throw new ConcurrentModificationException();
	    }
	}
	public void add(E e) {
            checkForComodification();
	    try {
		AbstractList.this.add(cursor++, e);    //在当前游标索引处插入元素,然后光标自增1
		lastRet = -1;
		expectedModCount = modCount;
	    } catch (IndexOutOfBoundsException ex) {
		throw new ConcurrentModificationException();
	    }
	}
}

2.1.1 ArrayList类

   ArrayList类继承自AbstractList类,实现了List,RandomAccess,Cloneable,java.io.Serializable接口,是可序列化的.

   在ArrayList中使用对象数组来作为存储容器,使用transient关键字修饰,transient关键字具有以下特征:

1、transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。

2、被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

3、一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。也可以认为在将持久化的对象反序列化后,被transient修饰的变量将按照普通类成员变量一样被初始化


ArrayList类提供了3个构造方法:

1.带参构造方法,参数为容器初始化大小

2.无参构造方法,默认容器大小为10,jdk1.8源码中默认为一个静态的空数组,当第一次进行add操作时,在确保容量的方法中设置为默认大小(10)

3.带参构造方法,参数为Collection实例


ArrayList类中的重要属性包括:

private transient Object[] elementData;    //容器
private int size;    //记录元素个数
protected transient int modCount = 0;    //结构上改动的次数,例如扩容操作,继承自AbstractList父类

ArrayList类中的常用方法:

1. 扩容 public void ensureCapacity(int minCapacity)

从源码中可以看出,当传入参数大于当前容量时有效,该方法扩容至少为 原容量的1.5倍+1并取整

public void ensureCapacity(int minCapacity) {
	modCount++;
	int oldCapacity = elementData.length;
	if (minCapacity > oldCapacity) {
	    Object oldData[] = elementData;
	    int newCapacity = (oldCapacity * 3)/2 + 1;
    	    if (newCapacity < minCapacity)
		newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
	}
}

2. 将此 ArrayList 实例的容量调整为列表的当前大小 public void trimToSize()

也就是把容器的大小调整为size属性大小

3. indexOf(Object o) 返回某对象在数组容器中第一次出现的索引,没有该元素则返回-1

参数可以是null

4. lastIndexOf(Object o) 返回某对象在数组容器中最后一次出现的索引,没有该元素则返回-1

参数可以是null

5. 克隆方法Object clone()

因为超类中的clone()方法是浅拷贝的,所以要实现深拷贝,要在super.clone()之后,将引用变量重新赋值;

public Object clone() {
	try {
	    ArrayList<E> v = (ArrayList<E>) super.clone();
	    v.elementData = Arrays.copyOf(elementData, size);
	    v.modCount = 0;
	    return v;
	} catch (CloneNotSupportedException e) {
	    // this shouldn't happen, since we are Cloneable
	    throw new InternalError();
	}
}

6. 转为数组 

Object[] toArray() 和 

public <T> T[] toArray(T[] a)

7. 取值 get(int index)

8. 设置某索引对应的元素 set(int index, E element)

该方法返回该索引上的旧值

 在末尾添加元素 boolean add(E e)

9. 在指定索引位置插入元素 add(int index, E element)

原理: 数组容器从该索引位置整体向后挪动1位,然后给改索引位置赋值

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 复制指定的数组到目标数组

参数1: 要复制的原始数组

参数2: 从哪开始拷贝

参数3: 目的地数组

参数4: 开始存放的位置,也就是复制的元素从哪个索引开始放

参数5: 拷贝长度

public void add(int index, E element) {
	if (index > size || index < 0)
	    throw new IndexOutOfBoundsException(
		"Index: "+index+", Size: "+size);
	ensureCapacity(size+1);  // Increments modCount!!
	System.arraycopy(elementData, index, elementData, index + 1,
			 size - index);
	elementData[index] = element;
	size++;
}

类似的还有:

插入全部 addAll(Collection<? extends E> c)

从指定索引开始插入全部  addAll(int index, Collection<? extends E> c)

10. 移除某个索引上的元素remove(int index),返回被移除的元素

和上面的方法一样,需要整体移动数组容器;

同样的:

remove(Object o)方法是先找到对应元素的索引,然后整体移动

removeRange(int fromIndex, int toIndex) 移除索引fromIndex到toIndex的元素

11. 清空集合 clear()

循环给数组容器的元素赋值null

ArrayList中没有重写iterator()方法和listIterator()方法,继承的父类AbstractList中的方法.


2.1.2 LinkedList类

双向链表的数据结构.

类中有一个核心属性header,他是列表的头,初始化时会将上下节点都指向自身(在jdk1.8中header改为了first和last,分别记录链表的头和尾

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    private transient Entry<E> header = new Entry<E>(null, null, null);
    private transient int size = 0;
    public LinkedList() {
        header.next = header.previous = header;
    }
...

存放元素的容器为Entry对象(jdk1.8中为Node,内部属性类似,构造函数中参数顺序变为上一个-当前-下一个),Entry是LinkedList的静态内部类,源码如下:

private static class Entry<E> {
	E element;    //存放元素本身
	Entry<E> next;    //指向下一个Entry
	Entry<E> previous;    //上一个Entry
    //构造函数形参分别表示: 元素,下一个Entry,上一个Entry
	Entry(E element, Entry<E> next, Entry<E> previous) {
	    this.element = element;
	    this.next = next;
	    this.previous = previous;
	}
}

插入数据核心方法 addBefore(E e,Entry<E> entry),在某个entry前插入元素:

private Entry<E> addBefore(E e, Entry<E> entry) {
	Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
	newEntry.previous.next = newEntry;    //将上一个节点的下节点指向e
	newEntry.next.previous = newEntry;    //将下一个节点的上节点指向e
	size++;
	modCount++;
	return newEntry;
}

add(e)方法就是通过 addBefore(e,header)在header节点前插入 实现的

get(int index) 根据索引查找元素时会判断header正向和反向哪个离索引更近,然后向更近的方向遍历,源码如下:

//调用私有方法entry(i)找到对应索引的节点
public E get(int index) {
        return entry(index).element;
}
private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        //>>位移运算,可以理解为除以2
        //当索引在前半部分,就遍历下个节点,一直遍历到该索引的节点
        //在后半部分,就遍历上个节点
        if (index < (size >> 1)) {    
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
}

删除元素核心方法如下:

删除元素,不论是删除第一个还是最后一个,又或是根据索引删除,或者根据元素删除,步骤大致如下:

1.找到该元素对应的节点,然后调用remove(Object o)方法,该方法核心步骤主要做2,3两步操作;

2.让该节点的上下节点互相指向对方;

3.然后置空该节点的元素和上下节点应用;

注意:该方法会返回被删除元素

private E remove(Entry<E> e) {
	if (e == header)
	    throw new NoSuchElementException();
        E result = e.element;
    //下面2行让上下节点互相指向对方
	e.previous.next = e.next;
	e.next.previous = e.previous;
    //置空被删除节点的属性
        e.next = e.previous = null;
        e.element = null;
	size--;
	modCount++;
        return result;
}

LinkedList重写了listIterator(int index)方法,内部也存在个私有的内部类ListItr,和AbstractList抽象类中的实现类似,只是由操作索引换成了操作Entry节点,内部还有个nextIndex属性,记录下一个元素的索引,还有个next属性,记录下一个节点.


与ArrayList类似,连续调用remove会报异常IllegalStateException(jdk1.8)

2.2 Set接口

首先,来看下Set接口源码:

package java.util;
public interface Set<E> extends Collection<E> {
    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 retainAll(Collection<?> c);
    boolean removeAll(Collection<?> c);
    void clear();
    boolean equals(Object o);
    int hashCode();
}

没啥特殊的方法.基本全是Collection里面的.

AbstractSet抽象类

继承了AbstractCollection抽象类,实现了Set接口

里面就3个方法hashCode(),equals(),removeAll(ollection<?> c)

package java.util;
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
    protected AbstractSet() {
    }
    public boolean equals(Object o) {
	if (o == this)
	    return true;
	if (!(o instanceof Set))
	    return false;
	Collection c = (Collection) o;
	if (c.size() != size())
	    return false;
        try {
            return containsAll(c);
        } catch (ClassCastException unused)   {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }
    }
    public int hashCode() {
	int h = 0;
	Iterator<E> i = iterator();
	while (i.hasNext()) {
	    E obj = i.next();
            if (obj != null)
                h += obj.hashCode();
        }
	return h;
    }
    public boolean removeAll(Collection<?> c) {
        boolean modified = false;
        if (size() > c.size()) {
            for (Iterator<?> i = c.iterator(); i.hasNext(); )
                modified |= remove(i.next());    // a |= b; 相当于 a = a | b;
        } else {
            for (Iterator<?> i = iterator(); i.hasNext(); ) {
                if (c.contains(i.next())) {
                    i.remove();
                    modified = true;
                }
            }
        }
        return modified;
    }
}

2.2.1 HashSet类

继承了AbstractSet抽象类,实现了Set,Cloneable,和序列化接口

基于HashMap实现的,关于HashMap的介绍看 ★★集合框架--Map

package java.util;
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;
    
    private static final Object PRESENT = new Object();
    
    public HashSet() {
	map = new HashMap<E,Object>();
    }
    
    public HashSet(Collection<? extends E> c) {
	map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
	addAll(c);
    }
    
    public HashSet(int initialCapacity, float loadFactor) {
	map = new HashMap<E,Object>(initialCapacity, loadFactor);
    }
    
    public HashSet(int initialCapacity) {
	map = new HashMap<E,Object>(initialCapacity);
    }
    
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
	map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
    }
    
    public Iterator<E> iterator() {
	return map.keySet().iterator();
    }
    
    public int size() {
	return map.size();
    }
    
    public boolean isEmpty() {
	return map.isEmpty();
    }
    
    public boolean contains(Object o) {
	return map.containsKey(o);
    }
    
    public boolean add(E e) {
	return map.put(e, PRESENT)==null;
    }
    
    public boolean remove(Object o) {
	return map.remove(o)==PRESENT;
    }
    
    public void clear() {
	map.clear();
    }
    
    public Object clone() {
	try {
	    HashSet<E> newSet = (HashSet<E>) super.clone();
	    newSet.map = (HashMap<E, Object>) map.clone();
	    return newSet;
	} catch (CloneNotSupportedException e) {
	    throw new InternalError();
	}
    }
    
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
	
	s.defaultWriteObject();
        
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());
        
        s.writeInt(map.size());
	
	for (Iterator i=map.keySet().iterator(); i.hasNext(); )
            s.writeObject(i.next());
    }
    
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
	
	s.defaultReadObject();
        
        int capacity = s.readInt();
        float loadFactor = s.readFloat();
        map = (((HashSet)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));
        
        int size = s.readInt();
	
	for (int i=0; i<size; i++) {
            E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }
}


评论
0 人参与, 0 条评论
·联系方式

Q Q:243144837

Mail:243144837@qq.com

码云:https://gitee.com/I_M_ROOKIE

·公告

大家好,我是公告!

©2018 WangJY | 元码-轻云低代码 | 晋ICP备18002524号-1

晋公网安备 14050002000652号