集合知识点总结node
Java的集合类主要由两个接口派生而出:Collection和Map 算法
Set、List和Map能够看作集合的三大类:数组
List集合是有序集合,集合中的元素能够重复,访问集合中的元素能够根据元素的索引来访问。缓存
Set集合是无序集合,集合中的元素不能够重复,访问集合中的元素只能根据元素自己来访问(也是集合里元素不容许重复的缘由)。安全
Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。数据结构
ArrayList并发
有序可重复容许为空,初始容量为10的动态数组(非安全)app
ArrayList类中两个私有属性,elementData存储ArrayList内的元素,size表示它包含的元素的数量。dom
有序:元素是按照该 collection 的迭代器返回它们的顺序排列的。函数
动态扩容:int newCapacity = (oldCapacity * 3)/2 + 1;
插入元素:按照指定位置,把从指定位置开始的全部元素利用System.arraycopy方法作一个总体的复制,向后移动一个位置(固然先要用ensureCapacity方法进行判断,加了一个元素以后数组会不会不够 大),而后指定位置的元素设置为须要插入的元素,完成了一次插入的操做,该方法的根本目的就是将index位置空出来以供新数据插入,这里须要进行数组数据的右移,若是要复制的元素不少, 那么就会比较耗费性能。
删除元素:一、把指定元素后面位置的全部元素,利用System.arraycopy方法总体向前移动一个位置
二、最后一个位置的元素指定为null,这样让gc能够去回收它,若是要复制的元素不少,那么就会比较耗费性能。
访问元素:ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,所以查 找也就是get的时候很是快。基于数组实现,能够经过下标索引直接查找到指定位置的元素,所以 查找效率高
ArrayList是线程非安全的,一个方法是用Collections.synchronizedList方法把你的ArrayList变成一个线程安全的List,另外一个方法就是Vector,它是ArrayList的线程安全版本
LinkedList
有序可重复容许为空,初始容量为10 的双向链表(非安全)
LinkedList中定义了两个私有属性:size 和Entry (Entry中包含成员变量:previous, next,element)
LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据,数据结构——咱们能够称之为节点,节点实例保存业务数据、前一个节点的位置信息和后一个节点位置信息
插入元素:改变先后Entry的引用地址
删除元素:预删除节点的前一节点的后指针指向预删除节点的后一个节点。预删除节点的后一节点的前指针指向预删除节点的前一个节点。清空预删除节点:交给gc完成资源回收,删除操做结束。与 ArrayList比较而言,LinkedList的删除动做不须要“移动”不少数据,从而效率更高。
访问元素:get(int)方法首先判断位置信息是否合法(大于等于0,小于当前LinkedList实例的Size),而后遍历到具体位置,得到节点的业务数据(element)并返回。当index小于数组大小的一半的时候 (size >> 1表示size / 2,使用移位运算提高代码运行效率),从前向后查找;不然,从后向前查找。
ArrayList和LinkedList比较
(1)LinkedList作插入、删除的时候,慢在寻址,快在只须要改变先后Entry的引用地址
(2)ArrayList作插入、删除的时候,慢在数组元素的批量copy,快在寻址
HashMap
无序可重复容许为空,链表的数组 (非安全)
构造一个具备默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
无序:特别说明这个无序指的是遍历HashMap的时候,获得的元素的顺顺序
可重复:Key重复会覆盖、Value容许重复)
Key和Value都容许为空
非线程安全的
HashMap的底层主要是基于数组和链表来实现的,它之因此有至关快的查询速度主要是由于它是经过计算散列码来决定存储的位置。(key 的hash值决定位置)HashMap中主要是经过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就同样。
若是存储的对象对多了,就有可能不一样的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同窗都知道,解决hash冲突的方法有不少,HashMap底层是经过链表来解决hash冲突的。Entry就是数组中的元素,每一个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。(previous element next)
构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap: 空间利用和查找效率最好,加载因子它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。;若是负载因子过小,那么散列表的数据将过于稀疏,对空间形成严重浪费 ; 加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.链表长度会愈来愈长,查找效率下降。
存储元素(读取):先判断key是否为null,若为null,则直接调用putForNullKey方法,将value放置在数组第一个位置上。若不为空则根据key的hashCode从新计算hash值,而后根据hash值获得这个元素在table数组中的位置(即下标),若是table数组在该位置处已经存放有其余元素了,则经过比较是否存在相同的key,若存在则覆盖原来key的value,不然将该元素保存在链头(最早保存的元素放在链尾)。若table在该处没有元素,就直接将该元素放到此数组中的该位置上。经过比较是否存在相同的key,若存在则覆盖原来key的value:对比Key是否相同,是先比HashCode是否相同,HashCode相同再判断equals是否为true
访问元素:此时的 HashMap 具备最好的性能:当程序经过 key 取出对应 value 时,系统只要先计算出该key 的 hashCode() 返回值,在根据该 hashCode 返回值找出该 key 在 table 数组中的索引,而后
取出该索引处的 Entry,最后返回该 key 对应的 value 便可
2的n 次方:
HashMap的table而言,数据分布须要均匀(最好每项都只有一个元素,这样就能够直接找到),不能太紧也不能太松,太紧会致使查询速度慢,太松则浪费空间。HashMap的底层数组长度老是2的n次方数据在table数组中分布较均匀,查询速度也较快。
LinkedHashMap
HashMap+LinkedList,即它既使用HashMap操做数据结构,又使用LinkedList维护插入元素的前后顺序有序可重复
HashMap迭代HashMap的顺序并非HashMap放置的顺序,也就是无序。咱们期待一个有序的Map。LinkedHashMap就闪亮登场了,它虽然增长了时间和空间上的开销,可是经过维护一个运行于全部条目的双向链表,LinkedHashMap保证了元素迭代的顺序,该迭代顺序能够是插入顺序或者是访问顺序。
利用LinkedHashMap实现LRU算法缓存
LRU:LRU即Least Recently Used,最近最少使用,也就是说,当缓存满了,会优先淘汰那些最近最不常访问的数据。比
LinkedHashMap能够实现LRU算法的缓存基于两点:
一、LinkedList首先它是一个Map,Map是基于K-V的,和缓存一致
二、LinkedList提供了一个boolean值可让用户指定是否实现LRU
accessOrder 1)false,全部的Entry按照插入的顺序排列(2)true,全部的Entry按照访问的顺序排列
concurrentHashMap
背景: 线程不安全的HashMap 使用Hashmap进行put操做会引发死循环 ;
效率低下的HashTable容器 , 当一个线程访问HashTable的同步方法时,其余线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。
ConcurrentHashMap的锁分段技术(数据分段存储,分段枷锁)
首先将数据分红一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其余段的数据也能被其余线程访问。
ConcurrentHashMap为了提升自己的并发能力,在内部采用了一个叫作Segment的结构,一个Segment其实就是一个类Hash Table的结构,Segment内部维护了一个链表数组。当对HashEntry数组的数据进行修改时,必须首先得到它对应的Segment锁。
高并发:ConcurrentHashMap定位一个元素的过程须要进行两次Hash操做,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部。在最理想的状况下,ConcurrentHashMap能够最高同时支持Segment数量大小的写操做(恰好这些写操做都很是平均地分布在全部的Segment上),因此,经过这一种结构,ConcurrentHashMap的并发能力能够大大的提升。总的Map包含了16个Segment(默认数量),每一个Segment内部包含16个HashEntry(默认数量),这样对于这个key所在的Segment加锁的同时,其余15个Segmeng还能正常使用,在性能上有了大大的提高。
详细解释一下Segment里面的成员变量的意义:
count:Segment中元素的数量
modCount:对table的大小形成影响的操做的数量(好比put或者remove操做)
threshold:阈值,Segment里面元素的数量超过这个值依旧就会对Segment进行扩容
table:链表数组,数组中的每个元素表明了一个链表的头部
loadFactor:负载因子,用于肯定threshold
volatile的保证:对volatile域的写入操做happens-before于每个后续对同一个域的读写操做。因此,每次判断count变量的时候,即便刚好其余线程改变了segment也会体现出来。
get方法没有使用锁来同步,只是判断获取的entry的value是否为null,为null时才使用加锁的方式再次去获取。
put 操做:首先对Segment的put操做是加锁完成的。由于每一个HashEntry中的next也是final的,无法对链表最后一个元素增长一个后续entry因此新增一个entry的实现方式只能经过头结点来插入了。
remove 操做:先定位Segment的过程,而后肯定须要删除的元素的位置, 程序就将待删除元素前面的那一些元
素所有复制一遍,而后再一个一个从新接到链表上去,
知识问答:
1ArrayList,LinkedList,HashMap,LinkedHashMap,ConcurrentHashMap的底层实现原理
HashMap:HashMap底层就是一个数组结构,数组中的每一项又是一个链表。
LinkedHashMap:LinkedHashMap继承于HashMap,底层使用哈希表和双向链表来保存全部元素,而且它是非同步,容许使用null值和null键。
ConcurrentHashMap:ConcurrentHashMap采用 分段锁的机制,实现并发的更新操做,底层采用数组+链表的存储结构。在JDK1.8利用CAS+Synchronized来保证并发更新的安全,底层采用数组+链表+红黑树的存储结构。
ArrayList:底层使用数组实现
LinkedList:底层的数据结构是基于双向链表
2 1.7和1.8版本中HashMap的区别
JDK1.7中
使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,若是hashcode相同,或者hashcode取模后的结果相同(hash collision),那么这些key会被定位到Entry数组的同一个格子里,这些key会造成一个链表。在hashcode特别差的状况下,比方说全部key的hashcode都相同,这个链表可能会很长,那么put/get操做均可能须要遍历这个链表也就是说时间复杂度在最差状况下会退化到O(n)
JDK1.8中
使用一个Node数组来存储数据,但这个Node多是链表结构,也多是红黑树结构
若是插入的key的hashcode相同,那么这些key也会被定位到Node数组的同一个格子里。
若是同一个格子里的key不超过8个,使用链表结构存储。
若是超过了8个,那么会调用treeifyBin函数,将链表转换为红黑树。
那么即便hashcode彻底相同,因为红黑树的特色,查找某个特定元素,也只须要O(log n)的开销
也就是说put/get的操做的时间复杂度最差只有O(log n)
3 1.7和1.8版本中ConcurrentHashMap的区别
1.7中concurrenthashmap主要使用Segment来减少锁力度,把hashmap分割成若干个segment,在put的时候须要锁住segment,get时候不加锁,使用volatile来保证可见性,当要统计全局时(size),首先会尝试屡次计算modcount来肯定,这几回尝试中,是否有其余线程进行了修改操做,若是没有,则直接返回size,若是有,则须要一次锁住全部的segment来计算。
1.7中concurrenthashmap中,当长度过长时碰撞会很频繁,链表的增删改查操做都会消耗很长的时间,因此1.8中彻底重写了,主要有几点东西不一样:
1 不采用segment而采用node,锁住node来实现减少锁力度
2 设计了moved状态,当resize的过程当中,线程2还在put数据,线程2会帮助resize
3 使用三个cas操做来确保node的一些操做的原子性,这种方式代替了锁
4 sizeCtl的不一样值来表明不一样含义,起到了控制的做用
4 HashMap能不能排序?HashMap的长度为何要是2的次方?
2的N次方
HashMap为了存取高效,要尽可能较少碰撞,就是要尽可能把数据分配均匀,每一个链表长度大体相同,这个实现就在把数据存到哪一个链表中的算法;
这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中作了优化hash&(length-1),hash%length==hash&(length-1)的前提是length是2的n次方;