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表的,即每次锁住整张表让线程独占