集合源码初步解析

菜鸟新手,出错请见谅

Collections:

Collections和collection不一样,collection是一个接口,是List、Set、Queue接口的父接口,而Collections是一个类,一个操作集合的工具类,它提供了大量的方法对集合元素进行排序、查询和修改等操作,还提供将集合对象设置为不可变、对集合对象实现同步控制等方法。
提供了对list集合的排序:
void sort(List l):调用了List接口的sort(Comparator c)方法来实现,只不过c=null,在jdk1.8时,接口的方法中出现了方法体,所以Collections是直接调用的。
所以默认的升序排序。
sort(List l,Comparable c):和上面的一样,不过传的参数不为null,而是c 。
binarySerch(List l, T key)  : 使用二分查找法来搜索List对象,在调用该方法前,必须根据列表元素的自然顺序对列表进行升序排序(sort(list)),如果不排序,则结果不确定。
如果指定列表为实现RandomAcess接口且列表长度大于BINARYSEARCH_THRESHOLD(5000),则该链表是一个大型列表,需要基于迭代器的二分查找。
reverse(List list):反转集合元素顺序
shuffle(List list):随机排序
swap(List list,int i,int j):将list集合中i处元素和j处元素交换
rotate(List list,int distance): 当distance为正数,将list集合中的后distance个元素“整体”移到前面;当distance为负数时,将list集合的前distance个元素“整体”移到最后面。
查找和替换操作:
asLifoQueue(Deque<T> deque) 
          以后进先出 (Lifo) Queue 的形式返回某个 Deque 的视图。相当于栈。
 void fill(Collection c,object o):c中全部元素被O替换
 int frequency(Collection c,Object o):O在集合C 中出现的次数
 int indexOfSubList(List source,List target):返回list对象在父List集合中第一次出现的位置索引,如果没有返回-1
int lastOfSubList(List source,List target):返回list对象在父List集合中最后一次出现的位置索引,如果没有返回-1
boolean replace(List list,Object oldVal, Object newVal):将新值替换掉所有旧值
同步控制:
Collections类中提供了多个synchronizedXxx()方法,可将指定集合包装成线程同步的集合,从而解决多线程并发访问集合的线程安全问题。
java中常用的集合框架中有:实现类HashSet、TreeSet、ArraySet、ArrayList、ArrayDeque、LinkedList、HashMap和TreeMap都是线程不安全的。
可通过以下方法创建线程安全的集合对象:
Collection c = Collections.synchronized(new ArrayList());
Set s = Collections.synchronized(new TreeSet());等等。。。
三类方法来返回一个不可变(只读)方法:
emptyXxx();返回一个空的、不可变的集合对象。例:List l = Collections.emptyList();
singletonXxx();返回一个只包含一个指定对象的、不可变的集合对象。例:Set s = Collections.singleton();//只有set不需要在singleton加上Set
unmodifiableXxx();返回指定集合对象的不可变视图。例:Map score = new HashMap(); score.add("语文",1); Map m = Collections.unmodifiableMap(score);
如果要向其中添加元素则引用UnsupportedOperationExeption异常。
可以说,collections类是一个为了方便使用集合操作的一个工具类,在以后的集合使用中,可以直接调用该类的方法,来实现一些简单的操作,不用再自己写方法实现。
arrayList:
继承于abstractList类,实现List、RandomAccess,cloneable, serializable接口,完全支持List接口的全部功能,可存在null值。
它是基于数组实现List接口的类,所以封装了一个动态的、可分配的Object[]数组。ArrayList通过使用initialCapacity参数设置数组长度,当添加的元素超出该数组长度时,initialCapacity自动增长。当一次要插入大量元素时,可使用ensureCapacity(int initialCapacity)一次性增加长度,提高性能。
trimToSize()是将当前数组容量改为目前列表实际大小
hashMap:
继承于abstractMap类,实现map,cloneable,Serializable接口.
基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 
大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 hash表里可以存储元素的位置被称为“桶”,在通常情况下,单个“桶”里存储一个元素,此时有最好的性能:hash算法可以根据hashCode值计算出“桶”的存储位置,
接着从“桶”中取出元素。但hash表的状态是open的:
在发生“hash冲突”的情况下,单个桶里会存储多个元素,这些元素以链表形式存储,必须按顺序搜索。所以hashMap的key值使用hash来存储(key值的hashCode的hash)。
在自jdk1.8之后,当链表存储长度超过8时,转换为红黑树存储,
hash表中的属性:
容量(capacity):hash表中桶的数量。源码中最大为: 1<<30
初始化容量(initialcapacity):创建hash表时桶的数量。hashMap允许在构造器中指定初始化容量。 1<<4
尺寸(size):当前hash表中记录的数量
负载因子(load factor):负载因子等于 size/capacity  。负载因子为0,表示hash表为空。0.5表示半满。轻负载的hash表具有冲突少、易插入与查询的特点,
此外,还有一个“负载极限”,是一个0~1的数值,决定hash表的最大填满程度。默认值为0.75。表明当hash表的3/4已被填满时,hash表发生rehashing。扩充2倍

