java源码学习7-Collection

来源:互联网 发布:外国人羡慕中国淘宝 编辑:程序博客网 时间:2024/06/05 03:11

java的集合框架,

首先列出大体的集合框架图片


从上往下依次进行讲解

1、首先我们引出 一个超类 Iterable  

terable里面就一个 方法,Iterator<T> iterator();,该方法返回一个迭代器,用于集合中各元素的遍历

2、正视我们的Collection,我先把类图贴出来


Collection  作为接口,定义的这些集合操作方法,这块没有什么需要注意的。

3、看看我们更加细分的List,Set 和Queue 三个集合接口

(1)List  有序集合


List集成自Collection,多出来几个方法,

a、addAll方法,在指定位置添加一个集合

b、get  set   add   remove方法,获取指定位置的元素,在指定位置替换元素、在指定位置插入元素,移除指定位置的元素,就是我们常说的增删查改方法

c、indexOf,lastIndexOf,获取指定对象在List中出现的位置和最后出现的位置

d、listIterator 返回一个迭代对象,用于遍历List中的元素

e、subList,获得子List


(2)Set  无序集合


基本上完全覆盖了Collection要实现的方法

(3)Queue  队列


Queue(队列)继承自Collection,多出来几个用于队列操作的方法,分别是

a、offer,插入指定的元素,此方法特殊的地方,是优于add进行插入

b、peek,element,获取但不移除此队列的头,如果队列为空,返回null

c、poll,remove,获取并移除此队列的头,

4、ArrayList,LinkList,Vector,Stack

(1)ArrayList基本上是我们最常用的集合类之一,,首先贴一下类图:


ArrayList的数据结构是一个数组,ArrayList中关于集合的操作都是对这个数组进行操作。

a、常用的方法如 空构造方法,add,get,remove,size,isEmpty这些都是 我们比较常用的

b、看它包含的内部类,Itr,ListItr,SubList,前两者是Iterator的实现,后者是AbstractList的子类,AbstractList实现了List接口,暂不多述。

c、成员变量关注的包括:

DEFAULT_CAPACITY:默认的初始容量,为10

EMPTY_ELEMENTDATA:空元素数组

elementData:数组列表

size:元素的个数

modCount: 继承自AbstractList,该字段标识已从结构上修改列表的次数

d、我们通过一段代码来显示在我们日常的应用中ArrayList各方法的调用情况,代码如下:

List l=new ArrayList();l.add("abc");l.add(new Student());

下面贴上ArrayList中的方法调用情况:


基本上我们使用ArrayList的时候,大部分的开销都在扩展数组的操作上。如果需要频繁的进行扩展数组,除了性能上有开销,安全上也可能会有问题,需要多加注意的是modCount这个变量,它的作用是标记当前集合被操作的次数,当我们在遍历集合的时候,如果modCount改变,则会抛出一个ConcurrentModificationException的异常,

e、下面看一下遍历用到的方法和数据结构:

public ListIterator<E> listIterator(int index) {        if (index < 0 || index > size)            throw new IndexOutOfBoundsException("Index: "+index);        return new ListItr(index);    }       public ListIterator<E> listIterator() {        return new ListItr(0);    }       public Iterator<E> iterator() {        return new Itr();    }

Itr是ArrayList的内部类,

<span style="white-space:pre"></span>int cursor;       // index of next element to return        int lastRet = -1; // index of last element returned; -1 if no such        int expectedModCount = modCount;        public boolean hasNext() {   }         public E next() {  }        public void remove() { }        final void checkForComodification() {            if (modCount != expectedModCount)                throw new ConcurrentModificationException();        }

Itr定义了三个变量,cursor用于遍历的时候指向下一个元素,判断列表是否还有下一个元素,lastRet,主要是用于在删除元素的时候标记元素的位置,
expectedModCount 用于在执行遍历或者移除元素的过程中,通过与modCount进行比较,判断列表是否被修改。
ListItr继承自Itr,比Itr多出来的操作则是向前遍历,添加和设置元素。

