java集合框架分析
接口框架:
集合框架中包含List,Queue,Set和Map这四大块,注意:Map虽然属于集合框架,但Map接口并不从Collection接口扩展。
一个Map提供了通过Key对Map中存储的Value进行访问,也就是说它操作的都是成对的对象元素,比如put()和get()方法,而这是一个Set或List所不就具备的(它们是add()和remove())。当然在需要时,你可以由keySet()方法或values()方法从一个Map中得到键的Set集或值的Collection集。
1.Collection
在你实现一个集合接口时,你可以很容易的在你不想让用户使用的方法中抛出UnsupportOperationException来告诉使用者这个方法当前没有实现。
Java的容器类库还有一种Fail fast的机制。modCount就是保证每次操作后都会加一,Iterator操作会检查此值来控制并发,比如你正在用一个Iterator遍历一个容器中的对象,这时另外一个线程或进程对那个容器进行了修改,那么再用next()方法时可能会有灾难性的后果,而这是你不愿看到的,这时就会引发一个ConcurrentModificationException例外。这就是fail-fast。所以要在遍历容器时,删除其中的元素,必须调用Iterator的remove来删除才不会抛出异常。
2.List
List接口对Collection进行了简单的扩充。List在Collection的基础上添加了大量方法,使之能在序列中间插入和删除元素。
ArrayList从其命名中可以看出它是一种类似数组的形式进行存储,因此它的随机访问速度极快。
而LinkedList的内部实现是链表,它适合于在链表中间需要频繁进行插入和删除操作。在具体应用时可以根据需要自由选择。其中放置元素的是Node内部类,包含指向前和后的两个指针。
Vector:线程是安全的,也就是说是同步的。ArrayList不安全。
前面说的Iterator只能对容器进行向前遍历,而ListIterator则继承了Iterator的思想,并提供了对List进行双向遍历的方法。
ArrayList的扩充实现(add):
1.检查是否容量不够
2.扩容到原来的1.5倍
3.如果新容量小于需要的容量,则使用需要容量大小, 并检查是否为容量是否为负,则使用Integer.MAX来代替。
4.使用Arrays.copyOf -> System.arraycopy来进行底层数组的复制。
3.Set
4.Map
1 /** 2 * Transfers all entries from current table to newTable. 3 */ 4 void transfer(Entry[] newTable, boolean rehash) { 5 int newCapacity = newTable.length; 6 for (Entry<K,V> e : table) { 7 while(null != e) { 8 Entry<K,V> next = e.next; 9 if (rehash) { 10 e.hash = null == e.key ? 0 : hash(e.key); 11 } 12 int i = e.hash & (newCapacity-1); 13 e.next = newTable[i]; 14 newTable[i] = e; 15 e = next; 16 } 17 } 18 }
5.Queue
add、remove和element(返回队列头部)操作在你试图为一个已满的队列增加元素或从空队列取得元素时抛出异常。
offer、poll、peek方法在无法完成任务时只是给出一个出错示而不会抛出异常。
Deque(接口):(deque,全名double-ended queue)是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。LinkedList实现了它,因为LinkedList本来就具有双向操作的性质。另外还有一个实现是ArrayDeque,它是用数组来实现Deque的双向操作。
BlockingQueue(接口):作为线程容器,可以为线程同步提供有力的保障。对它的某些操作是阻塞的,如果BlockQueue是空的,从BlockingQueue取东西(take或poll(time))的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒.同样,如果BlockingQueue是满的,任何试图往里存东西(put(E))的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作.
1)ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的.
2)LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的
3)PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序.
4)SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的.
Block的阻塞实现(put(E)):
final ReentrantLock lock;
/** Condition for waiting puts */
private final Condition notFull;
final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); insert(e); } finally { lock.unlock(); }
首先,用ReentrantLock 来锁定以此控制并发。
java.util.concurrent.lock
中的 Lock
框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock
的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。
synchronized机制提供了对与每个对象相关的隐式监视器锁的访问, 并强制所有锁获取和释放均要出现在一个块结构中, 当获取了多个锁时, 它们必须以相反的顺序释放. synchronized机制对锁的释放是隐式的, 只要线程运行的代码超出了synchronized语句块范围, 锁就会被释放.
而Lock机制必须显式的调用Lock对象的unlock()方法才能释放锁, 这为获取锁和释放锁不出现在同一个块结构中, 以及以更自由的顺序释放锁提供了可能。可中断,可定时。
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁(因为锁是显示的释放,所以可能一个线程获得锁后再次试图获取锁),那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。
reentrant 锁还提供了公平和非公平两种锁,公平锁保证锁的等待队列是先进先获,而非公平有可能有插队的现象。
然后,使用Conditon来进行阻塞等待。
Condition
实例实质上被绑定到一个锁上。要为特定 Lock
实例获得 Condition
实例,请使用其newCondition()
方法。
当线程调用condition.wait的时候,当前线程持有的可重入锁会释放;当其他线程调用condition.signal的时候,该线程重新获得锁并复原。
Condition的优势是支持多路等待,就是我可以定义多个Condition,每个condition控制线程的一条执行通路。传统方式只能是一路等待。
当有其它的线程拿走元素时,就会触发condition.signal唤醒等待的线程。
6.AbstractCollection
此类提供了 Collection 接口的骨干实现,从而最大限度地减少了实现此接口所需的工作。
要实现一个不可修改的 collection,程序员只需扩展此类,并提供 iterator 和 size 方法的实现。(iterator 方法返回的迭代器必须实现 hasNext 和 next。)
其它的Abstract的抽象类都是这个原理,为了减少实现接口所需的工作。
以下是List和Set的抽象类架构:
以下是Map的抽象类架构:
参考:百科
http://www.iteye.com/topic/1114847
http://dryr.blog.163.com/blog/static/58211013201132352356657/