集合

一、 集合家族(常用)

在这里插入图片描述

二、 两大接口概括介绍:

1. Collection接口:规范单值集合的接口;

     1. Collection接口没有直接实现类,有更具体的子接口:List、Set等;
     2. Collection接口定义了一些公共方法(查阅API):
        // 接口是不能直接new对象的,但为了关注Collection接口中的方法可以多态引用,创建其子类的对象;
        > 1. boolean add(Object obj): 向集合添加一个指定元素(确保此集合包含指定的元素)。
        > 2. boolean addAll(Collection c):合并两个集合元素(将指定集合中的所有元素添加到此集合)。    
        > 3. boolean contains(Object o):如果此集合包含指定的元素,则返回 true 。
        > 4. boolean containsAll(Collection c):如果此集合包含指定集合中的所有元素,则返回true。
        > 5. boolean remove(Object o):从该集合中删除指定元素的单个实例(如果存在)。  
        > 6. boolean removeAll(Collection c):删除本集合中 本集合与指定集合的交集。  
        > 7. void clear():清空此集合。  
        > 8. boolean retainAll(Collection c):仅保留本集合中 本集合与指定集合的交集元素。  
		> 9. Object[] toArray():返回一个包含此集合中所有元素的数组。  
	3. Collection系列集合的遍历方法:
		> 1. 调用toArray() 方法先把集合转换成数组,再遍历数组。
		> 2. Collection系列的每个集合内部都有一个迭代器,调用iterator() 方法,获得其内部提供的迭代器进行遍历。
		> 3. foreach循环。
		     什么样的容器可以使用foreach循环来遍历呢?
			 ——凡是实现了java.lang.Iterable接口的集合或容器都可以用foreach来遍历。

相关接口详细讲解:java.util.Iteratorjava.lang.Iterable


1.1 子接口——List:

    1) List系列的集合都是有序的、可重复的;
    2) List系列的常用集合: ArrayList、Vector、Stack、LinkedList;
    3) 在Collection接口实现的方法基础上又新增了一些方法:(详细可查阅API):
    	> 1. void add(int index, E element):将指定的元素插入此列表中的指定位置。 
    	> 2. boolean addAll(int index, Collection c):将指定集合中的所有元素插入到此列表中的指定位置。
    	> 3. E remove(int index):删除该列表中指定位置的元素。  
    	> 4. E get(int index):返回此列表中指定位置的元素。 
    	> 5. E set(int index, E element):用指定的元素替换此列表中指定位置的元素。 
    	> 6. int indexOf(Object o):返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。 
    	> 7. int lastIndexOf(Object o):返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。  
    	> 8. List<E> subList(int fromIndex, int toIndex):返回此列表中指定的 fromIndex (含)和 toIndex之间的视图。 
    	> 9. ListIterator<E> listIterator():返回列表中的列表迭代器。  
    4)说明:虽然List系列的集合都是可以通过索引进行操作的,但是例如LinkedList这样的集合是不建议使用索引相关方法进行操作的,因为他的底层不是数组,如果使用索引相关方法还需要从头到尾遍历来对应索引,效率是不高的。

相关接口详细讲解:java.util.listIterator


1.1.1 List接口的实现类——Vector(动态数组):


 1. 物理结构:数组。
 2. 同为动态数组Vector和ArrayList的区别是什么呢?
 	> 1. Vector是旧版本的动态数组,ArrayList相对于Vector是新一点的版本。
 	> 2. Vector是线程安全的,ArrayList不能保证线程安全。
 	> 3. Vector默认扩容为原来2倍,ArrayList默认扩容为原来1.5倍。
 	> 4. Vector支持三种遍历方式:① foreach、② Iterator、③ Enumeration。
 		 ArrayList支持两种便利方式:① foreach、② Iterator。 
 	> 5. ArrayList和Vector都有一个方法提供创建时指定容量的功能。

1.1.2 List接口的实现类——ArrayList(动态数组):


 1. 物理结构:数组。
 2. JDK1.7以后ArrayList初始化是空数组,这是为什么呢?
 	我们很多使用ArrayList集合的情况是用来装载查询结果,这就存在空数组的情况,初始化时数组容量为0就可以避免空间上的浪费。在首次向集合中添加元素时扩展容量为10,之后的每次扩容默认扩展为原来的1.5倍。