(2)LinkedList


LinkedList与ArrayList需要操作的数据结构不同,LinkedList操作的是Node节点,而ArrayList操作的是数组元素

Node 结构如下:

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;        }    }
LinkedList对于数据的增删改查,没有像ArrayList扩展数组那样的消耗,所以对于频繁的插入删除,LinkedList的性能优于ArrayList,对于查询操作,个人觉得两者没有多大区别,基本上都是在线性时间内。

LinkedList与ArrayList的区别,这里附上网络上的一篇分析:

http://pengcqu.iteye.com/blog/502676

(3)Vector

Vector与Arraylist的不同之处在于,Vector是线程安全的,我们查看源码,可以看到Vector的很多方法都是synchronized 修饰,

另外Vector可以设置增长因子,我们的ArrayList在扩展数组的时候,他的增长量是这么计算的:

private void grow(int minCapacity) {        // overflow-conscious code        int oldCapacity = elementData.length;        int newCapacity = oldCapacity + (oldCapacity >> 1);        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)            newCapacity = hugeCapacity(minCapacity);        // minCapacity is usually close to size, so this is a win:        elementData = Arrays.copyOf(elementData, newCapacity);    }

假设oldCapacity的大小是10,用二进制表示是1010,  1010 >>1 变成0101 换成十进制则是5基本上可以理解为扩展为原来的1.5倍 10-->15-->22+33  可以看成近似的等比数列

再来看Vector的grow方法

private void grow(int minCapacity) {        // overflow-conscious code        int oldCapacity = elementData.length;        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?                                         capacityIncrement : oldCapacity);        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)            newCapacity = hugeCapacity(minCapacity);        elementData = Arrays.copyOf(elementData, newCapacity);    }

可以看出来,如果我们在使用Vector的时候,如果指定了capacityIncrement,即我们使用如下的创建方式

Vector v=newVector(10,5);

那么它的初始大小是10,增量大小为5,基本上每次调用grow的时候,就是每次扩展的时候,它的大小都是在原先的基础上加上5, 10-->15-->20-->25可以理解为等差数列

以上两点,基本上是Vector与ArrayList最为明显的不同之处


(4)Stack

该类继承自Vector,并且类也比较简单,贴一下代码:

public   class Stack<E> extends Vector<E> {    public Stack() {    }    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() {        return size() == 0;    }    public synchronized int search(Object o) {        int i = lastIndexOf(o);        if (i >= 0) {            return size() - i;        }        return -1;    }    private static final long serialVersionUID = 1224463164541339165L;}
Stack可以理解为是一个栈的数据结构,该数据结构依赖于数组的实现,栈的操作 基本上就是 入栈出栈查询操作。回归到数组上,基本上操作的都是数组的最后一个元素。

5、Map

说到集合Collection,另一种数据结构也不得不提,Map在我们平时的开发过程中也是经常使用的一种集合,Map存储方式是以键值对的形式存储,Map接口除了定义一些操作方法,也定义了实现该Map数据结构的接口Entry

interface Entry<K,V> {             K getKey();               V getValue();              V setValue(V value);               boolean equals(Object o);               int hashCode();    }
基本上如果需要Map的具体实现都需要定义自己的Entry实现类,

这里先说一下HashMap

6、HashMap



(1)HashMap的基础数据结构是Entry的数组table,

再来看下Entry的具体实现

static class Entry<K,V> implements Map.Entry<K,V> {        final K key;        V value;        Entry<K,V> next;        int hash;              Entry(int h, K k, V v, Entry<K,V> n) {            value = v;            next = n;            key = k;            hash = h;        }        public final K getKey() {            return key;        }        public final V getValue() {            return value;        }        public final V setValue(V newValue) {            V oldValue = value;            value = newValue;            return oldValue;        }        public final boolean equals(Object o) {            if (!(o instanceof Map.Entry))                return false;            Map.Entry e = (Map.Entry)o;            Object k1 = getKey();            Object k2 = e.getKey();            if (k1 == k2 || (k1 != null && k1.equals(k2))) {                Object v1 = getValue();                Object v2 = e.getValue();                if (v1 == v2 || (v1 != null && v1.equals(v2)))                    return true;            }            return false;        }        public final int hashCode() {            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());        }        public final String toString() {            return getKey() + "=" + getValue();        }        void recordAccess(HashMap<K,V> m) {        }               void recordRemoval(HashMap<K,V> m) {        }    }

Entry也可以看成是一个类似链表的数据结构,

拿以下代码来看看我们平时使用HashMap做了哪些工作

<span style="white-space:pre"></span>Map<String,Object> map=new HashMap<String,Object>();map.put("zxl", 18);map.put("z", 22);Integer old=(Integer) map.get("zxl");

对比Arraylist的add方法,不同的地方则在于数据结构的不同,ArrayList直接维护Object的数组即可,而HashMap需要维护的则是Entry的数组,Entry可以理解为类似于链表的结构,在HashMap调用put方法的时候,数组的大小也会面临扩展的问题。

如果我们需要通过key查询某个值,则首先计算key 的hash,并且根据hash找到在table的索引,从table索引的位置开始,依次遍历Entry,知道找到符合key的Entry,返回他的key。

(2)EntrySet

平时我们需要遍历Map的时候,可能会进行如下操作:

<span style="white-space:pre"></span>for(Entry<String,Object> entry:map.entrySet()){System.out.println(entry.getKey()+entry.getValue());}
而map.entrySet()方法代码如下:

    private transient Set<Map.Entry<K,V>> entrySet = null;
    public Set<Map.Entry<K,V>> entrySet() {        return entrySet0();    }    private Set<Map.Entry<K,V>> entrySet0() {        Set<Map.Entry<K,V>> es = entrySet;        return es != null ? es : (entrySet = new EntrySet());    }

再来看下内部类EntrySet

  private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {        public Iterator<Map.Entry<K,V>> iterator() {            return newEntryIterator();        }        public boolean contains(Object o) {            if (!(o instanceof Map.Entry))                return false;            Map.Entry<K,V> e = (Map.Entry<K,V>) o;            Entry<K,V> candidate = getEntry(e.getKey());            return candidate != null && candidate.equals(e);        }        public boolean remove(Object o) {            return removeMapping(o) != null;        }        public int size() {            return size;        }        public void clear() {            HashMap.this.clear();        }    }

(3)Values

private final class Values extends AbstractCollection<V> {        public Iterator<V> iterator() {            return newValueIterator();        }        public int size() {            return size;        }        public boolean contains(Object o) {            return containsValue(o);        }        public void clear() {            HashMap.this.clear();        }    }public Collection<V> values() {        Collection<V> vs = values;        return (vs != null ? vs : (values = new Values()));    }

7、HashTable

HashTable与HashMap最大的不同是HashTable的方法是同步的,

HashTable的很多方法都有synchronized的关键字,

但是我们可以用Collections.synchronizedMap(Map)方法来使HashMap实现被同步。

在HashTable中,声明的entrySet,keySet,values都是volatile的,并且以entrySet为例,

在调用entrySet()方法的时候,如果为空,返回的也是一个被同步的对象,代码如下

 public Set<Map.Entry<K,V>> entrySet() {        if (entrySet==null)            entrySet = Collections.synchronizedSet(new EntrySet(), this);        return entrySet;    }

8、ConcurrentHashMap

(1)数据结构

concurrentHashMap维护的是一个数组,Segment<K,V>[] segments;

而SegMent操作的也是一个数组,HashEntry<K,V>[] table;

接下来HashEntry的结构和我们的HashMap里的Entry类似

static final class HashEntry<K,V> {        final int hash;        final K key;        volatile V value;        volatile HashEntry<K,V> next;        HashEntry(int hash, K key, V value, HashEntry<K,V> next) {            this.hash = hash;            this.key = key;            this.value = value;            this.next = next;        }
}

关于数据结构更为细致的讨论,这里粘贴一篇文章,讲的很透彻

https://my.oschina.net/zhenglingfei/blog/400515

(2)同步分析,

在进行get操作的时候,我们看下源码

    public V get(Object key) {        Segment<K,V> s; // manually integrate access methods to reduce overhead        HashEntry<K,V>[] tab;        int h = hash(key);        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&            (tab = s.table) != null) {            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);                 e != null; e = e.next) {                K k;                if ((k = e.key) == key || (e.hash == h && key.equals(k)))                    return e.value;            }        }        return null;    }

首先也是计算hash,找到对应的Segment,

然后遍历Segment中的table,(HashEntry[] table )

最后从table中找到匹配的HashEntry,

并且这段代码注意的地方是
在获取Segment的时候,使用的是UNSAFE.getObjectVolatile方法,我们都知道volatile关键字在java中的作用是标记变量在使用的过程中,一直读取的是最新的值,一般用于原子操作。那么我们就可以大胆猜测一下,UNSAFE.getObjectVolatile可以获取到最新的Segment。然后就是在遍历table的时候,也是使用类似的操作,


再来看进行put操作的时候

public V put(K key, V value) {        Segment<K,V> s;        if (value == null)            throw new NullPointerException();        int hash = hash(key);        int j = (hash >>> segmentShift) & segmentMask;        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment            s = ensureSegment(j);        return s.put(key, hash, value, false);    }
看下Segment 中对put的设计

final V put(K key, int hash, V value, boolean onlyIfAbsent) {            HashEntry<K,V> node = tryLock() ? null :                scanAndLockForPut(key, hash, value);            V oldValue;            try {                HashEntry<K,V>[] tab = table;                int index = (tab.length - 1) & hash;                HashEntry<K,V> first = entryAt(tab, index);                for (HashEntry<K,V> e = first;;) {                    if (e != null) {                        K k;                        if ((k = e.key) == key ||                            (e.hash == hash && key.equals(k))) {                            oldValue = e.value;                            if (!onlyIfAbsent) {                                e.value = value;                                ++modCount;                            }                            break;                        }                        e = e.next;                    }                    else {                        if (node != null)                            node.setNext(first);                        else                            node = new HashEntry<K,V>(hash, key, value, first);                        int c = count + 1;                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)                            rehash(node);                        else                            setEntryAt(tab, index, node);                        ++modCount;                        count = c;                        oldValue = null;                        break;                    }                }            } finally {                unlock();            }            return oldValue;        }

不难发现,在进行put操作的时候,我们找到需要put的Segment,然后加锁,之后再通过hash添加新的数据,与HashTable不同的地方则是HashTable中的put 是使用了synchronized关键字的,由此可以看出ConcurrentHashMap执行put操作锁的粒度是在Segment上的,锁粒度更细的情况下,性能上有比较明显的提升。

9、CopyOnWriteArrayList

不多说,直接说add 方法
 public boolean add(E e) {        final ReentrantLock lock = this.lock;        lock.lock();        try {            Object[] elements = getArray();            int len = elements.length;            Object[] newElements = Arrays.copyOf(elements, len + 1);            newElements[len] = e;            setArray(newElements);            return true;        } finally {            lock.unlock();        }    }

在添加元素的时候,会进行加锁,但是这里面比较明显的一个问题是,每次add操作都需要进行数组的拷贝复制,所以该类的设计思想则是写时复制
那么随之而来的问题也十分明显,一个是内存开销的问题,频繁的进行大数据量的拷贝工作会造成性能上的瓶颈,另一个则是需要扩展批量操作的功能设计,以此来减少拷贝的次数,并且提升性能。
0 0
原创粉丝点击