object和objects:
objects是jdk1.7出现的,是一个final类,在java.util 包中,这个类包含用于操作对象的静态实用方法。
这些实用工具包括用于计算对象哈希代码、返回对象的字符串以及比较两个对象的null安全或零容忍方法。

tableSizeFor():该方法在HashMap的有参构造方法(HashMap(int initialcapacity,int loadfactor))中被调用,返回的capacity赋给了threshold: this.threshold = tableSizeFor(initialCapacity);
该值最大为32个bit的1。就是说参数最大为1<<30;

hashMap中存在hash方法,对key的hashCode再hash,是因为table的长度为2的幂,因此index仅与hash值的低n位有关。n = table.length; index = (n - 1) & hash; 在putval()方法中有体现。

put(),putVal()方法:
对key的hashCode()做hash,然后再计算index;
如果没碰撞直接放到bucket里;
如果碰撞了,以链表的形式存在buckets后;
如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD = 8),就把链表转换成红黑树(java8新特性);
如果节点已经存在就替换old value(保证key的唯一性)
如果bucket满了(超过size  = load factor*current capacity),就要resize。

get()方法:
bucket里的第一个节点,直接命中;
如果有冲突,则通过key.equals(k)去查找对应的entry
若为树,则在树中通过key.equals(k)查找,O(logn);(java8的新特性,明显提高效率)
若为链表,则在链表中通过key.equals(k)查找,O(n)。

resize():
当发现目前的桶占用已经超过loadfactor时,就会将其扩充2倍,重新计算table下标index,将结点放入新的桶里。
此处,当要重新扩充时,hash和n-1都多了一位,只要看hash多的那个bit是1还是0,是1的话,index = 原位置+旧表长度。 是0的话 index不变。
hashSet:
继承于abstractSet类,实现set,cloneable,Serializable接口.
在hashSet的构造方法中是new HashMap对象,所以HashSet是通过map(HashMap对象)保存内容的。但是,map是键值对存储的,所以此处就定义了一个Object类型的静态常量PRESENT
--private static final Object PRESENT = new Object();使键值对的值始终为PRESENT。所以说hashSet的构造函数不管是有参或者是无参,都可以说是调用了map的构造来创建对象。
其中的iterator(),size(),isEmpty(),contains(Object o),都是调用的HashMap中的方法。add(E e)方法,也是将key值放入到HashMap中。
remove(Object o),删除HashSet中的元素(o),其实是在HashMap中删除了以o为key的Entry。
clear(),清空HashMap的clear方法清空所有Entry。
clone(),克隆一个HashSet,并返回Object对象.
writeObject(java.io.ObjectOutputStream s)java.io.Serializable的写入函数。将HashSet的“总的容量,加载因子,实际容量,所有的元素”都写入到输出流中。
readObject(java.io.ObjectInputStream s)。java.io.Serializable的读取函数。将HashSet的“总的容量,加载因子,实际容量,所有的元素”依次读出。
spliterator()。创建一个late-binding和fail-fast的可分割迭代器,还是hashmap中一个静态内部类的方法
可以说,hashSet就是一个对hashMap的封装而已,只是value值为静态的Object对象。


vector:
继承abstractList类,实现List,RandomAccess,Cloneable,Serialiable
vrctor主要有2个参数int initialCapacity, int capacityIncrement。Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。
但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。 每个向量会试图通过维护 capacity 和 capacityIncrement 来优化存储管理。
创建对象时,默认的initialCapacity是10。capacityIncrement为0;
上面说到vector是一个可增长的对象数组,grow()方法就是对其进行扩容。扩容量由 oldCapacity 与 capacityIncrement 共同决定的,
capacityIncrement是我们可控的一个参数,在构造方法中传入,即每次容量具体增加多少,如果我们在创建 Vector 对象是没有指定 capacityIncrement,
则默认每次把容量增加为原来的二倍(不越界的情况下)。
vector类几乎对所有的操作都实现了同步,这样做是不好的,所以现在是一个过时的类,一般不会用的。它的子类Stack(栈)在算法中会用到,但是可以使用ArrayDeque来代替,因为stack也是一个很老的类,一般不用。
vector和arrayList可以放在一起来说,基本功能相同
HashTable:
继承于Dirctionary类,实现map、cloneable、serializable接口。
hashtable和hashMap经常放在一起来讲,区别就是HashTable线程安全的,不允许null的键或值;
HashMap键值对,key不能重复,但是value可以重复;key的实现就是HashSet;value对应着放;允许null的键或值;
而且,hashTable的hashCode是直接用hash进行异或^,而hashMap不同。
HashMap中使用了Node<K,V>实现数组,而在Hashtable中使用了Entry<K,V>,并且是私有的。
hashTable的put方法加了同步锁保持线程安全。
key的hashCode是调用Object的hashCode()方法,是native的方法,如果为null,就会抛出空指针异常。
TreeSet:
TreeSet继承了abstractSet类也是实现了三个接口,并且和hashSet的存储模式差不多,不同的是,它是在NavigableMap中存储,hashSet在hashMap中存储,只是定义了一个PRESENT静态常量,用来填充map的value位置。至于常用方法都是调用TreeMap中的方法,没什么好说的。Iterator()迭代器方法,返回的就是map的keySet的迭代器。
ConcurrentHashMap
继承于abstractMap,实现ConcurrentMap,serializable接口。
private transient volatile int sizeCtl;
hash表初始化或扩容时的一个控制位标识量 ; 
   负数代表正在进行初始化或扩容操作 ;
   -1代表正在初始化 ;
   -N 表示有N-1个线程正在进行扩容操作 ;
   正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小,它的值始终是当前ConcurrentHashMap容量的0.75倍,这与loadfactor是对应的。
