本文关键词:
java集合框架 框架设计理念 容器 继承层级结构 继承图 集合框架中的抽象类 主要的实现类 实现类特性 集合框架分类 集合框架并发包 并发实现类
什么是容器?
由一个或多个确定的元素所构成的整体叫做集合。
容器用来包装或装载物品的贮存器 (如箱、罐、坛)或者成形或柔软不成形的包覆材料。
在Java中的Collection框架,有的人叫做集合有的叫做容器,不管怎么叫基本上也离不开"把元素装起来"这个本质.
我们的世界里面丰富多彩,有各种各样的事物,很多事物都会有他的容器
人的生活自然也离不开各种容器,喝水需要杯子,吃饭需要碗,煮汤需要锅,这都是容器;
容器有各种各样的形状,也有各种各样的特性;
比如茶杯有的有盖子,有的没有盖子,水壶可以从壶嘴往外倒水等
java是面向对象的语言,万事万物皆是对象,纵然有着千姿百态的各种不同类型
所以想要在java对象中更加畅快的使用对象,自然也是需要容器的;
为什么要有容器?
按照容器的概念,数组也是一种容器,可以用于存放一个或者多个元素;
可是,数组只能保存同一种类型的元素,而且长度是固定的;
人们自然希望可以有一种容器能够保存各种不同的类型的元素,并且长度是不固定的;
这也是集合框架设计的初衷;
提供一种可以保存多种类型元素,并且长度不受限制的容器,来更加方便的保存对象;
所以java中的容器也就是java世界里面承装对象的器皿.
JAVA集合框架本质
容器根本属性在于存/取,以及一些其他的附加的操作.
容器内部有其摆放形式:排成一行还是扔到一堆?
也有他的存取顺序:先进先出还是先进后出的被压倒最下面?
这是抽象的描述
对应到计算机科学的世界里面,那即是数据结构与算法的描述
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成
数据结构中有线性结构,树形结构等,形式有队列 栈 键值对 等
至此,可以这么理解编程语言中的集合框架:
集合框架目的就只是为了盛装对象,操作对象
本质就是Java语言,针对于容器这一概念,数据结构与算法的描述实现.
更直白的说,也就只是数据结构与算法,java只是一个表现形式
比如LinkedList 他就是java语言对于双向链表的一种描述,如果你懂双向链表的原理,并且懂得java的语法,你也可以实现一个LinkedList
不过,选取哪些数据结构,使用哪些算法,继承层级如何安排,这是java自己的特点;
集合框架的层级结构
当然,并不是说你用Java编写一个双向链表就是写出来集合框架了Java是面向对象的语言,面向对象的三大基础特征,封装继承多态嘛想要给一门编程语言提供一个集合框架,自然不是写几个算法数据结构这么简单的事情Java中的集合框架是自顶而下设计的如同所有的对象的祖宗都是Object一样集合框架自然也是有祖宗的,那就是Collection 这就表示集合 ,在Java中用来存储元素的容器
不过也还有另外一派,叫做Map ,如官方文档中描述的那样,Map并不算是集合,只不过是一种操作数据的结构而已但是Map也提供了类似集合似的存取元素,元素操作等功能广义上按照我们之前说的集合/容器的概念去理解的话,自然他也可以算得上是Java集合的一份子所以一般都是把Map和Collection统称为Java的集合体系的鉴于Java语言的特性,集合体系中这些用于刻画家族脸谱的东西,自然都是接口,除非特别指明,所提到的类型均为接口
Collection中是一组独立的元素而Map中则是一组成对的键值对元素
一组独立的元素,Collection,中又可以按照有序的列表和无序的集,这就是List 和Set当然还有Queue,队列其实话说回来,为何Queue直接继承自Collection?有序和无序的并集不就已经是整体了么队列不也算是一种特殊的List么,的确队列是一种特殊的List
而且,常用的实现类LinkedList
.....implements List<E>, Deque<E>..... 并且其中 interface Deque<E> extends Queue<E>
所以说,队列逻辑思维意义上就是列表中比较特殊的一种,只不过他的特殊性比较多
所以在实现代码的时候把它单独拿出来直接继承自Collection 作为一种大的分类
也我觉得也并没有什么太大的违和感,个人理解,欢迎指正
现在我们已经"高屋建瓴"的把集合Collection分为了 List Set Queue这三种,再加上刚才说到的Map
- 列表(List):List集合区分元素的顺序,允许包含相同的元素,访问集合中的元素可以根据元素的索引来访问。
- 集(Set):Set集合不区分元素的顺序,不允许包含相同的元素,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。
- 映射(Map):Map集合保存的”键”-“值”对,“键”不能重复,而且一个“键”只能对应一个“值”,访问时只能根据每项元素的key来访问其value。
- 队列(Queue) 没什么好理解的,就是类似队伍的概念,详细的以后再说
四大天王已经诞生了
Set和Map本身是无序的,在此基础上又增加了排序的概念
所以家族里面又多出来sortedSet 和 sortedMap
既然有了排序的概念,那么在此之上继续增加个可搜索的功能也没什么好奇怪的
也就是
NavigableSet 和 NavigableMap
不得不说,美国人英语真好
Queue队列中又分为:
双端队列Deque (double ended queue)
所以主要的接口是这些:
Collection
|---List
|---Set
|---sortedSet
|---NavigableSet
|---Queue
|---Deque
|---Map
|---sortedMap
|---NavigableMap
至此,对于java集合来说,意识形态层面的设计已经完成.
集合框架的抽象类
一人心难如万人意,集合框架设计者也明白这个道理
自然知道提供的实现类并不能满足所有人需求,自然有人想要自己实现,
如果从头写来一个自然是代价巨大,考虑到这点,集合框架提供了不少的抽象类,抽象类实现了大部分通用的方法
你想要实现,只需要继承抽象类,并且实现必要的几个方法即可
当然,集合的设计本身也是这个思路,一举两得,自己写的这么方便的东西,没道理不用;
抽象类大多数以Abs开头的
AbstractCollection:
提供了Collection的主要实现
- 为了实现一个不可修改的集合,程序员只需要扩展这个类并为iterator和size 方法提供实现。(iterator方法返回的迭代器必须实现hasNext和next。)
- 为了实现一个可修改的集合,程序员必须另外重写这个类的add方法(否则抛出一个UnsupportedOperationException),迭代器方法返回的迭代器必须另外实现它的remove方法。
Collection 下的大多数子类都继承 AbstractCollection ,比如 List 的实现类, Set的实现类。
AbstractList
List接口的骨架实现,最大限度地减少实现由“随机访问”数据存储(如数组)所支持的接口所需的工作量。
对于顺序访问数据(如链接列表),应该优先使用AbstractSequentialList。
AbstractSequentialList
List接口的骨架实现,以最大限度地减少实现由“顺序访问”数据存储(如链接列表)支持的接口所需的工作量。
对于随机访问数据(如数组),应优先使用AbstractList。
AbstractSet
提供了Set接口的骨架实现,不会覆盖AbstractCollection类中的任何实现。它只是增加了equals和hashCode的实现。
通过扩展此类来实现集合的过程与通过扩展AbstractCollection来实现集合的过程相同
不同之处在于此类的所有子类中的所有方法和构造函数都必须遵守Set接口施加的额外约束(例如,添加方法不得允许将一个对象的多个实例添加到一个集合中)。
AbstractQueue
提供了一些Queue操作的骨架实现
当基类实现不允许空元素时,此类中的实现适用。
方法add,remove和element分别基于offer,poll和peek,但是会抛出异常而不是通过false或null返回来指示失败。
扩展此类的任何Queue实现类至少也需要定义方法Queue.offer(E),该方法不允许插入空元素
以及方法Queue.peek(),Queue.poll(),Collection.size()和Collection.iterator()。
通常,其他方法也将被覆盖。如果这些要求不能满足,请考虑派生AbstractCollection的子类。
AbstractMap
Map接口的骨架实现
要实现一个不可修改的映射,程序员只需要扩展这个类并为entrySet方法提供一个实现,该方法返回Map映射的set-view。
通常,返回的集合是AbstractSet的一个实现。而且一般是内部类的形式
这个集合不应该支持add或remove方法,它的迭代器不应该支持remove方法。
EnumSet
用于枚举类型的专用Set实现
集合框架的重要实现
主要的实现类有:
Collection下面:
其中List的实现类主要是:
(1)ArrayList
List接口的可调整大小数组实现。
实现List接口中所有的可选操作,并允许任意元素,包括null。
除了实现List接口之外,该类还提供了一些方法来控制用于内部存储列表的数组大小。(这个类大致相当于Vector,除了它是不同步的。)
此实现是不同步。
(2)LinkedList
List和Deque接口的双端链表实现。实现List接口所有的可选操作,并允许任意元素(包括null)。
此实现是不同步。
(3)Vector
Vector类实现了一个可增长的对象数组。
像数组一样,它包含可以使用整数索引访问的组件。
不同于数组的是,Vector的大小可根据需要增大或减小,以适应在创建Vector之后添加和移除项目。
同步的
(4)Stack
Stack类表示后进先出(LIFO)对象堆栈。
它使用五个操作来扩展类Vector,这样子可以将一个Vector视为一个堆栈。
提供了:
通常的推送和弹出操作,
以及一种方法来查看堆栈中的顶层项目,
一种方法来测试堆栈是否为空,
以及一种方法来搜索堆栈中的项目并发现它有多远是从顶部。
当第一次创建堆栈时,它不包含任何元素。
Deque接口及其实现提供了更完整和一致的LIFO堆栈操作集,应优先使用此类。例如:
Deque<Integer> stack = new ArrayDeque<Integer>();
Set下面主要是:
(1)HashSet
这个类实现了Set接口
由一个哈希表(实际上是一个HashMap实例)支持。
它对集合的迭代次序没有任何保证;
特别是,它不能保证顺序会随着时间的推移保持不变。这个类允许null元素。
HashSet应该是你在没有特殊要求下的默认选择
这个类为基本操作(添加,删除,包含和大小)提供了恒定的时间性能,假设散列函数在桶之间正确地分散元素。
迭代此集合需要的时间与HashSet实例的大小(元素数量)加上支持HashMap实例的“容量”(桶的数量)的总和成正比。
因此,如果迭代性能很重要,不要将初始容量设置得太高(或者负载因子太低)是非常重要的。
非同步的
(2)TreeSet
基于TreeMap的NavigableSet实现。
这些元素使用它们的自然顺序或者在创建集合时提供的比较器进行排序,具体取决于使用哪个构造函数。
这个实现保证:基本操作(添加,移除和包含)的时间复杂度为 log(n)非同步的
(3)LinkedHashSet
Set接口的哈希表和链表实现,具有可预测的迭代顺序。
这个实现与HashSet的不同之处在于它保持了一个双向链表,它贯穿其所有条目。
此链接列表定义迭代排序,即元素插入到集合中的顺序(插入顺序)。
请注意,如果元素重新插入到集合中,则插入顺序不受影响。
(如果s.contains(e)在调用之前立即返回true,则调用s.add(e)时,将元素e重新插入到集合s中。)
非同步的
HashSet的性能总是比TreeSet好,特别是添加和查询元素
TreeSet存在的唯一原因就是可以维持元素的排序状态,所以,只有当需要一个排好序的Set时
才应该使用TreeSet
对于插入操作 LinkedHashSet比HashSet的代价要高
Queue的:
(1)ArrayDeque
Deque接口的可调整大小的实现。
Array deques没有容量限制;根据使用情况动态增长.
它们不是线程安全的
在没有外部同步的情况下,它们不支持多线程的并发访问。
禁止使用空元素
当用作堆栈时,该类可能比Stack快,并且在用作队列时比LinkedList快。
这个类的迭代器方法返回的迭代器是快速失败机制的,会抛异常
ConcurrentModificationException
.
(2)PriorityQueue
基于优先级堆的无限优先级队列
优先级队列的元素根据其自然排序或队列构建时提供的比较器进行排序,具体取决于使用哪个构造函数
优先级队列不允许空元素。依赖于自然顺序的优先级队列也不允许插入非可比对象(这样做可能导致ClassCastException)。
非同步的
优先级队列是无界的,但具有控制用于存储队列中元素的数组大小的内部容量。
它总是至少与队列大小一样大。随着元素被添加到优先级队列中,其容量会自动增加。
Map下面:
(1)HashMap
基于哈希表的Map接口实现
该实现提供了所有可选的Map操作,并允许使用空值和空键
(HashMap类与Hashtable大致相同,只是它不同步并允许空值。)
这个类不能保证顺序;而且,它不能保证顺序会随着时间的推移保持不变。
非同步的
(2)Hashtable
这个类实现了一个哈希表,它将键映射到值。任何非空对象都可以用作键或值。
要成功地从哈希表存储和检索对象,用作键的对象必须实现hashCode方法和equals方法。
一个Hashtable的实例有两个影响其性能的参数:初始容量和负载因子
容量是哈希表中桶的数量,初始容量就是哈希表创建时的容量。
通常,默认的加载因子(.75)在时间和空间成本之间提供了一个很好的折衷。
从Java 2平台v1.2开始,该类被改型为实现Map接口,使其成为Java集合框架的成员。
与新的集合实现不同,Hashtable是同步的。
如果不需要线程安全的实现,建议使用HashMap来代替Hashtable。
如果需要线程安全的高度并行实现,则建议使用ConcurrentHashMap代替Hashtable。
(3)LinkedHashMap
Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。
此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。
此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。
注意,如果在映射中重新插入 键,则插入顺序不受影响。
(如果在调用 m.put(k, v) 前 m.containsKey(k) 返回了 true,则调用时会将键 k 重新插入到映射 m 中。)
(3)TreeMap
基于红黑树(Red-Black tree)的 NavigableMap 实现。
该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
此实现为 containsKey、get、put 和 remove 操作提供受保证的 log(n) 时间开销。
这些算法是 Cormen、Leiserson 和 Rivest 的 Introduction to Algorithms 中的算法的改编。
此实现不是同步的
(4)WeakHashMap
以弱键 实现的基于哈希表的 Map。
在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。
更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。
丢弃某个键时,其条目从映射中有效地移除,因此,该类的行为与其他的 Map 实现有所不同。
null 值和 null 键都被支持。该类具有与 HashMap 类相似的性能特征,并具有相同的效能参数初始容量 和加载因子。
像大多数 collection 类一样,该类是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap。
(5)EnumMap
与枚举类型键一起使用的专用 Map 实现。
枚举映射中所有键都必须来自单个枚举类型,该枚举类型在创建映射时显式或隐式地指定。
枚举映射在内部表示为数组。此表示形式非常紧凑且高效。
(6)IdentityHashMap
此类利用哈希表实现 Map 接口,比较键(和值)时使用引用相等性代替对象相等性。
换句话说,在 IdentityHashMap 中,当且仅当 (k1==k2) 时,才认为两个键 k1 和 k2 相等
(在正常 Map 实现(如 HashMap)中,当且仅当满足下列条件时才认为两个键 k1 和 k2 相等:(k1==null ? k2==null : e1.equals(e2)))。
此类不是 通用 Map 实现!
此类实现 Map 接口时,它有意违反 Map 的常规协定,该协定在比较对象时强制使用 equals 方法。此类设计仅用于其中需要引用相等性语义的罕见情况。
并发编程相关接口和类
基于并发编程的特性
又延伸出来下面这些接口:
java.util.concurrent包中
队列Queue中:
阻塞队列 BlockingQueue--生产者向队列添加元素但队列已满时,生产者会被阻塞;当消费者从队列移除元素但队列为空时,消费者会被阻塞
双端的阻塞队列 BlockingDeque
阻塞队列又细分出来的概念TransferQueue
比BlockingQueue更进一步:
生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)。
新添加的transfer方法用来实现这种约束。
顾名思义,阻塞就是发生在元素从一个线程transfer到另一个线程的过程中,它有效地实现了元素在线程之间的传递
Map中:
ConcurrentMap 接口代表一个Map,它可以处理并发访问。
ConcurrentMap除了继承自java.util.Map的方法,还有一些自己的原子方法。
ConcurrentNavigableMap支持 NavigableMap 操作,且以递归方式支持其可导航子映射的 ConcurrentMap。
并发相关实现类 java.util.concurrent:
LinkedBlockingQueue
ArrayBlockingQueue
PriorityBlockingQueue
DelayQueue
SynchronousQueue
LinkedBlockingDeque
LinkedTransferQueue
CopyOnWriteArrayList
CopyOnWriteArraySet
ConcurrentSkipListSet
ConcurrentHashMap
ConcurrentSkipListMap
ConcurrentLinkedDeque
ConcurrentLinkedQueue
集合框架接口中使用到的一些基础支撑类
Iterable:
实现这个接口允许对象成为 "foreach" 语句的目标。
实现了这个接口就表明已经遵从"迭代定义的规则",拥有了迭代的能力.
他是一个顶级接口:
其中:
/** * Returns an iterator over elements of type {@code T}. * * @return an Iterator. */ Iterator<T> iterator();
Iterator:
public interface Iterator<E>
想要有迭代的能力,
需要实现Iterable接口
实现接口最重要的就是提供一个 iterator() 方法,用于返回迭代器
为什么不直接实现Iterator?
首先,集合本身并不是迭代器,他只是有可以迭代的功能,所以是组合关系.
而且,如果继承的话,那么集合框架中:
Iterator接口的核心方法next()或者hasNext()
集合对象中就会包含当前迭代位置的数据(指针)
当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么next()方法的结果会变成不可预知
RandomAccess
标记接口
/ * * @since 1.4 */ public interface RandomAccess { }
用来表明其支持快速(通常是固定时间)随机访问。
主要目的是使算法能够在随机和顺序访问的list中表现的更加高效。
此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。
比如:
Collections下的binarySearch方法的源码:
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c) { if (c==null) return binarySearch((List<? extends Comparable<? super T>>) list, key); if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) return Collections.indexedBinarySearch(list, key, c); else return Collections.iteratorBinarySearch(list, key, c); }
从中可以清晰的看到,这个RandomAccess标记的作用
java集合框架中的所有具体类中都实现了Cloneable和Serializable接口
因此它们的实例都是可复制且可序列化的。
Serializable
public interface Serializable { }