经过之前List、Set、Collection、Iterable的介绍、AbstractList、AbstractSet、AbstractCollection的介绍两篇文章,我们终于进入了List、Set实现类的内容,相信这三期的内容你看完后,以后人家谁在问你List和Set的关系你都能跟他唠一唠。
1. UML
2. List接口的实现类
2.1 Vector
The Vector class implements a growable array of objects. Like an array, it contains components that can be accessed using an integer index. However, the size of a Vector can grow or shrink as needed to accommodate adding and removing items after the Vector has been created. Vector类实现可增长的对象数组,和数组一样,他包含可以使用整数索引访问的主键,不同的是Vector的大小可以根据需求增大或减小,以适应创建Vector之后添加和删除元素的需求
If a thread-safe implementation is not needed, it is recommended to use ArrayList in place of Vector 如果不需要线程安全的实现,建议使用ArrayList代替Vector
2.2 ArrayList
In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.) 除了实现List接口以外,此类还提供一些方法来操纵内部用于存储列表的数组大小。此类与Vector大致等效,但它不是同步的
The add operation runs in amortized constant time, that is, adding n elements requires O(n) time. All of the other operations run in linear time (roughly speaking). The constant factor is low compared to that for the LinkedList implementation. 添加元素以固定时间运行,也就是是说,添加n个元素需要O(n)时间。所有其他操作均以线性时间运算。与LinkedList实现相比,常数因子较低。
2.3 LinkedList
All of the operations perform as could be expected for a doubly-linked list. Operations that index into the list will traverse the list from the beginning or the end, whichever is closer to the specified index. 所有操作按双向链表的预设执行。索引到列表中的操作将从开头或结尾遍历列表,以更接近指定索引的位置为准
Note that this implementation is not synchronized. If multiple threads access a linked list concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. 注意此类未实现同步,如果多个线程同时访问链表,并且至少有一个线程在结构上修改了该链表,则必须在外部进行同步。
3. Set接口的实现类
3.1 TreeSet
The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used. 元素使用自然顺序进行排序,或通过在设置创建时提供的Comparator进行排序,具体取决于所使用的构造函数
This implementation provides guaranteed log(n) time cost for the basic operations (add, remove and contains). 添加、删除以及包含关系提供了保证log(n)的时间成本
Note that this implementation is not synchronized. If multiple threads access a tree set concurrently, and at least one of the threads modifies the set, it must be synchronized externally. 请注意,TreeSet未实现同步。如果多个线程同时访问TreeSet,并且至少有一个线程修改了TreeSet,则必须在外部对其进行同步。
3.2 HashSet
This class implements the Set interface, backed by a hash table (actually a HashMap instance). It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time. This class permits the null element. 此类实现Set接口,该接口由哈希表(HashMap)支持,他保证集合的迭代顺序,但不能保证顺序随着时间的推移保持恒定,此类允许使用null元素
This class offers constant time performance for the basic operations (add, remove, contains and size), assuming the hash function disperses the elements properly among the buckets. Iterating over this set requires time proportional to the sum of the HashSet instance's size (the number of elements) plus the "capacity" of the backing HashMap instance (the number of buckets). Thus, it's very important not to set the initial capacity too high (or the load factor too low) if iteration performance is important. 为添加、删除、包含关系以及大小关系操作提供了恒定的时间性能 遍历HashSet需要的时间与HashSet实例的大小加上后备HashSet实例的容量之和成比例关系(正比例关系)。因此,如果迭代性能很重要,则不要将初始容量设置的过高。
3.3 LinkedHashSet
Hash table and linked list implementation of the Set interface, with predictable iteration order. This implementation differs from HashSet in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is the order in which elements were inserted into the set (insertion-order). Set接口的哈希表、链表的综合实现,具有可预测的迭代顺序。与HashSet不同之处在于拥有双向链表的特性。LinkedHashSet定义了迭代顺序,即。将元素插入到集合中的顺序
Like HashSet, it provides constant-time performance for the basic operations (add, contains and remove), assuming the hash function disperses elements properly among the buckets. Performance is likely to be just slightly below that of HashSet, due to the added expense of maintaining the linked list, with one exception: Iteration over a LinkedHashSet requires time proportional to the size of the set, regardless of its capacity. 像HashSet一样,LinkedHashSet为添加、包含和删除提供了恒定时间的性能,但由于维护链表需要增加系统开销,因此整体性能上还是会略低于HashSet,但有一点例外:在LinkedHashSet上进行迭代需要的时间与集合的大小成正比,无论容量如何。
Iteration over a HashSet is likely to be more expensive, requiring time proportional to its capacity.A linked hash set has two parameters that affect its performance: initial capacity and load factor. They are defined precisely as for HashSet. Note, however, that the penalty for choosing an excessively high value for initial capacity is less severe for this class than for HashSet, as iteration times for this class are unaffected by capacity. HashSet上的迭代耗费资源可能较高,需要的时间与其容量成正比,LinkedHashSet有两个影响性能的参数:初始容量和负载因子。他们的定义与HashSet一样,但是LinkedHashSet的初始容量过高相比于HashSet来说影响会小很多,因此LinkedHashSet在迭代时不受容量的影响
4. 关系对比
本小节的内容是我根据各实现类的源码、注释以及网上的资料整理出来的,可能理解上的偏差或者是遗漏,欢迎指正。
4.1 ArrayList、LinkedList和Vector
它们都实现了List接口,因此用法非常相似。主要区别在于它们的实现,这直接影响到使用场景和性能的不同。
-
存储结构
- ArrayList和Vector是基于数组实现的。随着将更多元素添加到ArrayList或Vector中,其大小将动态增加。
- LinkedList是基于双向链表实现的。
-
线程安全
- ArrayList和LinkedList不具备线程安全机制,需要手动显式添加。
- Vector实现了线程安全。
-
扩容机制
- ArrayList和Vector都是基于数组形式实现的,因此也基于数组来存储,所以当向这两种类型中增加元素的时候,若容量不够,需要进行扩容。ArrayList扩容后的容量是之前的1.5倍,然后把之前的数据拷贝到新建的数组中去。而Vector现在基于扩容因子,即每次扩容多少,扩容因子默认为0,若不设置扩容因子,则默认扩容容量就是翻倍。
- Vector可以设置容量增量(capacityIncrement参数),而ArrayList不可以。
- LinkedList基于双向链表实现,存储空间是动态分配的。
-
基本方法
- ArrayList、LinkedList和Vector都实现了List接口,所以有同名的增删改查等基本方法
- LinkedList还实现了Queue接口,该接口添加了比ArrayList和Vector更多的方法,例如offer(),peek(),poll()等。
科普:这里给大家科普一下数据结构的知识,为什么基于数组查得快改的慢,基于链表却改的快查的慢。
- 数组在内存中是一块连续的地址,你查第几个就访问这一块内存的第几个地址,所以查询快,但是你要插入、删除的时候,你插入、修改后,就占了某个地址,那这个地址以后的地址就要往后移动或者往前移动,所以改的慢
- 链表的存储是随便那个内存地址都可以,我当前元素会自带下一个元素的内存地址,这样就形成了一条链,所以你插入、删除的时候,我把他删了,然后更新上一个节点存储的下一个节点的地址,就可以了,所以快,但是查询的话,因为内存地址不连续,你就要基于这个链一个一个的往下找。
- 双向链表还是链表,只不过每个节点既存了下一个节点的内存地址,又存了上一个节点的内存地址
- 下图是实例,大概就这个意思,想深究学习的自己百度一下数据结构的数组的链表,又更专业的博主为你们讲解
4.2 HashSet、TreeSet和LinkedHashSet
它们都实现了Set接口,因此用法也相似,而且它们都不允许有重复的元素。主要区别在于它们的实现,这直接影响到使用场景和性能的不同。
-
存储结构
- HashSet基于哈希表实现的.即里面每个对象都有一个哈希码值,然后根据每个对象的哈希码值(调用hashCode()获得),用固定的算法算出它的存储索引,把对象存放在一个叫散列表的相应位置上。
- TreeSet基于二叉树实现的.对象之间有个对比关系,这个比较关系分为三种:优于、劣于、相等,其中相等就是他本身,那么基于优于和劣于就可以组成一棵二叉树,其中这个比较关系一般分为自然排序和自定义排序。
- LinkedHashSet是在哈希表的基础上引入双向链表实现的。即里面每个对象都有一个哈希码值,然后根据每个对象的哈希码值(调用hashCode()获得),用固定的算法算出它的存储索引,把对象存放在一个叫散列表的相应位置上,然后再把所有的节点以双向链连接起来(符合插入顺序)。
- 这里可以看出HashSet、TreeSet和LinkedHashSet是分别基于HashMap、TreeMap和LinkedHashMap来实现的,而Map都是键值对,而Set的实现只取了键,而值是一个空的Object对象。
-
线程安全
- HashSet、TreeSet、LinkedHashSet都不具备线程安全机制,需要手动显式添加。
-
扩容机制
- HashSet扩容基于HashMap
- TreeSet扩容基于TreeMap
- LinkHashSet扩容基于LinkHashMap
- 是的,我现在只能说的上这个... 我看了源码,看了一早上也没看出个所以然,哎,再等等,等我写到HashMap、TreeMap、LinkedHashMap的区别也许我就理解他的扩容机制了。
-
基本方法
- 首先三个类都继承自AbstractSet类,且实现了Set接口,所以有同名的增删改查方法。
科普:同样介绍一下HashSet、TreeSet、LinkedHashSet的数据结构(同理于HashMap、TreeMap、LinkedHashMap),直接上图
5. 小结
好了,到这里List、Set的入门系列就介绍完了,很多东西都是描述性的,基本符合初级程序员的需求了,想更进一步达到中级程序员我认为还得深入理解数据结构,并且懂得各种使用场景。