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
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操作都需要进行数组的拷贝复制,所以该类的设计思想则是写时复制