1.1.3 List接口的实现类——Stack(动态数组):


 1. 物理结构:数组。
 2. 先进后出(FILO),是Vector的子类,比Vector多了几个方法,并且先进后出的这种特点也是调用这几个方法实现的。
 	(1)E peek():查看此堆栈顶部的对象,而不从堆栈中删除它。  
 	(2)E pop():删除此堆栈顶部的对象,并将该对象作为此函数的值返回。  
 	(3)E push(E item):将元素压入此堆栈的顶部。 


1.1.4 List接口的实现类——LinkedList(双向链表):


 1. 物理结构:链表。
 2. LinkedList可以作为双向链表、栈、队列、双端队列来使用
 	(1)LinkedList是如何实现双向链表结构的呢?
 	
 		> 1. E getFirst():返回此列表中的第一个元素。 
 		> 2. E getLast():返回此列表中的最后一个元素。
 		> 3. E get(int index):返回此列表中指定位置的元素,底层会检查这个索引更靠近头还是尾来决定从头查找还是从尾查找。 
 		> 4. boolean offerFirst(E e):在此列表的前面插入指定的元素。 
 		> 5. boolean offerLast(E e):在该列表的末尾插入指定的元素。  
  		> 6. int indexOf(Object o):返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。  
 		> 7. int lastIndexOf(Object o):返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。  
 		  		
 	(2)LinkedList是如何实现栈结构的呢?
 	
  		> 1. E peek():检索但不删除此列表的头(第一个元素)。 
 		> 2. E pop():从此列表表示的堆栈中弹出一个元素。  
 		> 3. void push(E e):将元素推送到由此列表表示的堆栈上。 
 		
 	(3)LinkedList是如何实现队列结构的呢?
 	
 				   抛出异常				返回特殊值
  		> 1. 添加:boolean add(E e) 	boolean offer(E e) 
 		> 2. 删除:E remove()  			E poll()  
 		> 3. 检查:E element()  		E peek() 
 		
 	(4)LinkedList是如何实现双端队列结构的呢?(JDK1.6以后才支持的)
 	
 			 	  队首元素												队尾元素
 			 	  抛出异常					返回特殊值					抛出异常				返回特殊值
  		> 1. 添加:void addFirst(E e)		boolean offerFirst(E e)		void addLast(E e) 	boolean offerLast(E e)  
 		> 2. 删除:E removeFirst()			E pollFirst()   			E removeLast() 		E pollLast()  
 		> 3. 检查:E getFirst() 			E peekFirst()  				E getLast()  		E peekLast() 

1.2 子接口——Set:


 1)Set系列集合的元素都是不能重复的。
 2)Set系列的常用集合:HashSet、TreeSet、LinkedHashSet。
 3)Set接口中的方法都是从Collection根接口中继承过来的,没有自己声明其他的方法。

1.2.1 Set接口的实现类——HashSet


 1. HashSet集合只能保证集合中元素的不可重复性,不能保证添加顺序也不能保证大小顺序。
 2. HashSet是如何判定存入其中的元素是否相同的呢?
	(1)先比较存入元素的hash值如果hash值不同则两元素一定不同
	(2)如果两个元素的hash值是相同的再调用equals方法比较
 3. HashSet的底层实现其实是HashMap,我们add到HashSet中的元素是作为HashMap的键,它的值是一个Object类型的常量对象PERSENT。也就是说HashSet中的元素对象是共用同一个值的,即PERSENT。

1.2.2 Set接口的实现类——TreeSet


 1. HashSet和TreeSet的区别:当我们需要满足集合中元素不可重复性同时又要给元素排大小时选择TreeSet否则选择HashSet效率更好。
 2. 想要添加到TreeSet集合中的元素类型一定是实现了java.lang.Comparable接口或java.util.Comparator接口的。
 3. TreeSet是如何判定存入其中的元素是否相同的呢?按照元素的大小来决定两元素是否相同。
 4. TreeSet的底层实现其实是TreeMap。

1.2.3 Set接口的实现类——LinkedHashSet


 1. HashSet和LinkedHashSet的区别:LinkedHashSet是HashSet的子类,当我们想要实现集合元素的不可重复性和添加顺序时选择LinkedList否则选择HashSet效率更高。
 2. LinkedHashSet是如何判定存入其中的元素是否相同的呢?
	(1)先比较存入元素的hash值如果hash值不同则两元素一定不同
	(2)如果两个元素的hash值是相同的再调用equals方法比较
 3. LinkedHashSet的底层实现其实是LinkedHashMap。
	

