集合

List & Set

都实现了collection接口

List:1、可以允许重复的对象;2、可以插入多个null元素;3、是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序;4、常用的实现类有ArrayList、LinkedList和Vector。

Set:1、不允许重复对象;2、无序容器,无法保证每个元素的存储顺序,TreeSet通过Comparator 或者 Comparable 维护了一个排序顺序;3、只允许一个null元素;3、Set接口最流行的几个实现类是HashSet、LinkedHashSet以及TreeSet。最流行的是基于HashMap实现的HashSet;TreeSet还实现了SortedSet接口,因此TreeSet是一个根据其compare() 和compareTo() 的定义进行排序的有序容器。

Map:1、Map不是collection的子接口或者实现类。Map是一个接口;2、Map的每个Entry都持有两个对象,也就是一个键一个值,Map可能会持有相同的值对象但键对象必须是唯一的;3、TreeMap也通过Comparator或者Comparable维护了一个排序顺序;4、Map里你可以拥有随意个null值但最多只能有一个null键;5、Map接口最流行的几个实现类是HashMap、LinkedHashMap、Hashtable和TreeMap。

ArrayList

ArrayList 底层以数组实现,允许重复,默认第一次插入元素时创建数组的大小为10,超出限制时会增加50%的容量,每次扩容底层采用System.arrayCopy()复制到新的数组,初始化时最好能给出数组大小的预估值

按数组下标访问元素-get(i)/set(i,e)的性能很高

利用Arrays.asList(array)将返回一个List,然而这个返回的List并不支持add和remove的操作。

ArrayList并不是java.util.ArrayList,而是Arrays的内部类。该内部类继承的是AbstractList

List list = new ArrayList<>(Arrays.asList(1,2,3));

LinkedList

以双向链表实现,允许重复,并保留头指针和尾指针。

按下标访问元素-get(i)/set(i,e)要悲剧的遍历链表将指针移动到位(如果i>数组大小的一半,会从末尾移起)。

插入、删除元素时修改前后节点的指针即可,但还是要遍历部分链表的指针才能移动到下标所指的位置,只有在链表两头的操作-add(),addFirst(),removeLast()或用iterator() 上的remove()能省掉指针的移动

ArrayList 和 LinkedList的区别

1、ArrayList是实现了基于动态数组的数据结构,LinedList基于链表的数据结构。

2、对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。

3、对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

HashSet 保证数据不重复

1,如果hash码值不相同,说明是一个新元素,存;

2(1),如果hash码值相同,且equles判断相等,说明元素已经存在,不存;

2(2),如果hash码值相同,且equles判断不相等,说明元素不存在,存;

HaspMap

使用一个Entry数组保存key、value数据,当一对key、value被加入时,会通过一个hash算法得到数组的下标index,算法很简单,根据key的hash值,对数据的大小取模hash&(length-1),并把结果插入数组该位置,如果该位置上已经有元素了,就说明存在hash冲突,这样会在index位置生成链表。

如果存在hash冲突,最惨的情况,就是所有元素都定位到同一个位置,形成一个长长的链表,这样get一个值时,最坏情况需要遍历所有节点,性能变成了O(n),所以元素的hash值算法和HashMap的初始化大小很重要。

当插入一个新的节点时,如果不存在相同的key,则会判断当前内部元素是否已经达到阈值(默认是数组大小的0.75),如果已经达到阈值,会对数据进行扩容,也会对链表中的元素进行rehash。

1.7 h& (length-1)运算等价于对length取模

1.8 通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16)

HashMap的put方法实现:

1、判断key是否已经存在

2、检查容量是否达到阈值threshold

如果元素个数已经达到阈值,则扩容,并把原来的元素移动过去

1、长度为16的数组中,元素存储在哪个位置

2、如果key出现hash冲突,如何解决

inthash=hash(key);

int i = indexFor(hash, table.length);

首先先hash,之后结合数组的长度进行一个&操作得到数组的下标。

