Colletion接口,注意他只是个接口,里面的方法都是抽象方法,是没有方法体的。方法的底层原理也得看是List集合(底层是数组+链表)实现他还是Set集合(哈希值+散列函数+数组+链表)实现他
Colletion接口的常用方法
1.添加元素:add(Object obj)
2.删除元素:remove(Object obj)
3.获取集合中有效元素的个数size()
4.转成对象数组Object[] toArray()
5.获取集合对象的哈希值。hashCode()
6.获取迭代器进行集合中的遍历 iterator()
7.判断集合是否包含某个元素contains(Object obj)
List接口的常用方法,除了从Collection接口中继承过来的常用方法外新添加的常用方法。基本都是基于底层是有序(物理位置存储是连续的)数组。添加了很多根据索引来操作集合的方法。
1.根据索引把元素插入到指定位置:add(int index,Object obj)
2.根据索引删除元素:remove(int index)
3.根据索引修改元素:set(int index,Object value)
4.根据索引查看指定位置的元素:get(int index)
看似很多,其实都是根据链表结构的特点,对头尾节点的操作(对头尾节点的增删查)
ArrayList作为List接口的实现类,方法都是从List接口实现过来的(根据自己的底层结构是数组来重写List接口里面的方法),没有额外的方法。因为ArrayList底层就是数组,List接口里面的方法非常适合ArrayList。没必要再写额外的方法了。
linkedList的额外方法(除了继承的来和实现得来的方法之外)
1、void addFirst(Object obj)
2.void addLast(Object obj)
3.Object getFirst()
4.Object getLast()
5.Object removeFirst()
6.Object removeLast()
ArrayList集合的常用方法底层原理
ArrayList的两个构造方法,一个空参构造,一个指定数组长度的构造方法。如果是空参构造,则默认初始化数组长度为0,等到添加第一个数组的时候再将该数组扩容到10容量。这样的话让创建集合而没有添加的元素减少数组空间的浪费。
add(E e)。这个话是先判断数组的长度能否容纳这个新元素,如果能则直接添加到数组的空余空间。如果不能则进行扩容,扩容后再添加进去。
还有一个插入的方法add(int index,Object obj)。插入之前先判断数组也没有越界,然后再判断数组是否需要扩容(数组的长度能否容纳这个插入的元素)。插入原理也是元素的移动,这里就不再重复了。
remove(int index)。这个的话是先判断所给的索引也没有超出的数组长度,如果没有则把要删除的元素删除(删除原理是把后面的元素统统往前移一位,覆盖到要删除的元素)。
set(int index,Object obj)。这个也只是判断索引也没有超出数组长度,然后直接根据索引定位元素位置进行修改即可。
get(int index)。这个原理与set方法基本一致,不再重复。
ArrayList底层数组扩容原理:每次扩容都是在原来数组容量的基础上扩大1.5倍。扩容的过程就是再造一个指定容量(1.5倍)的数组。然后把原来数组的数据Copy过来。
提示:这种操作的代价是很高的,因此在
实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,
要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,
通过调用 ensureCapacity 方法来手动增加 ArrayList 实例的容量。
ArrayList集合底层原理和Vector集合底层原理的异同。
1.ArrayList集合创建 的时候,默认容量是0,而Vector是10。2.在扩容的时候,ArrayList默认是扩容1.5倍,而Vector是两倍。3.也是最大的区别,ArrayList是线程不安全的,而Vector是线程安全的。
linkedList的常用方法。方法基本都是从List接口实现过来的(根据自己的底层结构是链表来重写List接口里面的方法)
底层原理:底层原理是双向链表。就是双向链表的增删改查操作。这个刚学过数据结构不久,应该是相当熟悉了,就不在这里展开重复了。
ArrayList和linkedList的对比:二者都线程不安全,相对线程安全的Vector,执行效率高。此外,ArrayList是实现了基于动态数组的数据结构,linkedList基于链表的数据结构。对于随机访问get和set,ArrayList觉得优于linkedList,因为linkedList要移动指针。对于新增和删除操作add(特指插入)和remove,linkedList比较占优势,因为ArrayList要移动数据。
Map集合接口的实现类
HashMap
HashMap常用方法的底层原理。总体来看是哈希值+数组+链表(或者红黑树)。链表和红黑树主要是用来解决哈希冲突的问题。
put方法
1.通过哈希值计算出桶的位置。(数组的每一个物理存储位置叫做桶)
2.判断哈希表和桶是否为空,如果桶为空,则直接将该节点插入到桶中。
3.如果桶不为空,那么就意味着有哈希冲突(或者说是碰撞冲突)解决方式有两种:取决于桶的底层数据结构是链表还是红黑树。所以说有两种解决哈希冲突的方式。
具体过程:我们得先判断桶上已有的元素和我们要插入的元素是否重复(遍历桶上的每一个元素和我们要插入的元素的键进行比较,如果有相等,则证明重复,然后用最新插入元素的键对应的值替换掉原来的值。如果没有相等,则证明没有重复,然后在桶上插入该节点即可。)
4,插入元素完成后还要检查数组的有效元素个数是否大于阙值,如果大于则对数组进行扩容操作。
remove方法
1.通过哈希值计算出桶的位置。(数组的每一个物理存储位置叫做桶)
2.判断哈希表和桶是否为空,如果桶为空,则返回null值。如果桶不为空,则直接判断桶上的元素是否是要删除的节点,如果是则删除该节点。如果不是则遍历桶上的后续节点,直到找到要删除的节点为止,找到后将该节点删除(或者遍历完都没有找到,则返回null)
get方法
1.通过哈希值计算出桶的位置。(数组的每一个物理存储位置叫做桶)
2.判断哈希表和桶是否为空,如果桶为空,则返回null值。如果桶不为空,则直接判断桶上的元素是否要查找的节点,如果是则返回该节点。如果不是则遍历桶上的后续节点,直到找到要查找的节点然后返回该节点(或者遍历完都没有找到,则返回null)
HashMap集合扩容的底层原理,还有为什么在达到一定条件后,链表会转换为红黑树
为什么要进行扩容,两点理由:1.数组的长度不足以容纳现有的元素了。2.尽可能的减少哈希冲突,因为哈希冲突越多,查询效率就会越低。
1.HashMap的默认容量为16。2.默认的负载因子为0.75。.当数组的有效元素个数size = capacity()* load factor的时候,数组就会进行扩容。
负载因子的作用是决定什么时候数组可以扩容。1.负载因子为1的时候,意味着数组要存满才能进行扩容,此时虽然空间利用率上去了,因为是通过哈希值计算的桶的位置,要想把数组存满,就必然会发生大量的哈希冲突(HashCode函数计算出来的值是随机,导致分配到数组位置也是随机分配,很难很难才会数组存满,因为他的随机性,要想把数组填满,就必然会发生大量的哈希冲突),大量的哈希冲突导致桶的结构必定会从链表结构变为红黑树结构并且红黑树结构的节点会非常多,导致查询起来时间效率非常低,等于用时间来换空间。2.第二种情况,当负载因子为0.5时,这意味着数组存一半的空间就会自动扩容,哈徐冲突发生的比较少,但2M的空间你只用了,浪费了50%的空间,空间利用率大大降低,等于是用空间换时间。3.经过大量的实践,实践出负载因子为0.75时,时间效率不会太低,空间也没有浪费太多,也就是空间利用率也不会太低,属于是实践找到了时间效率和空间利用的相对平衡。
达到一定条件后,链表为什么要转换为红黑树,就是因为达到条件后红黑树的综合效率高于链表,所以要把链表转换为红黑树。具体详解在下面有写。
当桶上的链表结构的元素数量大于8时并且数组的容量超过 MIN_TREEIFY_CAPACITY = 64时,链表才会转化成红黑树去存储桶上那些元素,否则还是会使用数组扩容机制来减少哈希冲突。当红黑树上的元素少于6时,红黑树就会转化回链表来存储那些元素。
原理:链表的时间效率o(n),虽然低于红黑树o(logn)。但在节点数较少的时候,链表和红黑树的时间差距表现得不是很明显,但是红黑树需要用的空间是比链表多一倍,无论节点数的多少。所以只有当节点数足够多的时候,链表的时间效率和红黑树的时间效率比较,表现的效果才明显,权衡考虑,在节点数较少的时候,链表的综合效率(时间和空间)是要远远优于红黑树的。但在节点数较多的时候,红黑树的综合效率是要优于链表的。那为什么这个节点的临界值设为8呢?统计显示,节点数达到的该概率已经低至千分之一了,因为节点数要到达8的几率太低了,等他真的到达8时,哈希数组已经扩容到相当大的容量,不适宜再继续使用扩容来减少哈希冲突的发生了,而是要把桶上的链表结构转为红黑树,从而提高集合的查询效率。
linkedHashMap
底层和HashMap区别的就是:linkedHashMap多了一个双向链表来维护插入键值的顺序(默认是插入顺序,当然你也手动设置访问顺序),其他和HashMap的底层基本一致。(linkedHashMap是继承于HashMap并且实现了Map接口)
TreeMap
暂时只是用来排序,底层到时候再深挖
Set集合底层调用的就是Map集合。
简单说一下map集合和Set集合的区别。
最大的区别一个是单列集合,一个是双列集合,还有一个就是一个是用键来计算哈希值的,一个是用对象来计算哈希值的(需要自己重写equals方法和HashCode方法)。相对效率上来看HashMap要优于HashSet的。
最后说一下两个比较器Comparable和Comparator的区别:
Comparable接口是作为内部比较器,也就是要在自己需要排序的元素类中实现该接口。而Comparator接口是作为外部比较器的,不需要在自己需要排序的元素类中实现该接口,只需要调用Colletions.sort(List