2. Map接口:规范键值对集合的接口;

 1. Map接口的常用方法:
 
 	> 1. V put(K key, V value):将指定的值与该映射中的指定键相关联。 
 	> 2. void putAll(Map m):将指定地图的所有映射复制到此映射。 
 	> 3. int size():返回此地图中键值映射的数量。 
 	> 4. boolean containsKey(Object key):如果此映射包含指定键的映射,则返回 true 。 
 	> 5. boolean containsValue(Object value):如果此地图将一个或多个键映射到指定的值,则返回 true 。 
 	> 6. boolean isEmpty():检查此地图中的映射是否为空,则返回 true  
 	> 7. V get(Object key):返回指定键对应的值。 
 	> 8. void clear():从该地图中删除所有的映射。  
 	> 9. V remove(Object key):如果指定键存在,从该地图中删除这个键并删除该键的映射,并返回该键对应的值。
 	
 2. Map集合的遍历方式:
 
 	Map接口没有实现java.lang.Iteratable接口所以不支持foreach循环遍历;
 	Map也没有提供返回Iterator类型迭代器的方法;
 	
	(1)Set<K> keySet() 方法获取所有的Key然后循环每一个key来获取对应的值。
	(2)Collection<V> values()  方法获取所有的value在遍历他们。
	(3)Set<Map.Entry<K,V>> entrySet() 方法获取所有的映射关系,然后遍历他们。
	
3. Map接口下所有的集合都要求key是不能重复的,这是怎么保证的?
	(1)HashMap、HashTable、LinkedHashMap、Properties都是先比较hashCode如果相同再equals比较如果最后还相同就认为这两个元素是相同的。
	(2)TreeMap是比较key的大小来判定两元素是否相同的。
	无论是什么比较方式,如果认定key重复了那么后来的value将会覆盖掉之前的value与这个key形成映射关系。
 	

2.1 Map接口的实现类——HashMap(哈希表)


 1. 同为哈希表HashMap和HashTable的区别是什么呢?
 	(1)HashTable是旧版本,是线程安全的;而HashMap相对来说是新版本是线程不安全的。
 	(2)HashTable中的key和value是不能为null的;但HashMap中的key和value是允许为空的。

 2. HashMap和LinkedHashMap的关系是什么呢?
 	(1)LinkedHashMap是HashMap的子类,比HashMap多维护了映射关系的添加顺序。也就是说:HashMap是无序的,LinkedHashMap是有添加顺序的。
 	(2)LinkedHashMap多维护了添加顺序 也就意味着效率更低一些,所以只在需要保证添加顺序时使用LinkedHashMap否则使用HashMap比较好。
 	
3. HashMap和TreeMap的区别是什么呢?
	(1)HashMap是无序的,TreeMap是有大小顺序的,按照key的大小顺序。

2.2 Map接口的实现类——HashTable(哈希表)

2.3 Map接口的实现类——TreeMap


1. TreeMap是按照key大小顺序排序的,所以要想将某类型元素存入TreeMap就要保证一下两点之一:
	(1)该类型实现了java.lang.Iteratable接口或java.util.Iterator接口。
	(2)在创建TreeMap时指定一个java.util.Iterator接口的实现类对象。

2.4 Map接口的实现类——LinkedHashMap

2.5 Map接口的实现类——Properties


1. Properties是HashTable的子类,也就是说Properties集合中的key和value是不允许为null的,并且Properties的key和value的类型都必须是String类型。
2. 通常用于存储配置文件的配置属性信息。
3. 为了增强可读性Properties类增加了两个方法:
	(1)Object setProperty(String key, String value) 添加。
	(2)String getProperty(String key) 获取。

三、Map的底层实现


1.  哈希表系列的底层:
 					数组+链表;
 					数组+链表/红黑树;
2. TreeMap的底层:红黑树;

1. HashMap的底层实现


1. JDK1.6 HashMap:数组+链表;
  (1)new HashMap():table数组初始化为一个长度为16的空数组;
