简介
JDK1.2 引入了 Java 集合框架,包含一组数据结构。所有这些数据结构在 java.util 包里,包含了 Collection、List、Set、Map、SortedMap 接口。这些接口的实现类有 LinkedList、TreeSet、ArrayList、HashMap 等。
Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。
集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:
- **接口:**是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
- **实现(类):**是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
- **算法:**是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
常用集合接口
Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。Collection 接口存储一组***不唯一***,***无序***的对象。
List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。List 接口存储一组***不唯一***,有序(插入顺序)的对象。
Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。Set 接口存储一组***唯一***,***无序***的对象。
Map 接口存储一组***键值对象***,提供key(键)到value(值)的映射。
常用集合实现类
LinkedList
该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。例如:
Listlist=Collections.synchronizedList(newLinkedList(...));
LinkedList 查找效率低。
ArrayList 该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低。
Vector
该类和ArrayList非常相似,但是该类是同步的,可以用在多线程的情况,该类允许设置默认的增长长度,默认扩容方式为原来的2倍。
HashSet
该类实现了Set接口,不允许出现重复元素,不保证集合中元素的顺序,允许包含值为null的元素,但最多只能一个。
HashMap
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 该类实现了Map接口,根据键的HashCode值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步。
Set和List的区别
- Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。
- Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。
- List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。
注意
在Java中任何集合中存放的都是对象,即引用数据类型,基本数据类型不能放到集合中。将基本数据类型放到集合中,这个过程中发生了自动装箱,从集合中取出了基本数据类型,因为这个过程发生了自动拆箱。装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型
List接口
List是接口不能实例化,可以使用其实现类来实例List类型集合变量。例:
List list = new ArrayList();
实现类
ArrayList
ArrayList是基于动态数组数据结构的实现,访问元素速度优于LinkedList。
以数组实现。节约空间,但数组有容量限制。超出限制时会增加 50% 容量,用System.arraycopy()复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为 10 的数组。扩容增量:原容量的 0.5倍 + 1。
LinkedList
LinkedList是基于链表数据结构的实现,在批量插入或删除数据时优于ArrayList,但占用的内存空间比较大。
以双向链表实现。链表无容量限制,但双向链表本身使用了更多空间,也需要额外的链表指针操作。
Vector
Vector类实现了一个动态数组。初始容量为 10 ,加载因子为 1,扩容增量:原容量的 1倍 。和 ArrayList 很相似,但是两者是不同的:
- Vector 是同步访问的。
- Vector 包含了许多传统的方法,这些方法不属于集合框架。
Vector 类支持 4 种构造方法。
Vector() //创建一个默认的向量,默认大小为 10
Vector(int size) //创建指定大小的向量
Vector(int size,int incr) //创建指定大小的向量,并且增量(表示向量每次增加的元素数目)用incr指定。
Vector(Collection c) //创建一个包含集合 c 元素的向量
常用方法
List接口继承自Collection接口,List接口中的很多方法都继承自Collection接口的。List接口中常用方法如下。
1.操作元素
get(int index):返回List集合中指定位置的元素。set(int index, Object element):用指定元素替换List集合中指定位置的元素。add(Object element):在List集合的尾部添加指定的元素。该方法是从Collection集合继承过来的。add(int index, Object element):在List集合的指定位置插入指定元素。remove(int index):移除List集合中指定位置的元素。remove(Object element):如果List集合中存在指定元素,则从List集合中移除第一次出现的指定元素。该方法是从Collection集合继承过来的。clear():从List集合中移除所有元素。该方法是从Collection集合继承过来的。
2.判断元素
isEmpty():判断List集合中是否有元素,没有返回true,有返回false。该方法是从Collection继承过来的。contains(Object element):判断List集合中是否包含指定元素,包含返回true,不包含返回false。该方法是从Collection集合继承过来的。
3.查询元素
indexOf(Object o):从前往后查找List集合元素,返回第一次出现指定元素的索引,如果此列表不包含该元素,则返回-1。lastIndexOf(Object o):从后往前查找List集合元素,返回第一次出现指定元素的索引,如果此列表不包含该元素,则返回-1。
4.其它
iterator():返回迭代器(Iterator)对象,迭代器对象用于遍历集合。该方法是从Collection继承过来的。size():返回List集合中的元素数,返回值是int类型。该方法是从Collection集合继承过来的。subList(int fromIndex, int toIndex):返回List集合中指定的 fromIndex(包括 )和 toIndex(不包括)之间的元素集合,返回值为List集合。
遍历集合
集合最常用的操作之一是遍历,遍历就是将集合中的每一个元素取出来,进行操作或计算。List集合遍历有三种方法:
- 使用for循环遍历:List集合可以使用for循环进行遍历,for循环中有循环变量,通过循环变量可以访问List集合中的元素。
- 使用for-each循环遍历:for-each循环是针对遍历各种类型集合而推出的,笔者推荐使用这种遍历方法。
- 使用迭代器遍历:Java提供了多种迭代器,List集合可以使用Iterator和ListIterator迭代器。
// 使用迭代器遍历
Iterator it = list.iterator();
while (it.hasNext()) {
Object item = it.next();
String s = (String) item;
System.out.println("读取集合元素: " + s);
}
注:使用for-each循环遍历和使用迭代器遍历从集合中取出的元素都是Object类型。
Set接口
Set是接口不能实例化,可以使用其实现类来实例List类型集合变量。例:
Set set = new HashSet();
Set集合是由一串无序的,不能重复的相同类型元素构成的集合。
当不考虑顺序,且没有重复元素时,Set集合和List集合可以互相替换的。
Set几乎都是内部用一个Map来实现, 因为Map里的KeySet就是一个Set,而value是假值,全部使用同一个Object。Set的特征也继承了那些内部Map实现的特征。
实现类
HashSet
继承Set接口,储存的是无序,唯一的对象。HashSet使用equals来进行对象对比,确定数据是唯一的,如果两个数据的对象是一致的,那么HashSet将会把这两个合并,只储存一个空间。内部是HashMap来实现。初始容量为 16,加载因子为 0.75 ,扩容增量:原容量的 1倍。
TreeSet
TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。它继承于AbstractSet抽象类,实现了NavigableSet, Cloneable, java.io.Serializable接口。 TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。 TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。 TreeSet 实现了Cloneable接口,意味着它能被克隆。 TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。
TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。 TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。 另外,TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。
常用方法
Set接口也继承自Collection接口,Set接口中大部分都是继承自Collection接口,这些方法如下。
1.操作元素
add(Object element):在Set集合的尾部添加指定的元素。该方法是从Collection集合继承过来的。remove(Object element):如果Set集合中存在指定元素,该方法是从Collection集合继承过来的。clear():从Set集合中移除所有元素。该方法是从Collection集合继承过来的。
2.判断元素
isEmpty():判断Set集合中是否有元素,没有返回true,有返回false。该方法是从Collection集合继承过来的contains(Object element):判断Set集合中是否包含指定元素,包含返回true,不包含返回false。该方法是从Collection集合继承过来的。
3.其他
iterator():返回迭代器(Iterator)对象,迭代器对象用于遍历集合。该方法是从Collection集合继承过来的。size():返回Set集合中的元素数,返回值是int类型。该方法是从Collection集合继承过来的。
遍历集合
Set集合中的元素由于没有序号,所以不能使用for循环进行遍历,但可以使用for-each循环和迭代器进行遍历。事实上这两种遍历方法也是继承自Collection集合,也就是说所有的Collection集合类型都有这两种遍历方式。
Map接口
Map(映射)集合表示一种非常复杂的集合,允许按照某个键来访问元素。Map集合是由两个集合构成的,一个是键(key)集合,一个是值(value)集合。键集合是Set类型,因此不能有重复的元素。而值集合是Collection类型,可以有重复的元素。Map集合中的键和值是成对出现的。
Map是接口不能实例化,可以使用其实现类实例化Map类型集合变量,例:
Map map = new HashMap();
实现类
HashMap
以Entry<K,V>数组实现的哈希桶数组,用Key的哈希值取模桶数组的大小可得到数组下标。
插入元素时,如果两条Key落在同一个桶((不同的key对应相同的下标)),Entry用一个next属性实现多个Entry以单向链表存放,后入桶的Entry将next指向桶当前的Entry。
查找哈希值为17的key时,先定位到第一个哈希桶,然后以链表遍历桶里所有元素,逐个比较其key值。
在JDK8里,新增默认为8的閥值,当一个桶里的Entry超过閥值,就不以单向链表而以红黑树来存放以加快Key的查找速度。
HashTable
和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。
Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
Hashtable 的函数都是同步的,所有的读写等操作都进行了锁(synchronized)保护,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。
Hashtable 的实例有两个参数影响其性能:初始容量 和 加载因子。容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。 通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。
Hashtable定义了四个构造方法。第一个是:
Hashtable() //默认构造方法
Hashtable(int size) //创建指定大小的哈希表
Hashtable(int size,float fillRatio) //创建了一个指定大小的哈希表,并且通过fillRatio指定填充比例,填充比例必须介于0.0和1.0之间,它决定了哈希表在重新调整大小之前的充满程度
Hashtable(Map m) //创建了一个以M中元素为初始化元素的哈希表。哈希表的容量被设置为M的两倍。
TreeMap
TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。 TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。 TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。 TreeMap 实现了Cloneable接口,意味着它能被克隆。 TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。
TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。 TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。 另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。
常用方法
Map集合中包含两个集合(键和值),所以操作起来比较麻烦,Map接口提供很多方法用来管理和操作集合。主要的方法如下。
1.操作元素
get(Object key):返回指定键所对应的值;如果Map集合中不包含该键值对,则返回null。put(Object key, Object value):指定键值对添加到集合中。remove(Object key):移除键值对。clear():移除Map集合中所有键值对。
2.判断元素
isEmpty():判断Map集合中是否有键值对,没有返回true,有返回false。containsKey(Object key):判断键集合中是否包含指定元素,包含返回true,不包含返回false。containsValue(Object value):判断值集合中是否包含指定元素,包含返回true,不包含返回false。
3.查看集合
keySet():返回Map中的所有键集合,返回值是Set类型。values():返回Map中的所有值集合,返回值是Collection类型。size():返回Map集合中键值对数。
遍历集合
Map集合遍历与List和Set集合不同,Map有两个集合,因此遍历时可以只遍历值的集合,也可以只遍历键的集合,也可以同时遍历。这些遍历过程都可以使用for-each循环和迭代器进行遍历。
推荐:Java 集合系列