java源码学习7-Collection

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值