Java集合类
1、概念
-
数组与集合的区别
- 数组长度不可变化而且无法保存具有映射关系的数据;集合类用于保存数量不确定的数据,以及保存具有映射关系的数据
-
数组元素既可以是基本类型的值,也可以是对象;集合只能保存对象
-
Java集合类存放于 java.util 包中,是一个用来存放对象的容器
-
集合只能存放对象
-
集合存放的是多个对象的引用,对象本身还是放在堆内存中
-
集合可以存放不同类型,不限数量的数据类型
-
Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue(Java5新增的队列),因此Java集合大致也可分成List、Set、Queue、Map四种接口体系,(注意:Map不是Collection的子接口)。
其中List代表了有序可重复集合,可直接根据元素的索引来访问;Set代表无序不可重复集合,只能根据元素本身来访问;Queue是队列集合;Map代表的是存储key-value对的集合,可根据元素的key来访问value。
2、架构图
3、集合类的详解
3.1、Collection接口
Collection接口是处理对象集合的根接口,其中定义了很多对元素进行操作的方法,AbstractCollection是提供Collection部分实现的抽象类。
3.2、Set集合类
继承自Collection。不能存在相同的对象,无序的,就是数学意义上的集合。具体实现类有HashSet,LinkedHashSet,TreeSet等。
3.2.1、HashSet
HashSet 底层是使用了哈希表来支持的,特点:存取速度快。往Hashset添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值,然后通过元素的哈希值经过移位等运算,就可以算出该元素在哈希表中的存储位置。
3.2.2、LinkedHashSet
LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同,因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法。并通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。
3.2.3、TreeSet
treeSet 底层是以红-黑树的数据结构实现的,默认对元素进行自然排序(String)。 如果在比较的时候两个对象返回值为0,那么元素重复。
自然排序:TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素的大小关系,然后将元素按照升序排列,这就是自然排序。如果试图将一个对象添加到TreeSet集合中,则该对象必须实现Comparable接口,否则会抛出异常。当一个对象调用方法与另一个对象比较时,例如obj1.compareTo(obj2),如果该方法返回0,则两个对象相等;如果返回一个正数,则obj1大于obj2;如果返回一个负数,则obj1小于obj2。对于TreeSet集合而言,它判断两个对象是否相等的标准是:两个对象通过compareTo(Object obj)方法比较是否返回0,如果返回0则相等。
定制排序:想要实现定制排序,需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由Comparator对象负责集合元素的排序逻辑。
综上:自然排序实现的是Comparable接口,定制排序实现的是Comparator接口。(具体代码实现会在后续章节中讲解)
3.2.4、EnumSet类
EnumSet是一个专为枚举类设计的集合类,不允许添加null值。EnumSet的集合元素也是有序的,它以枚举值在Enum类内的定义顺序来决定集合元素的顺序。
3.2.5、Set实现类的性能分析
HashSet的性能比TreeSet的性能好(特别是添加,查询元素时),因为TreeSet需要额外的红黑树算法维护元素的次序,如果需要一个保持排序的Set时才用TreeSet,否则应该使用HashSet。
LinkedHashSet是HashSet的子类,由于需要链表维护元素的顺序,所以插入和删除操作比HashSet要慢,但遍历比HashSet快。
EnumSet是所有Set实现类中性能最好的,但它只能 保存同一个枚举类的枚举值作为集合元素。
以上几个Set实现类都是线程不安全的,如果多线程访问,必须手动保证集合的同步性
3.3、List集合类
List接口扩展自Collection,它可以定义一个允许重复的有序集合,从List接口中的方法来看,List接口主要是增加了面向位置的操作,允许在指定位置上操作元素,同时增加了一个能够双向遍历线性表的新列表迭代器ListIterator。AbstractList类提供了List接口的部分实现,AbstractSequentialList扩展自AbstractList,主要是提供对链表的支持。具体实现类有ArrayList,LinkedList,Vector等(已经被废弃,很少使用)。
3.3.1、ArrayList
最常用的List接口实现类,底层使用可变长度的动态数组实现。ArrayList有一个初始容量(capacity = 10),当元素数量大于初始容量时进行扩容,新的数组长度 = 旧数组长度 + 旧数组长度 / 2;
因为每个元素都有固定的位置索引,所以根据索引查询元素的速度非常快。如果在中间插入元素时,由于后面的元素全部要后移一位,所以性能会比较差;
由于没有做并发访问控制,所以它是一个非线程安全的集合。允许重复元素或null元素。
3.3.2、LinkedList
List接口的双向链接的实现类,允许NULL元素。它表现上是一个有序的集合,但内存中其实是无序保存,所以它插入的速度会很快,但是查询一个元素的速度较ArrayList速度慢很多。是一个非线程安全的集合。
3.4、Map集合类
以键值对的形式存放对象。key-value。一般是key为String类型,value为Object的类型。具体实现类有HashMap,LinkedHashMap,TreeMap等。
3.4.1、HashMap与Hashtable
HashMap与Hashtable是Map接口的两个典型实现,它们之间的关系完全类似于ArrayList与Vertor。HashTable是一个古老的Map实现类,它提供的方法比较繁琐,目前基本不用了。
HashMap与Hashtable主要存在以下两个典型区别:
HashMap是线程不安全,HashTable是线程安全的。
HashMap可以使用null值最为key或value;Hashtable不允许使用null值作为key和value,如果把null放进HashTable中,将会发生空指针异常。
为了成功的在HashMap和Hashtable中存储和获取对象,用作key的对象必须实现hashCode()方法和equals()方法。
HashMap工作原理如下:
HashMap基于hashing原理,通过put()和get()方法存储和获取对象。当我们将键值对传递给put()方法时,它调用建对象的hashCode()方法来计算hashCode值,然后找到bucket位置来储存值对象。当获取对象时,通过建对象的equals()方法找到正确的键值对,然后返回对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会存储在链表的下一个节点中。
3.4.2、LinkedHashMap
LinkedHashMap使用双向链表来维护key-value对的次序(其实只需要考虑key的次序即可),该链表负责维护Map的迭代顺序,与插入顺序一致,因此性能比HashMap低,但在迭代访问Map里的全部元素时有较好的性能。
3.4.3、TreeMap
TreeMap是SortedMap的实现类,是一个红黑树的数据结构,每个key-value对作为红黑树的一个节点。TreeMap存储key-value对时,需要根据key对节点进行排序。TreeMap也有两种排序方式:
自然排序:TreeMap的所有key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则会抛出ClassCastException。
定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。
3.4.4、Map实现类的性能分析
HashMap通常比Hashtable(古老的线程安全的集合)要快
TreeMap通常比HashMap、Hashtable要慢,因为TreeMap底层采用红黑树来管理key-value。
LinkedHashMap比HashMap慢一点,因为它需要维护链表来爆出key-value的插入顺序。
3.5、迭代器Iterator
3.5.1、概念
迭代器(Iterator)是一种设计模式、提供了一种方法,来对集合、容器进行遍历的方式,不需要关注底层数据结构和数据类型,来达到底层和上层遍历解耦的目的
3.5.2、迭代器方法
boolean hasNext() :判断集合是否还有元素; true表示还存在元素 ,false表示不存在元素
E next():返回当前数据
void remove():删除元素
3.6、ListIterator接口
ListIterator接口继承Iterator接口,提供了专门操作List的方法。ListIterator接口在Iterator接口的基础上增加了以下几个方法:
boolean hasPrevious():判断集合里是否存在上一个元素。如果有,该方法返回 true。
Object previous():返回集合里上一个元素。
void add(Object o):在指定位置插入一个元素。