前言
最近在看集合相关的东西,看完总是不知所云,至此仍是云里雾里。看来有必要了解集合的底层实现,方能解心中之惑。遂建立一个集合专题,记录所思所学。
什么是集合
Java有多种方式保存对象(或者说对象的引用),例如数组。如果想保存一组对象,可以用数组;如果想保存一组基本类型的数据,更应该用数组。数组虽好,可也不是万能的。因为数组在初始化时要指定一个固定长度,这就很不好了。为什么呢?因为有些时候,你不知道有多少个对象,如果数组长度定得太小,则会报错。定得太大则造成浪费(虽然大部分人都没有对象)。因此数组尺寸固定这一限制显得在存储对象时过于受限了。
因此java类库提供了一套相当完整的容器来解决这个问题。其中基本的类型是List 、Set 、Queue 、Map,这些类型一般被称为集合类。通常使用范围更广的术语“容器”称呼他们。
java容器类能够自动调整自己的尺寸(自动扩容机制)。与数组不同的是,可以放任意数量的对象到容器里而不用担心容器要设置多大。
基本概念
在 Java 类库中,集合类的有两个基本接口: Collection 接口、Map接口。其中Collection 接口又有3个子接口List 接口、Set接口 、Queue接口。
为什么要这么分?
Collection接口主要储存的是一个独立元素的序列,这些元素都服从一条或者多条规则。
Map接口主要储存的是一组键值对对象,允许你通过使用键(key)来查找值(value)。
放一张集合框架的简化图:
Collection接口
我们先看看Collection接口源码:
1 public interface Collection<E> extends Iterable<E> { 2 //... 3 }
可见Collection接口继承了Iterable接口。Iterable接口是提供了返回一个迭代器对象的方法和统一遍历集合元素的方法,迭代器的作用就是用来遍历集合中对象。关于Iterable接口和Iterator接口这里就不多介绍了。
关于方法:
再看一下collection接口所定义的方法:
这里介绍其中一些的方法及API文档的介绍,后面就只介绍改变的部分
-
Iterator < E> iterator()返回一个用于访问集合中每个元素的迭代器。• int size()返回当前存储在集合中的元素个数。• boolean isEmpty()如果集合中没有元素, 返回 true。• boolean contains(Object obj)如果集合中包含了一个与 obj 相等的对象, 返回 true。• boolean containsAl 1(Collection<?> other)如果这个集合包含 other 集合中的所有元素, 返回 trueo• boolean add(Object element)将一个元素添加到集合中。如果由于这个调用改变了集合,返回 true。• boolean addAl 1(Col 1 ection<? extends E> other)将 other 集合中的所有元素添加到这个集合。如果由于这个调用改变了集合,返回 true。• boolean remove(Object obj)从这个集合中删除等于 obj 的对象。如果有匹配的对象被删除, 返回 true。• boolean removeAl 1(Col 1 ection<?> other)从这个集合中删除 other 集合中存在的所有元素。如果由于这个调用改变了集合,返回 true。• default boolean removelf(Predicate<? super E> filter)8从这个集合删除 filter 返回 true 的所有元素。如果由于这个调用改变了集合, 则返回 true。• void clear()从这个集合中删除所有的元素。• boolean retainAl 1(Collection<?> other)从这个集合中删除所有与 other 集合中的元素不同的元素。如果由于这个调用改变了集合, 返回 true。•Object[]toArray()返回这个集合的对象数组。
总结:
可见,Collection接口作为java集合的基本接口之一,声明了适用于JAVA集合(只包括Set和List)的通用方法。
Set接口
Set接口是Collection接口的子接口
1 public interface Set<E> extends Collection<E> { 2 //... 3 }
关于定义:
我们来看Set的注解:
为什么说Set是不包含重复元素的集合?我们来看Set接口中add方法的注解:
所以Set方法是重新定义了包括add方法在内的部分方法,使得在添加元素时,已包含的元素不能在添加进集合中,从而实现了Set集合 ①不包含重复元素。
我们再来看Iterater()方法的注解:
我们可以得知,当使用迭代器迭代Set集合中的元素时,是②对返回顺序不做保证的,除非具体的实现类提供保证。那么什么是对返回顺序不做保证?
首先顺序有两个概念,一是按添加的顺序排列,二是按自然顺序a-z排列。不做保证是指集合元素可能是乱序的,可能是有序的(按添加顺序或者自然顺序),还是具体有序无序还是要看具体实现类。
你比如HashSet就是输出时两个顺序都不能保证,LinkedHashSet保证输出添加顺序,TreeSet保证输出自然顺序。具体为什么待整理到相关内容再具体介绍。
关于方法:
我们可以看到,Set接口没有新增任何的方法,只是重新定义了Collection的部分方法(多态性)。
总结:
所以,对于Set集合我们可以得知它的两个特性①不包含重复元素。②对返回顺序不做保证的。
List接口
List接口是Collection接口的子接口,除了继承 Collection 的一些方法,还新增了许多方法
1 public interface List<E> extends Collection<E> { 2 //... 3 }
关于定义:
那么List集合有什么特点呢?
我们可知,List集合被定义为一个①有序集合,同时能够对某个位置的元素进行操作。
List集合第二个特点是②允许有重复元素
关于方法:
对应List集合的方法:
我们可以看到,List接口新增了以下操作:
位置相关:add(int ,E),get(int ,E),remove(int),set(int ,E),为增查删改提供了具体对某个位置操作的方法
搜索:indexOf(Object)、lastIndexOf(Object),从List集合中查找某个对象的位置
迭代器:listIterator()、listIterator(int),listIterator迭代器比Iterator更强大,支持向前迭代,迭代时修改等等。
范围操作:subList(),返回List中一个片段
总结:
可见,对于List集合我们知道它是①有序集合并且②允许有重复元素
Queue接口
java中的队列接口Queue是一个被设计用来按一定的优先级处理元素的集合,除了拥有基本的集合操作和数据结构队列的特性外它还添加了额外的元素插入,获取,检查操作。所有的这些额外添加的操作都有一个共性,如果操作失败,一种情况是:抛出异常;一种情况是:返回特殊的值(根据情况可能是null或false)。返回特殊值这种方式是针对有容量队列的插入操作。在大多数队列的实现中插入操作是不能失败的。
1 public interface Queue<E> extends Collection<E> { 2 //.... 3 }
关于方法:
我们可以看到Queue接口里并未新增或者重写多少方法。
- boolean add(E e);
add 方法将指定的元素添加入队列,如果队列是有界的且没有空闲空间则抛出异常。
- boolean offer(E e);
offer 方法将指定的元素添加入队列,如果队列是有界的且没有空闲空间则返回false。在使用有界队列时推荐使用该方法来替代add方法。
- E remove();
remove删除并返回队首的元素。如果队列为空则会抛异常。
- E poll();
poll删除并返回队首的元素。如果队列为空返回null。
- E element();
element返回队首元素但是不删除。如果队列为空会抛出异常。
- E peek();
element返回队首元素但是不删除。如果队列为空则返回null。
总结:
在JDK里面没有一个队列的实现是仅仅实现Queue接口定义的功能。可能是因为具有基本功能的队列实现比较简单而且实际的用途有点少。队列的实现基本可以分为:1.并发队列(ConcurrentLinkedQueue); 2.阻塞队列(ArrayBlockingQueue, LinkedBlockingQueue); 3.双端队列(Deque, ArrayDeque, LinkedList, ConcurrentLinkedDeque); 4:优先级队列(PriorityQueue, PriorityBlockingQueue) 每种类型的队列都是针对不同的应用场景的,所以还是需要仔细区分来选择合适的队列实现。
Map接口
map接口是集合根接口之一,用于保存具有映射关系的数据(key-vlaue)
1 public interface Map<K,V> { 2 //... 3 }
关于定义:
map集合的定义:
我们可得知:
- Map中的对象是用①键值对来存储数据的
- Map的②key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false
- 每个③键只对应一个值,不存在一个键对应多个值的情况。
- Map中的④元素是无序的,但是也有实现了排序的Map实现类,如TreeMap
关于内部接口:
Map中包括一个内部接口:Entry。此接口为泛型,定义为Entry<K,V>,它表示Map中的一个实体(一个key-value对)。
Entry有几个方法,这里只列举其中三个方法:
- Object getkey():返回该Entry里包含的key值。
- Object getValue():返回该Entry里包含的value值。
- Object setValue():设置该Entry里包含的value值,并返回新设置的value值。
关于方法:
Map接口提供的方法大致可以分为几类:
- 增删改查 :put/putAll 、 remove/clear 、 replace/replaceAll 、 get/values
- 判断:containKey/containValue(判断键/值是否存在)、isEmpty(是否为空)
- 迭代:entrySet/keySet (获取由key/entry对象组成的set集合)
- 比较:equals/hashcode (判断是否相等)
总结:
Map集合是用①键值对来存储数据的。其中②键(key)不允许重复,③只对着应一个值,而value可以重复。Map中的④元素是无序的,但是也有实现了排序的Map实现类,如:TreeMap。
至此,集合框架基本接口整理完毕。
参考资料:https://leokongwq.github.io/2016/10/15/java-Queue.html