在Java开发中,会经常使用到集合,那么何为集合呢?
集合是用于存储批量数据的容器工具,可以将其看做一个可变长度的数组。
在Java的API中集合被包含在java.util包中,被称作collection框架。
Collection
我们先从Collection接口说起,接口Collection是collection层次结构的根接口,表示一组对象,一些collection是允许重复,一些则不可以。一些是有序的,而一些则是无序的。
所有通用的Collection实现类(通常通过它的一个子接口间接实现Collection)应该提供两个“标准”构造方法:一个是 void(无参数)构造方法,用于创建空 collection;另一个是带有Collection类型单参数的构造方法,用于创建一个具有与其参数相同元素新的 collection。实际上,后者允许用户复制任何 collection,以生成所需实现类型的一个等效 collection。尽管无法强制执行此约定(因为接口不能包含构造方法),但是 Java 平台库中所有通用的Collection实现都遵从它。——官网介绍
下图是常用Collection集合的简化UML类图:
Set接口
Set类型的集合存储的元素是无序的且不可重复。
HashSet
对于HashSet,是基于HashMap(我们将会在后面介绍)实现的。HashSet底层采用HashMap来保存所有元素。主要用来做高性能的集运算,为快速查找而设计。存入HashSet的对象必须重写hashCode()方法。
当我们向HashSet集合添加元素时,它是按照hash算法来存储集合中的元素。首先HashSet会调用该对象的hashCode()方法来获取到该对象的hashCode值,然后根据hashCode值决定该对象在Set集合中的存储位置。HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,同时两个元素的hashCode()方法返回值也相等。因此如果两个元素通过equals方法比较返回为true,但它们通过hashCode()方法返回的值的比较不相等,那么HashSet会将这两个元素存储在不同的位置,仍然能够添加成功。
TreeSet
TreeSet是一种可指定排序方式的Set集合。其底层数据结构是二叉树。通过比较两元素的结果是否为0来保证元素的唯一性。
对于TreeSet的指定排序方式的方法有两种:
方式一:让元素自身具备比较性。只要让元素实现Comparable接口,并覆盖compareTo方法即可,当compareT方法的返回值为0时,就是相同元素。
方式二:让集合自身具备比较性。该方式通过自定义比较器来实现,自定义类实现Comparator接口,覆盖compare方法,将该Comparator接口实现类的对象作为参数传递给TreeSet的构造方法即可。
List接口
List类型的集合存储的元素时有序且可重复。
ArrayList
ArrayList底层采用一个可变长度的数组实现,可存储所有的元素,包括null。由于其实现了Serializable接口,所以其支持序列化。又由于其实现了RandomAccess接口,所以支持快速随机访问。ArrayList集合存储的元素按照存入的先后顺序排列元素,也就是说先进先出。
每个ArrayList实例都有一个容量,即用于存储元素的数组的大小,这个容量可随着不断添加新元素而自动增加,。但是增长算法并没有定义。当需要插入大量元素的时候,再插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
ArrayList岁支持快速随机访问,但向集合中插入与移除元素的操作速度很慢。
LinkedList
LinkList在的底层是一个链表结构,因此LinkList对于集合增删操作来说会更加占有优势,只需要对指针(注:在Java中没有指针的概念,这里是可以更形象地进行说明)操作即可。又因为链表的结构,LinkList可以被用作堆栈,队列或双向队列。
Vector
Vector同ArrayList一样底层是一个可变长度的数组。(下文有详细的介绍)
Stack
Stack集合继承于Vector类,Stack底层是一个堆栈结构,遵循后进先出(LIFO)原则,只能在一端执行插入操作(称为入栈)或删除操作(称为出栈)
peek()//查看栈顶元素 pop()//出栈,从栈顶移除一个元素 push(E Object)//入栈,将一个Object压入栈顶 search(E Object)//查询某个元素在栈中的位置
下图是常用Map集合简化的UML类图
Map接口
Map类型的集合以键值对的形式存储数据。不允许键的重复和从键到值的一对多映射。
HashMap
在HashMao内部通过一个哈希表来管理所有元素,同上文叙述的HashSet类似(其实应该说HashSet同HashMap类似),通过hashCode()方法和equals()方法来保证键的唯一性。也因此HashMap通过键的hashCode来快速的存取元素。
LinkedHashMap
LinkedHashMap继承于HashMap,在底层是以哈希表和链接列表相结合而实现的.该集合类型保留插入顺序,如果需要输入顺序与输出顺序相同,可以采用此集合。在LinkedHashMap内部有两种排序方式,一种是插入顺序(默认),一种是访问顺序(即近期访问最少到近期访问最多的顺序),这两种方式通过一个布尔值accessOrder进行切换,在LinkedHashMap的构造方法publicLinkedHashMap(intinitialCapacity,floatloadFactor,booleanaccessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder;} 中,如果accessOrder为true,表示按照访问顺序存储元素,如果为false表示按照插入顺序访问元素。
Hashtable
同样HashMap在底层是通过一个哈希表来实现的。
Properties
Properties继承于Hashtable,该类表示一个持久的属性值,可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。该类主要用于读取Java中的配置文件,在Java中,其配置文件常为.properties文件,格式为文本文件,文件的内容的格式是“键=值”的格式,文本注释信息可以用"#"来注释。了解Properties的详细用法,点我。
TreeMap
TreeMap实现了SortedMap接口,该集合根据键的自然顺序进行排序,或根据创建集合时传入的Comparator进行排序,具体取决于使用的构造方法。
各种集合的比较
1.同步性
以上介绍的集合中,除Vector、Stack、HashTable和Properties外,其他集合类型(HashSet、TreeSet、ArrayList、LinkedList、LinkedHashMap、TreeMap)都是非线程安全的。那么当多个线程同时访问这些非线程的集合类型时,我们必须自己实现访问同步,一种解决方案是在构造这些集合时构造一个同步集合,通过Collections类的静态方法synchronizedCollection() synchronizedList() synchronizedMap() synchronizedSet() synchronizedSortedMap() synchronizedSortedSet()
2.ArrayList与Vector的比较
同步性方面,如1所述,Vector不需要自己实现同步方案,而ArrayList需要自己实现同步方案。ArrayList和Vector都是使用可变长数组来保存数据,当数组空间不足时,两者就需要扩展其大小,ArrayList增加50%的大小,而Vector在默认情况下增长一倍的大小,但Vector可以自己设定这个增长幅度,在这方面Vector确实是有优势的,根据情况设定增长幅度,对程序的性能来说会更加友好。
3.LinkedList与ArrayList、Vector的比较
这三种集合类型都支持快速随机访问,在集合的首或末对元素进行更重操作,三者并没有什么的区别,但当从其他任意位置对元素进行操作,在性能上就会产生很大的差异。上文有叙述到LinkList是一个链表结构,在任意位置的对元素的操作都在一个常数级的时间,而Arrayl与Vecotr却颇为费时。
4.HashMap和Hashtable的区别
Hashtable与HashMap类似,但Hashtable不允许键与值为null,HashMap却允许。Hashtable是线程安全的,HashMap是县城非安全的。相比较下,HashMap比Hashtable效率要高下,所以要根据具体情况选择使用。。
5.TreeMap和HashMap的比较
针对于TreeMap,HashMap使用起来性能更好,因为在HashMap的构造方法中,可以自己调优初始容量和负载因子,这样我们就可以根据具体的开发环境去优化我们的HashMap。TreeMap可以自定义顺序遍历元素,这更是与使用在需要具体排序的情况下。但是TreeMap的性能不是很好,TreeMap的get操作的时间复杂度是O(log(n)),针对于HashMap的O(1)差的还是比较多的。因此为了中和两者,出现了LinkedHashMap。
6.各集合的性能比较
单线程模式下,测试元素在100~1000的情况下:
添加 HashMap效率最高,ArrayList最低,其他的效高的还有Stack、HashSet和Vector,较低的有LinkedList和TreeSet和TreeMap
删除 HashMap效率最高,LinkedList最低,其他的HashSet、TreeMap和TreeSet效率较高,较低的有Vector、ArrayList和Stack
查找 HashMap效率最高,LinkedList最低,HashXXX和TreeXXX效率都比较高,而基于List类效率耗时是Map或Set的十倍左右。
多线程模式下,测试元素100~1000,线程数10的情况下:
添加 HashSet效率最高,LinkedList最低,HashXXX和TreeXXX效率都比较高,这里ArrayList效率较低,整体相差不大。
删除 HashSet效率最高,LinkedList最低,整体性能同添加相似,但HashXXX或TreeXXX性能比List系列高出3倍。
查找 仍然是HashSet性能最好,LinkedList最低,性能较差的是ArrayList,其他的均表现很好。
——摘自Adroid中文网
请根据具体情况具体选择。
7.移动开发给出的建议
在谷歌官方推出的Android性能优化典范系列视频中提到,Android为移动操作系统特意编写了一些更加高效的容器,其中包括SparseArray和ArrayMap。
SparseArray采用二分法的方式存储数据,所以SparseArray内的元素时按照键从小到大排列的,SparseArray只接受int类型的键
put(int key, E value)//增 append(int key, E value)//增,其中的key是大于所有现有的键的数组,以便于提升性能 E get(int key)//查 E get(int key, E valueIfKeyNotFound)//查 delete(int key)//删 remove(int key)//删,实际上remove和delete是一样的,remove方法中调用了delete方法 setValueAt(int index, E value)//改 put(int key, E value)//改
ArrayMap,该类是在API19中出现的,单位了兼容低版本support.v4中也有ArrayMap类。ArrayMap在内部使用两个数组进行工作,其中一个数组记录key hash过后的顺序列表,另外一个数组按key的顺序记录Key-Value值。首先要清楚ArrayMap针对于移动设备的优化是在内存上进行的优化,也就是说使用这个容器比起HashMap会占用更少的内存,因为ArrayMap中的内存占用是连续不间断的,但随着数组中元素的增多,查找访问单个对象的花费也会跟着增长,同时ArrayMap的插入与删除的效率是不够高的,但如果你需要的是在一百这个数量级上时,则完全不用担心这些。
以上是自己对集合知识学习的总结,如果有不正确的地方,感谢指正,我也会及时修改。后面也会逐渐完善这方面的知识分享给大家。