利用Entry类的next变量来实现链表,把最新的元素放到链表头,旧的数据则被最新的元素的next变量引用着。

3、扩容实现

https://tech.meituan.com/java-hashmap.html

会新建一个更大的数组,并通过transfer 方法,移动元素,遍历原来table中每个位置的链表,并对每个元素进行重新hash,在新的newTable找到归宿,并插入。

在并发的情况,发生扩容时,可能会产生循环链表,在执行get的时候,会触发死循环,引起CPU的100%问题

HashMap线程不安全的表现


如何线程安全的使用HashMap

Hashtable、ConcurrentHashMap、Synchronized Map

ConcurrentHashMap:

http://blog.csdn.net/justloveyou_/article/details/72783008

可以支持16个线程执行并发写操作,及任意数量线程的读操作。

本质是一个Segment数组,一个Segment实例包含若干个桶,每个桶中都包含一条由若干个HashEntry对象链接起来的链表。

1、通过锁分段技术保证并发环境下的写操作;

2、通过HashEntry的不变性、Volatile变量的内存可见性和加锁重读机制保证高效、安全的读操作;

3、通过不加锁和加锁两种方案控制跨段操作的安全性

Segment类继承于ReentrantLock类,从而使得Segment对象能充当锁的角色。在Segment类中,count变量是一个计数器,它表示每个Segment对象管理的table数组包含的HashEntry对象的个数,每个Segment对象中包含一个计数器,当需要更新计数器时,不用锁定整个ConcurrentHashMap,count是volatile的,这使得对count的任何更新对其它线程都是立即可见的。

在ConcurrentHashMap中,线程对映射表做读操作时,一般情况下不需要加锁就可以完成,对容器做结构性修改的操作才需要加锁。

ConcurrentHashMap对K/V的读写都是加锁的,是一个可重入锁(ReenTrantLock),当然这是一个Segment(片段锁),只会锁定某一个K/V,基于CAS调度,也就是与CPU的直接打交道的,使用的是NonfairSync,所以能保证最大的吞吐量.

不允许key值为null,也不允许value值为null。

读操作不需要加锁的奥秘:
HashEntry对象几乎是不可变的(只能改变Value的值),因为HashEntry中的key、hash和next指针都是final的。这意味着,我们不能把节点添加到链表的中间和尾部,也不能在链表的中间和尾部删除节点。

1、用HashEntry对象的不变性来降低读操作对加锁的需求;

2、用Volatile变量协调读写线程间的内存可见性;

3、若读时发生指令重排序现象,则加锁重读

CHM允许并发的读和线程安全的更新操作

在执行写操作时,CHM只锁住部分的Map

并发的更新是通过内部根据并发级别将Map分割成小部分实现的

高的并发级别会造成时间和空间的浪费,低的并发级别在写线程多时会引起线程间的竞争

CHM的所有操作都是线程安全

CHM返回的迭代器是弱一致性,fail-safe并且不会抛出ConcurrentModificationException 异常

CHM不允许null的键值

Arrays 静态类如何实现排序

双轴快排

N*log(N)

对于基本类型,使用调优的快排,双指针快排

对于引用类型,采用改进的归并排序 TimSort()

1、首先检查数组长度,如果比阈值(286)小,直接使用双轴快排

2、否则先检查数据中数据的连续性,标记连续升序,反转连续降序,如果连续性好,使用TimSort算法

3、否则使用双轴快排+成对插入排序


HashMap 和 Hashtable 区别

1、HashMap几乎等价于Hashtable,除了HashMap非synchronized的,并可以接受null,而Hashtable则不行

2、HashMap是非synchronized,而Hashtable是synchronized,意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable,而如果没有正确同步的话,多个线程是不能共享HashMap的

3、HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast。

4、Hashtable是线程安全的也是synchronized,在单线程环境下它比HashMap要慢。

通过分析Hashtable就知道,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占

你可能感兴趣的:(集合)