2. JDK1.7 HashMap:数组+链表;
  (1)new HashMap():table数组初始化为一个长度为0的空数组;
  (2)DEFAULT_LOAD_FACTOR:默认加载因子,用来计算阈值;
  	   threshold:阈值,底层数组并不是装满才会扩容而是到达阈值就考虑扩容。
  	   threshold = capacity * loadFactor;
  (3)put():当我们向HashMap中添加元素时:
  			  ① 首先会检查table数组是否为空,如果为空就会把数组扩容为长度16的Entry类型数组。同时threshold值计算为12。
  			  这里注意:如果我们创建时手动指定了初始容量但这个初始容量却不是2的n次方,会自动纠正为2的n次方。  			  
  			  那么,为什么要调成2的n次方呢?
  			  ————原因一:后面计算每一个元素的index时候需要用到table.length,确保数组长度为2的n次方就确保了计算后的index范围在[0~数组长度]之间;
  			  ————原因二:根据内部的散列调整算法,2的n次方可以保证更好的散列效果;
  			  ② 判断key若为null,那么特殊对待,该映射关系的hash为0,index也为0;
  			  注意:所有key为null的映射关系都在table[0]下,但并不是说table[0]下面的映射关系的key一定为null;
  			  ③ 为存入的key的hash值计算一个散列后的hash值;
  			  ④ 计算该元素的index = hash & table.length-1;
  			  ⑤ 查看table[index]下面是否有key和我们即将添加元素的key是相同的如果有就替换掉之前的value,结束;
  			  ⑥ 如果没有相同的key,开始考虑添加入新的映射关系:
  			    > 首先,检查是否需要扩容;扩容条件:table的size达到阈值,并且table[index]下面已经存在映射关系;
  			    > 如果扩容了就会重新计算hash值;
  			    > 然后把新的映射关系包装成一个entry对象放到table[index]中,将table[index]下原有的元素连接到这个新的entry的next上;
3. JDK1.8 HashMap:数组+链表/红黑树;
  (1)DEFAULT_INITIAL_CAPACITY:默认初始化容量(16);
  (2)MAXIMUM_CAPACITY:最大容量
  (3)DEFAULT_LOAD_FACTOR:默认加载因子(0.75f);
  (4)TREEIFY_THRESHOLD:树化阈值(8)。当某个索引下的链表长度到达这个值就考虑开始树化;
  (5)UNTREEIFY_THRESHOLD:反树化阈值(6)。当某索引下的树结点少于这个临界值,开始考虑将树变为单链表。
  (6)MIN_TREEIFY_CAPACITY:最小树化容量(64)。当某索引下单链表长度达到阈值并且table数组容量达到最小树化容量,才树化;
  	                                           若单链表长度达到阈值但table数组容量没有达到最小树化容量,会扩容;
  (7)Node<K,V>[] table:数组;
  (8)int size:记录有效映射关系的个数;
  (9)threshold:阈值。当size达到阈值时table考虑扩容;
  (10)loadFactor:默认加载因子。影响扩容频率;

	> 3.1 new HashMap():
		public HashMap() {
			// loadFactor = 0.75, size = 0, threshold = 0, table = null;
	        this.loadFactor = DEFAULT_LOAD_FACTOR; // 其他的参数为默认值;
	    }
	> 3.2 put(key,value):
		  如果是第一次添加,也就是table == null,就会把table初始化为16,阈值计算为12;
		  如果不是第一次添加,也就是table != null:
			① 根据key的hash计算出这组映射关系的存放处数组下标i,如果table[i]下没有元素就把这组映射关系存到table[i]下;
			② 如果table[i]下有元素,要检查一下table[i]下是否为一棵树,如果是树就添加树节点并且符合树化条件就要考虑树化,或者扩容;
			③ 不论是否树化 存入之前都要判断是不是想要存入的映射关系的key已经存在了,若存在则根据设定决定替换其value或保持原value;
			④ 添加结点之后还要检查是否达到了阈值,检查是否需要扩容;
		    注意:每次扩容底层都是新建了数组再将所有结点重新计算index并搬家到新的数组,所以扩容的成本是比较高的;
	> 3.3 关于源码中的变量modCount:
		  每一个集合中都有一个modCount,用来记录当前集合被修改的次数。(凡是会改变集合中元素个数的操作都会被modCount记录);
		  作用:当我们使用迭代器或foreach来遍历集合的时候用来校验。
		  过程:在我们初始化迭代器的时候会将当前的modCount赋值给expectedModCount,在迭代过程中,不论是其他线程还是本线程一旦修改了集合元素就会导致modCount改变,那么当迭代器检验到expectedModCount != modCount就会抛出异常ConcurrentModificationException。
	

声明:该文章由本人自学后记录,仅供参考,如有疏漏 感谢您的指正!谢绝转载!

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页