作为java中常用的各大集合,在实际开发中必不可少,在面试中也会经常被问到,各大集合各有特色,笔者在此详细总结一下java中的各大集合,希望能帮助到各位,如有纰漏,欢迎补充。
一、集合关系概述与Iterator迭代器
首先,给大家看一张java中集合关系图,大家应该会更加明了。由于尺寸原因,可能会比较模糊
由上图可以看到,java中分为单列集合Collection和双列集合Map两大种,Collection集合的父类接口是Iterable。但我们一般使用的是Iterator迭代器迭代集合,它和Iterable有什么关系呢?Iterable存在于java.lang包中,而Iterator存在于java.util包中。如下图,我们可以看到,Iterable里面封装了 Iterator 接口。所以只要实现了只要实现了Iterable接口的类,就可以使用Iterator迭代器了。而从上图中可以看到Map集合没有实现Iterable接口,所以,Map集合是不能使用Iterator迭代器进行迭代的。
那么,Collection集合是如何使用Iterator迭代器进行迭代的呢?
它主要有hashNext(),next(),remove()三种方法。它的一个子接口 ListIterator 在它的基础上又添加了三种方法,分别是 add(),previous(),hasPrevious()。也就是说如果实现 Iterator 接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会再被遍历到,通常无序集合实现的都是这个接口,比如HashSet;而那些元素有序的集合,实现的一般都是 LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个 元素,比如ArrayList。
Object next():返回迭代器刚越过的元素的引用,返回值是 Object,需要强制转换成自己需要的类型
boolean hasNext():判断容器内是否还有可供访问的元素
void remove():删除迭代器刚越过的元素
void add():往集合中添加一个元素
Object previous():返回迭代器前一个元素
boolean hasPrevious():判断容器中前面还有没有元素
这里我们引用一个Iterator 的实现类 ArrayList 来看一下迭代器的使用:暂时先不管 List 集合是什么,只需要看看迭代器的用法就行了
当然,不喜欢使用迭代器的小伙伴我们也可以使用foreach循环迭代集合,这里只是介绍一下Iterator迭代器。
二、List与Set的父类接口——Collection集合
由第一幅图我们可以清楚的看到Collection集合作为单列集合的顶级接口,下面还分为List集合和Set集合,需要注意的是,虽然它们拥有共同的父类接口,但两者的特性是截然不同的。List集合是一个有序且可以重复的集合,而Set集合则刚好相反,Set结合无序且不可重复。而List集合又分为三大类:ArrayList、LinkedList、Vector。Set集合则有一个典型的实现——HashSet。除了HashSet外还有LinkedHashSet和TreeSet。下面我们具体看一看它们的区别。
1、List 接口的三个典型实现:
①、List list1 = new ArrayList();
底层数据结构是数组,查询快,增删慢;线程不安全,效率高
②、List list2 = new Vector();
底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合
③、List list3 = new LinkedList();
底层数据结构是链表,查询慢,增删快;线程不安全,效率高
为什么底层数据结构为数组时就查询快,增删慢,而数据结构为链表时就查询慢增删快呢?这是因为我们都知道数组都是有索引的,而我们查询的时候是直接根据索引进行查询的,比如list.get(2)就是获取集合中索引为2的元素,集合会直接找到数组索引为2的元素。但如果在增删的时候,数组会牵扯到扩容或重组,就会很影响效率;而链表则刚好相反,链表是指向性的,一个连着一个,当我们查询的时候,它会一个一个的往后面或者往前面找,所以效率就很慢,在插入或者删除的时候则需往后面在链一个元素或者改变指针指向,就比较方便。这就是链表查询慢但增删快的原因。
2、Set的典型实现——HashSet
①、HashSet:不能保证元素的顺序;不可重复;不是线程安全的;集合元素可以为 NULL;
②、其底层其实是一个数组,存在的意义是加快查询速度。我们知道在一般的数组中,元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的关系,因此,在数组中查找特定的值时,需要把查找值和一系列的元素进行比较,此时的查询效率依赖于查找过程中比较的次数。而 HashSet 集合底层数组的索引和值有一个确定的关系:index=hash(value),那么只需要调用这个公式,就能快速的找到元素或者索引。
③、对于 HashSet: 如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
1、当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置
1.1、如果 hashCode 值不同,直接把该元素存储到 hashCode() 指定的位置
1.2、如果 hashCode 值相同,那么会继续判断该元素和集合对象的 equals() 作比较
1.2.1、hashCode 相同,equals 为 true,则视为同一个对象,不保存在 hashSet()中
1.2.2、hashCode 相同,equals 为 false,则存储在之前对象同槽位的链表上,这非常麻烦,我们应该约束这种情况,即保证:如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
注意:每一个存储到 哈希 表中的对象,都得提供 hashCode() 和 equals() 方法的实现,用来判断是否是同一个对象
对于 HashSet 集合,我们要保证如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
3、LinkedHashSet
Set linkedHashSet = new LinkedHashSet();
LinkedHashSet是有序但不可以重复的,因为底层采用 链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性
4、TreeSet
Set treeSet = new TreeSet();
TreeSet:有序;不可重复,底层使用 红黑树算法,擅长于范围查询。
* 如果使用 TreeSet() 无参数的构造器创建一个 TreeSet 对象, 则要求放入其中的元素的类必须实现 Comparable 接口所以, 在其中不能放入 null 元素
* 必须放入同样类的对象.(默认会进行排序) 否则可能会发生类型转换异常.我们可以使用泛型来进行限制
以上三个 Set 接口的实现类比较:
共同点:1、都不允许元素重复
2、都不是线程安全的类,解决办法:Set set = Collections.synchronizedSet(set 对象)
不同点:
HashSet:不保证元素的添加顺序,底层采用 哈希表算法,查询效率高。判断两个元素是否相等,equals() 方法返回 true,hashCode() 值相等。即要求存入 HashSet 中的元素要覆盖 equals() 方法和 hashCode()方法
LinkedHashSet:HashSet 的子类,底层采用了 哈希表算法以及 链表算法,既保证了元素的添加顺序,也保证了查询效率。但是整体性能要低于 HashSet
TreeSet:不保证元素的添加顺序,但是会对集合中的元素进行排序。底层采用 红-黑 树算法(树结构比较适合范围查询)
三、双列集合——Map
Map作为一个双列集合,和单列集合最大的区别就是它是以key--value键值对的方式进行存值的,查询、修改、删除等操作也同样是根据key操作value的。需要注意的是:Map集合中key不允许重复,但value可以。
1、严格来说 Map 并不是一个集合,而是两个集合之间 的映射关系。
2、这两个集合没每一条数据通过映射关系,我们可以看成是一条数据。即 Entry(key,value)。Map 可以看成是由多个 Entry 组成。
3、因为 Map 集合即没有实现于 Collection 接口,也没有实现 Iterable 接口,所以不能对 Map 集合进行 foreach 遍历。
Map集合可以分为HashMap、LinkedHashMap、Properties三种。
1.HashMap
除了上述获取Map集合的key集合然后用迭代器迭代之外,小伙伴们也可以使用下面这种方法迭代Map集合:
Map hashMap = new HashMap<String, Object>;
Set<Map.Entry<String, Object>> entrys = hashMap.entrySet();
for(Map.Entry<String, Object> entry: entrys){
String key = entry.getKey();
Object value2 = entry.getValue();
System.out.println(key+"="+value2);
}
HashMap 存放是无序的,如果要让有序存放 可使用 LinkedHashMap 类。LinkedHashMap的使用方法和HashMap的使用方法是一样的,这里就不重复赘述了。
3.Properties
Properties 主要用来存储字符串类型的键和值,实际开发中经常用来存取应用的配置项