构造方法和HashMap基本一致,都是4个构造方法。put的方法也基本一致,只是hashMap多了一步调用,都是map方法中entrySet()来put的。但是要注意,此类中不能允许key和value值为null。并且多线程中,有以下两个情况:
如果一个或多个线程正在对ConcurrentHashMap进行扩容操作,当前线程也要进入扩容的操作中。这个扩容的操作之所以能被检测到,是因为transfer方法中在空结点上插入forward
节点,如果检测到需要插入的位置被forward节点占有,就帮助进行扩容;
如果检测到要插入的节点是非空且不是forward节点,就对这个节点加锁,这样就保证了线程安全。尽管这个有一些影响效率,但是还是会比hashTable的synchronized要好得多。

此外,Node还是其中的一个重要的内部类,它包装了key-value键值对,所有插入ConcurrentHashMap的数据都包装在这里面。
它与HashMap中的定义很相似,但是但是有一些差别它对value和next属性设置了volatile同步锁,它不允许调用setValue方法直接改变Node的value域,
它增加了find方法辅助map.get()方法。
在hashmap中,当链表过长(>8)时,会直接转换为红黑树,
但是ConcurrentHashMap不同,当链表长度过长的时候,会转换为TreeNode。把这些结点包装成TreeNode放在TreeBin对象中,
由TreeBin完成对红黑树的包装。而且TreeNode在ConcurrentHashMap集成自Node类,而并非HashMap中的集成自LinkedHashMap.Entry<K,V>类,
也就是说TreeNode带有next指针,这样做的目的是方便基于TreeBin的访问。TreeBin这个类并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。
它代替了TreeNode的根节点,也就是说在实际的ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象,这是与HashMap的区别。另外这个类还带有了读写锁。
initTable()初始化方法,在里面利用了sizeCtl的值来判断是否有其他线程正在初始化,如果sizeCtl<0就表明有,Thread.yield();让线程进入就绪态。
如果获得如果获得了初始化权限,就用CAS方法将sizeCtl置为-1,防止其他线程进入。初始化数组后,将sizeCtl的值改为0.75*n。
扩容方法transfer(),当容量不够时,和HashMap一样,需要对table扩容,但是,它是支持并发扩容的,支持多线程进行扩容操作,而并没有加锁。
整个扩容操作分为两个部分
第一部分是构建一个nextTable,它的容量是原来的两倍,这个操作是单线程完成的。
第二个部分就是将原来table中的元素复制到nextTable中,这里允许多线程进行操作。
对于size的一些方法,来查看table中到底有多少东西,只是个估计值,因为不会停止所有线程去统计一下到底有多少。
linkedList
继承于AbstractSequentialList,实现list,deque,cloneable,serializable接口。
LinkedList底层使用双向链表,实现了List和deque。线程不安全。
要注意,LinkedList是双向的所以插入和删除、得到节点,一定要调用正确的方法。
比如,removeLast和removeFirst两个方法分别是删除尾结点和头结点,并且返回删除的这个值,但是remove(Object o)这个方法是删除从First开始第一次出现的节点返回的是boolean类型。
addAll(int index,Collection c)这个方法,是从指定index的后面的位置插入,index从0开始。
offer(),offerLast()和add(),addLast()是同一个意思,都是在尾端添加一个元素
peek()是返回第一个元素不会删除,不存在返回null,element()也是返回第一个元素不会删除,但如果不存在则抛出异常。
poll返回第一个元素且删除,不存在返回null,remove()不存在则会抛出异常。
CopyOnWriteArrayList
CopyOnWriteArrayList是ArrayList的一个线程安全的变体,继承于Object,其中所有的可变操作(set、add、remove、sort)都是通过对底层数组进行一次新的复制来实现的。
是通过依赖ReentrantLock类来实现线程安全的。
这个类的一些基本可变操作都被锁住,所以适合写少读多的情况。

猜你喜欢

转载自blog.csdn.net/x_i_xw/article/details/79143672
今日推荐