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
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操作都需要进行数组的拷贝复制,所以该类的设计思想则是写时复制
- java源码学习7-Collection
- java collection接口源码
- java Collection源码解析
- Thinking in Java之深入Collection源码学习
- 集合源码学习(一):Collection
- Java源码解析 Collection<E>
- 源码分析-java-collection接口
- java集合源码解析:collection
- java.util.Collection学习
- java Collection学习
- Java Collection 学习
- java.util.Collection体系源码解读<二>Collection接口源码
- Java三大集合类源码阅读笔记【包含超类Collection】提供学习源码
- java.util.Collection体系源码解读<一>Collection简介
- Collection源码学习之toArray方法
- Collection源码学习之List.listIterator方法
- JAVA学习笔记之Collection
- JAVA学习笔记之Collection
- 通过测试和代码告诉你Maven是如何使用mirror和repository的
- Leetcode 413. Arithmetic Slices
- CF-Codeforces Round #377 (Div. 2)
- spring框架原理
- C#操作Access数据库
- java源码学习7-Collection
- 新鲜的单片机
- 配置完ubuntu环境变量之后,报错Ubuntu 程序“java”已包含在下列软件包中
- 判断一个男人穷还是富,只看这几点!
- 【仿vysor】做一个android投影的工具
- JAVA编程基础(一) 搭建开发环境
- 从内存角度分析:数组删除自己内部指定对象和通过函数形参改值问题
- 复制一个5G文件只需要两秒,全网最牛方法!
- swift Character类型