一文总结Java集合框架内容
思维导图
话不多说,先上图,形成一个大致的逻辑。
同时也给大家列出Map中的接口及实现,对二者的联系有个印象。
01集合简介
在Java类库中,集合类的基本接口是Collection接口。
和其他面向对象的语言的数据结构类库的一样,Java集合类库也将接口(interface)与实现(implementation)分离。
实现了Collection接口的操作类,代表着这个类中可以按照某种逻辑结构和物理结构,“线性关联”的存储着一组元素的集合。这种线性关联的逻辑结构可能是链表(例如:LinkedList),也可能是固定长度的数组(例如:Vector);可能向外界的输出的结果是有序的(例如:ArrayList),也可能是无序的(例如:HashSet);可能是保证了多线程下的操作安全性的(例如:CopyOnWriteArrayList),也可能是不保证多线程下的操作安全性的(例如:ArrayDeque);
这个接口的基本方法:
public interface Collection extends Iterable { //基本查询操作 int size(); boolean isEmpty(); boolean contains(Object o); 返回此集合中元素的迭代器。没有关于元素返回顺序的保证(除非这个集合是提供保证的某个类的实例)。 Iterator iterator(); 返回一个包含此集合中所有元素的数组。如果此集合对其迭代器返回其元素的顺序做出任何保证,则此方法必须以相同的顺序返回元素。 返回的数组将是“安全的”,因为此集合不维护对它的引用。(换句话说,即使此集合由数组支持,此方法也必须分配一个新数组)。因此调用者可以自由修改返回的数组。 此方法充当基于数组和基于集合的 API 之间的桥梁。 Object[] toArray(); //修改操作 boolean add(E e); boolean remove(Object o); //批量操作 boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); default boolean removeIf(Predicate<? super E> filter) { Objects.requireNonNull(filter); boolean removed = false; final Iterator each = iterator(); while (each.hasNext()) { if (filter.test(each.next())) { each.remove(); removed = true; } } return removed; } boolean retainAll(Collection<?> c); void clear(); //比较和散列 比较指定的对象与此集合是否相等。 boolean equals(Object o); 返回此集合的哈希码值 int hashCode(); 在此集合中的元素上创建一个Spliterator 。 实现应该记录拆分器报告的特征值。 如果拆分器报告Spliterator.SIZED并且此集合不包含元素, 则不需要报告此类特征值。 默认实现应该被可以返回更有效的拆分器的子类覆盖。 为了保留stream()和parallelStream() } 方法的预期惰性行为, 拆分器应该具有IMMUTABLE或CONCURRENT的特性,或者是后期绑定。 default Spliterator spliterator() { return Spliterators.spliterator(this, 0); } 返回一个以此集合作为源的顺序Stream 。 default Stream stream() { return StreamSupport.stream(spliterator(), false); } 返回一个可能的并行Stream ,以此集合作为其源。 允许此方法返回顺序流。 default Stream parallelStream() { return StreamSupport.stream(spliterator(), true); } }
02****迭代器Iterator
作为集合的重要组成部分,Iterator接口包含4个方法:
-
boolean hasNext*()*;
-
E next*()*;
-
default void remove*();*
-
default void forEachRemaining*(Consumer<? super E> action) {* Objects.requireNonNull*(action);
while (hasNext()) action.accept(next())*;
}
通过反复调用next方法,可以逐个访问集合中的每个元素。但是,如果到达了集合的末尾,next方法将抛出一个NoSuchElementException。因此,需要在调用next之前调用hasNext方法。如果迭代器对象还有多个供访问的元素,这个方法就返回true。对于新手小白想更轻松的学好Java提升,Java架构,web开发、大数据,数据分析,人工智能等技术,这里给大家分享系统教学资源,扩列下我尉(同英):CGMX9880 【教程/工具/方法/解疑】
调用forEachRemaining方法并提供一个lambda表达式(它会处理一个元素)。将对迭代器的每一个元素调用这个lambda表达式,直到再没有元素为止。
Iterator接口的remove方法将会删除上次调用next方法时返回的元素。
03集合的具体实现#List
List是一个有序集合(ordered collection)。元素会增加到容器中的特定位置。可以采用两种方式访问元素:使用迭代器访问,或者使用一个整数索引来访问。后一种方法称为随机访问(randomaccess),因为这样可以按任意顺序访问元素。与之不同,使用迭代器访问时,必须顺序地访问元素。
用的最多的就是LinkedList 和 ArrayList了
实现类:
-
ArrayList:数组实现,查询快,增删慢,轻量级;(线程不安全)
-
LinkedList:双向链表实现,增删快,查询慢 (线程不安全)
-
Vector:数组实现,重量级 (线程安全、使用少)
ArrayList
底层是Object数组,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点。
而在LinkedList的底层是一种双向循环链表。在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。
双向循环链表的查询效率低但是增删效率高。
ArrayList和LinkedList在用法上没有区别,但是在功能上还是有区别的。
ArrayList自动扩充机制
实现机制:ArrayList.ensureCapacity(int minCapacity)
首先得到当前elementData 属性的长度oldCapacity。
然后通过判断oldCapacity和minCapacity参数谁大来决定是否需要扩容, 如果minCapacity大于 oldCapacity,那么我们就对当前的List对象进行扩容。扩容的的策略为:取(oldCapacity * 3)/2 + 1和minCapacity之间更大的那个。然后使用数组拷 贝的方法,把以前存放的数据转移到新的数组对象中 如果minCapacity不大于oldCapacity那么就不进行扩容。
LinkedList
LinkedList是采用双向循环链表实现的。
利用LinkedList实现栈(stack)、队列(queue)、双向队列(double-ended queue )。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()等。
经常用在增删操作较多而查询操作很少的情况下:
队列和堆栈。
队列:先进先出的数据结构。
栈:后进先出的数据结构。
注意:使用栈的时候一定不能提供方法让不是最后一个元素的元素获得出栈的机会。
用LinkedList实现队列:
队列(Queue)是限定所有的插入只能在表的一端进行,而所有的删除都在表的另一端进行的线性表。
表中允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front)。
队列的操作是按先进先出(FIFO)的原则进行的。
队列的物理存储可以用顺序存储结构,也可以用链式存储结构。
用LinkedList实现栈:
栈(Stack)也是一种特殊的线性表,是一种后进先出(LIFO)的结构。
栈是限定仅在表尾进行插入和删除运算的线性表,表尾称为栈顶(top),表头称为栈底(bottom)。
栈的物理存储可以用顺序存储结构,也可以用链式存储结构。
Vector
(与ArrayList相似,区别是Vector是重量级的组件,使用使消耗的资源比较多。)
结论:在考虑并发的情况下用Vector(保证线程的安全)。
在不考虑并发的情况下用ArrayList(不能保证线程的安全)。
List的常用方法如下:
·ListIteratorlistIterator()
返回一个列表迭代器,以便用来访问列表中的元素。·ListIteratorlistIterator(int index)
返回一个列表迭代器,以便用来访问列表中的元素,这个元素是第一次调用next返回的给定索引的元素。
·void add(int i,E element)
在给定位置添加一个元素。
·void addAll(int i,Collection<?extends E>elements)
将某个集合中的所有元素添加到给定位置。
·E remove(int i)
删除给定位置的元素并返回这个元素。
·E get(int i)
获取给定位置的元素。
·E set(int i,E element)
用新元素取代给定位置的元素,并返回原来那个元素。
·int indexOf(Object element)
返回与指定元素相等的元素在列表中第一次出现的位置,如果没有这样的元素将返回–1。
·int lastIndexOf(Object element)
返回与指定元素相等的元素在列表中最后一次出现的位置,如果没有这样的元素将返回–1。
#Set
Set接口等同于Collection接口,不过其方法的行为有更严谨的定义。集(set)的add方法不允许增加重复的元素。要适当地定义集的equals方法:只要两个集包含同样的元素就认为是相等的,而不要求这些元素有同样的顺序。hashCode方法的定义要保证包含相同元素的两个集会得到相同的散列码。
无序集合,不允许存放重复的元素;允许使用null元素
对 add()、equals() 和 hashCode() 方法添加了限制
HashSet和TreeSet是Set的实现
实现类 :
HashSet:equals返回true,hashCode返回相同的整数;哈希表;存储的数据是无序的。
LinkedHashSet:此实现与HashSet的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。存储的数据是有序的。
HashSet
HashSet类直接实现了Set接口,其底层其实是包装了一个HashMap去实现的。HashSet采用HashCode算法来存取集合中的元素,因此具有比较好的读取和查找性能。
HashSet的特征:
-
不仅不能保证元素插入的顺序,而且在元素在以后的顺序中也可能变化(这是由HashSet按HashCode存储对象(元素)决定的,对象变化则可能导致HashCode变化)
-
HashSet是线程非安全的
-
HashSet元素值可以为NULL
HashSet常用方法:
-
public boolean contains(Object o) :如果set包含指定元素,返回true
-
public Iterator iterator()返回set中元素的迭代器
-
public Object[] toArray() :返回包含set中所有元素的数组public Object[] toArray(Object[] a) :返回包含set中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型
-
public boolean add(Object o) :如果set中不存在指定元素,则向set加入
-
public boolean remove(Object o) :如果set中存在指定元素,则从set中删除
-
public boolean removeAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素
-
public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一个set,只有是当前set的子集时,方法返回true
实现Set接口的HashSet,依靠HashMap来实现的。我们应该为要存放到散列表的各个对象定义hashCode()和equals()。
HashSet的equals和HashCode:
前面说过,Set集合是不允许重复元素的,否则将会引发各种奇怪的问题。那么HashSet如何判断元素重复呢?
HashSet需要同时通过equals和HashCode来判断两个元素是否相等,具体规则是,如果两个元素通过equals为true,并且两个元素的hashCode相等,则这两个元素相等(即重复)。
所以如果要重写保存在HashSet中的对象的equals方法,也要重写hashCode方法,重写前后hashCode返回的结果相等(即保证保存在同一个位置)。所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
试想如果重写了equals方法但不重写hashCode方法,即相同equals结果的两个对象将会被HashSet当作两个元素保存起来,这与我们设计HashSet的初衷不符(元素不重复)。
另外如果两个元素哈市Code相等但equals结果不为true,HashSet会将这两个元素保存在同一个位置,并将超过一个的元素以链表方式保存,这将影响HashSet的效率。
如果重写了equals方法但没有重写hashCode方法,则HashSet可能无法正常工作。
LinkedHashSet
LinkedHashSet是HashSet的一个子类,LinkedHashSet也根据HashCode的值来决定元素的存储位置,但同时它还用一个链表来维护元素的插入顺序,插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。对于新手小白想更轻松的学好Java提升,Java架构,web开发、大数据,数据分析,人工智能等技术,这里给大家分享系统教学资源,扩列下我尉(同英):CGMX9880 【教程/工具/方法/解疑】
TreeSet
TreeSet实现了SortedSet接口,顾名思义这是一种排序的Set集合,查看jdk源码发现底层是用TreeMap实现的,本质上是一个红黑树原理。正因为它是排序了的,所以相对HashSet来说,TreeSet提供了一些额外的按排序位置访问元素的方法,例如first(), last(), lower(), higher(), subSet(), headSet(), tailSet().
TreeSet的排序分两种类型,一种是自然排序,另一种是定制排序。
TreeSet是依靠TreeMap来实现的。
TreeSet是一个有序集合,TreeSet中元素将按照升序排列,缺省是按照自然顺序进行排列,意味着TreeSet中元素要实现Comparable接口。
我们可以在构造TreeSet对象时,传递实现了Comparator接口的比较器对象。
Comparable和Comparator
Comparable 接口以提供自然排序顺序。
对于那些没有自然顺序的类、或者当您想要一个不同于自然顺序的顺序时,您可以实现Comparator 接口来定义您自己的排序函数。可以将Comparator传递给Collections.sort或Arrays.sort。
Comparator接口
当一个类并未实现Comparable,或者不喜欢缺省的Comaparable行为。可以实现Comparator接口
直接实现Comparator的compare接口完成自定义比较类。
例:Arrays.sort(results, new Comparator() 数组排序 RepDataQueryExecutor
例:Collections.sort(lst,new Comparator()
#Queue
queue是一种先进先出的数据结构,也就是first in first out。队列可以让人们有效地在尾部添加一个元素,在头部删除一个元素。有两个端头的队列,即双端队列,可以让人们有效地在头部和尾部同时添加或删除元素。不支持在队列中间添加元素。在Java SE 6中引入了Deque接口,并由ArrayDeque和LinkedList类实现。这两个类都提供了双端队列,而且在必要时可以增加队列的长度。
PriorityQueue
优先级队列(priority queue)中的元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索。也就是说,无论何时调用remove方法,总会获得当前优先级队列中最小的元素。然而,优先级队列并没有对所有的元素进行排序。如果用迭代的方式处理这些元素,并不需要对它们进行排序。优先级队列使用了一个优雅且高效的数据结构,称为堆(heap)。堆是一个可以自我调整的二叉树,对树执行添加(add)和删除(remore)操作,可以让最小的元素移动到根,而不必花费时间对元素进行排序。与TreeSet一样,一个优先级队列既可以保存实现了Comparable接口的类对象,也可以保存在构造器中提供的Comparator对象。
04 集合总结及应用
针对不同的实现,可以将Java集合从不同维度进行比较。
最后,在实际的开发实践中,我们到底需要使用那种数据结构呢?
针对下图进行参考选择。