Java集合是Java非常重要的一个概念,也是我们经常用到的。在没有Java集合框架的时候,我们一般都使用数组,但数组的长度是不可变的,一旦在初始化数组的时候声明了长度,那么就有了固定的大小,在面对实际场景时,我们往往需要对数组进行扩展;而且数组无法表示一种映射关系,例如个人信息:姓名-张三。这个时候就需要引入我们的集合框架了。很难给一个集合框架下一个定义,
不过我们可以这样理解,集合就是把具有相同性质的东西汇聚成了一个整体。听起来是不是很抽象,感觉有点像我们类,类是具备某些共同特征的实体的集合,它是一种抽象的概念。
实际上我们可以把Java集合框架当作是一种容器,而把类的实例化对象,丢进容器里(实际上是对象的引用),相比于java类,集合框架的范围明显要更大一些,其实我们也
可以不需要类,直接把对象的一个个属性给丢进容器里,这样也是可以的,只不过用了类,之后才能更好管理我们的对象。对比我们生活中的例子就是,假如我们要去超市买东西,买了一瓶洗发水,洗发水是用瓶子装的吧,这个瓶子里面封装了我们的洗发水,我们可以把这个瓶子看作是一个类,就叫做瓶子类,将来我们实例化的时候,他就有了名字,
叫洗发水瓶,也有了作用,等等的一些属性,那么我们就可以说这是一个类。但是我们可能不止买洗发水,我们还会买洗面奶,沐浴露,这个时候我们就要把他们给装到袋子里面,我们就可以把这个袋子理解成为集合框架,他装了一堆用瓶子装的东西。
Java集合大致可以分为四种体系,无序不可重复的集合Set,有序可重复的集合List,带有映射关系的集合Map,实现队列集合的Queue,这里只是讨论常用的集合框架。Java集合类主要是有两个接口派生而出的,一个是Collection,一个是Map;他们是集合框架的根接口,跟集合有关的类都写在Java.uitl的包里面。集合框架功能强大,里面会存储枚举类型的数据,Java为每个类都写了一个抽象父类。这样做的好处就是我们可以去实例化父类,并可以重写里面的一部分方法,来达到我们的需求。另外每个集合类都可以生成一个迭代器,我们可以通过这种方法来遍历集合。
下面介绍几个Collection接口下定义的方法:
1.add:
确保此 collection 包含指定的元素(可选操作)。如果此 collection 由于调用而发生更改,则返回true。
(如果此 collection 不允许有重复元素,并且已经包含了指定的元素,则返回false。)
支持此操作的 collection 可以限制哪些元素能添加到此 collection 中来。需要特别指出的是,一些 collection
拒绝添加null 元素,其他一些 collection 将对可以添加的元素类型强加限制。Collection 类应该在其文档中
清楚地指定能添加哪些元素方面的所有限制。如果 collection 由于某些原因(已经包含该元素的原因除外)拒绝
添加特定的元素,那么它必须 抛出一个异常(而不是返回false)。
这确保了在此调用返回后,collection 总是包含指定的元素。
2.remove:
从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。更确切地讲,如果此 collection
包含一个或多个满足(o==null ? e==null : o.equals(e)) 的元素e,则移除这样的元素。如果此 collection
包含指定的元素(或者此 collection 由于调用而发生更改),则返回true 。
3.addAll:
将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。如果在进行此操作的同时修改指定的
collection,那么此操作行为是不确定的。(这意味着如果指定的 collection 是此 collection,并且此 collection 为非空,
那么此调用行为是不确定的。)
4.clear:
移除此 collection 中的所有元素(可选操作)。此方法返回后,除非抛出一个异常。
5.equals:
比较此 collection 与指定对象是否相等
5.size:
获取指定集合的长度
List集合:
List集合代表的是一组元素的有序集合,在集合中的每个元素都会按照添加的顺序来设置索引,例如第一个
添加的索引就是0,后面的将会依次添加,List集合是可以有重复的元素的。由于List集合也是继承了Collection接口,
所以Collection的方法,他都能够使用,另外List还扩展了几种方法,使之更加符合自身的特性。
List 接口提供了两种搜索指定对象的方法。从性能的观点来看,应该小心使用这些方法。在很多实现中,它们将执行高开销的线性搜索。
下面介绍几种List的方法
add(int index,E element)
在列表的指定位置插入指定元素(可选操作)。
get(int index)
返回列表中指定位置的元素。
indexOf(Object o)
返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
remove(int index)
移除列表中指定位置的元素(可选操作)。
set(int index,E element)
用指定元素替换列表中指定位置的元素(可选操作)。
listIterator()
返回此列表元素的列表迭代器(按适当顺序)。
listIterator()的迭代器比较特殊,他比Collection的迭代器要多一个添加和反向迭代的功能
,我们可以再迭代的过程中给集合添加值,也可以通过反向迭代来遍历集合。
打印结果如图所示:
下面将要来对比一个几个List常用的实现类有什么不同:
先了解一下List
List列表类,顺序存储任何对象(顺序不变),可重复。
List是继承于Collection的接口,不能实例化。实例化可以用:
ArrayList(实现动态数组),查询快(随意访问或顺序访问),增删慢。整体清空快,线程不同步(非线程安全)。数组长度是可变的百分之五十延长
LinkedList(实现链表),查询慢,增删快。
Vector(实现动态数组),都慢,被ArrayList替代。长度任意延长。线程安全(同步的类,函数都是synchronized)
Stack(实现堆栈)继承于Vector,先进后出。
List基本操作
插入:add()
查找:get()
删除:remove(int index)
修改:set()
清空表:clear()
遍历:用Iterator迭代器遍历每个元素
ArrayList、LinkedList性能对比
- ArrayList插入、删除效率明显低于LinkedList
- ArrayList查询效率远远高于LinkedList
可以认为插入删除频繁选择LinkedList,追求查询效率就选择ArrayList呢,我们先来分析一下效率差别的原因,这个就跟数据结构有关系了,可以参考一些数据结构中链表的知识,arraylist 顺序表,用数组的方式实现。想想数组要查询那个元素只给出其下标即可,所以才说arraylist随机访问多的场景比较合适。但是如果删除某个元素比如第 i 个元素,则要将 i 之后的元素都向前移一位以保证顺序表的正确,增加也是一样,要移动多个元素。要多次删除增加的话是很低效的。而LinkedList是双向链表,注意是链表。要查询只能头结点开始逐步查询,没有什么给出下标即可直接查询的便利,需要遍历。但是,如果要增加后删除一个元素的话,只需要改变其前后元素的指向即可,不需要像arraylist那样整体移动,所以才说多用于增删多的场合。
由于LinkedList查询只能从头结点开始逐步查询的,可以使用 iterator 的方式,就不用每次都从头结点开始访问,因为它会缓存当前结点的前后结点。实测查询效率与ArrayList没有太大差别
整Vector是在Collections API之前就已经产生了的, 而ArrayList是在JDK1.2的时候才作为Collection framework的一部分引入的.
所以Vector在JavaME、Card等各种微小版本都可以使用,而ArrayList不能.Arraylist和vector都是作为List类的典型实现,他们都有List接口的功能。Arraylist和VectorList都是基于数组实现的集合,所以他们内部都封装了一个动态的,允许再分配的Object[]数组。
他们都可以使用参数来设定初始值的大小,如果不设定的话,那么初始值都为10,如果他们的容量超过了10,可以给Vector设置他增长的大小,如果没有设置那么默认的就会去增加2倍,而Arraylist在JDk1.8以后会去增加0.5倍。他们之间最显著的区别就是Vector是线程安全的同步的,
而Arraylist是线程非安全的不同步的,所以Vector会需要额外的开销来维持同步锁, 那么它它要比ArrayList要慢.(理论上来说),实际上我们要
实现List的同步类,也同样不推荐使用Vector,我们可以使用Collections的工具类,Collection.synchronizedList(List)再去转一次了。
另外Vector还
提供了一个子类,Stack,它主要用于模拟栈的结构,先进后出。里面有如下几种方法
peek()
查看堆栈顶部的对象,但不从堆栈中移除它。
pop()
移除堆栈顶部的对象,并作为此函数的值返回该对象。
push(E item)
把项压入堆栈顶部。
search(Object o)
返回对象在堆栈中的位置,以 1 为基数。
Set集合:
set集合就类似于一个罐子,他会把所有的东西都给放在里面,集合里面的多个对象没有明显的顺序,Set和
Collection基本上差不多,只不过Set集合里面不包含重复的元素,而且最多也只允许有一个null值,如果你要把
一个相同的元素给放进set集合里面,使用add方法的时候会返回一个False,并没有添加成功。
打印结果会发现一个true,一个false;
Set判断两个值相等的方法用的是equalse,而不是==,也就是说当两个值一样的时候,就不会添加进去,反之当equalse的值等于false的时候,两个值都可以添加进去。当然也有极端的例外,
例如我重写了equalse方法,而没有重写HashCode方法,
当他们的值相同的时候也仍然会添加两个进去。
HashSet:
HashSet是Set的一个典型实现,大多数使用HashSet这个类,HashSet按照Hash算法来存储集合中的元素的,因此具有很好的存取和查询功能。
查看源代码,我们会发现其实HashSet里面有一个HashMap的实例,HashSet具有如下特点。
1.不能保证元素的排列顺序,顺序有可能发生变化。
2.HashSet不是同步的,因此如果有多个线程同时访问一个HashSet的话,那么必须的通过代码来保持同步;
3.只能有一个null值(这也是Set集合很典型的特点);
另外HashSet还有一个子类LinkedHashSet,它是以链表的形式来存储值的,它也同样根据HashCode值来决定
元素的存储位置,只不过他是按照插入的顺序来存储的,因为LinkedHashSet需要去维护元素插入的顺序,所
以性能要略微低于HashSet,但是当我们迭代访问里面的元素的时候,会有很好的性能,因为他本身就是链表
结构;
TreeSet:
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet是根据红黑树的数据结构来存储数
据的,TreeSet支持两种排序方式,自然排序 和定制排序,
其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该
接口的对象就可以比较大小。obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,
则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现
int compare(T o1,T o2)方法
EnumSet是一个专门为枚举类设计的集合类,EnumSet中的所有元素都是指定枚举类型的枚举值,该枚举
类型是创建在EnumSet时隐式的指定,Enum也是有序的,他以枚举值定义的集合顺序来决定排序的顺序的。
上图定义了一个颜色的枚举类,里面有红,黄,绿,三种颜色,通过枚举类,可以给他遍历出来。枚举内部是以向量的形式存储数据的,这种存储数据非常
紧凑高效,因此枚举对象占用内存少,运行效率很好,尤其是进行批量操作,效率会非常快;另外枚举值里面不允许有Null,如果试图插入一个Null值将会
报错;
HashSet与TreeSet,LinkedSet的比较:
首先他们的相同点:1.他们都实现了Set接口,都是Set类的典型实现;
2.他们都不允许有重复的值添加进去,而且只有有一个Null值;
3.他们都是线程不安全的,如果有多个线程执行集合操作我们就需要使用同步机制了;
4.他们都是根据哈希码来判断存储的元素的;
不同点:1.结构不同,HashSet是以数组的形式来存储集合的,TreeSet则是以红黑树的形式来存储的,LInkedSet则是以
链表的形式;
2.效率不同,HashSet对于普通的批量插入,删除操作,要快,而LinkedSet要慢一点,因为他有指针,而TreeSet最慢
,因为他需要去维持一个红黑树,对于遍历的LinkedSet要稍微快一点。
Map集合:
Map集合用来保存具有映射关系的数据,因此Map集合里面保存了两组值,一组是Key,一组是Value,我们通常叫做键值对。Map的值可以空Null,
但是Map的Key不允许重复。他们比较的方法是通过equals;Map的Key和Value总是一对一的存在的,即通过一个key总能找到一个唯一的value;当我
们取数据的时候只需要根据Key值来取就可以了;实际上Map和Set两个方法非常的相识,很多的Set里面会有一个Map实例;
下面说一说HashMap和HashTable的区别:
HashMap和HashTable是Map类的两个的经典实现,HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,他们之间的关
系有点类似Arraylist和Vector之间的关系;
1HashTable是JDK1.0都开始出现了,是一个很古老的版本,而HashMap则是从jdk1.2版本开始出现;
2.HashTable是线程同步的,所以效率要低于HashMap,而HashMap是线程非同步的,所以效率要高于HashTable;
3.HashTable里面不允许有空值,而HashMap里面可以有一个Null的键和无数个null的Value;
再说说他们的相似之处:
1.他们都是无序的,即都不能保证插入的顺序
2.他们都很好的实现了Map接口,都是以键值对的形式纯在的;
3.劲量不要使用可变对象作为他们的Key值,因为这样有可能会导致我们的访问异常;
LinkedHashMap:
HashSet类里面有一个LinkdHashSet类,HashMap里面也有一个LinkedHashMap类;LinkedHashMap类是以双向链表的形式来维护Key-value的的次序;
该链表负责Map的迭代顺序,迭代顺序与插入顺序一致;
由于LInkedHashMap需要维护插入的元素顺序,所以效率要慢与HashMap,但是它以链表的形式来维护里面的元素,所以迭代的效率很比较快;
Properties:
Properties是Hashtable的子类,该对象在处理属性对象时特别方便,Properties可以把Map对象和属性关联起来,从而可以把Map中的Key和Value对象
写到属性文件中去,也可以把属性文件中的值加载到集合中来。在我们平常开发的时候会用到这个属性文件,加载JDBC链接;他也可以把值以XML的
方式存储起来,也可以读取XML里面的元素;
TreeMap:
TreeMap就是一个红黑树的数据结构,每个Key-value都会作为红黑树的一个节点,TreeMap存储值的时候会排序,默认是升序排序,当然也有定制排序。
WeakHashMap:
他与HashMap的区别就在于,他保存了一个对象的弱引用,如果该对象的Key没有被其他对象强引用,Jvm就会认为这是一个垃圾,当系统第一次GC的时候
就会把它给回收;
最后再说一下集合工具类:Colltctions
此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。它包含在 collection 上操作的多态算法,即“包装器”,包装器返回由指定 collection 支持的新 collection,以及少数其他内容。
如果为此类的方法所提供的 collection 或类对象为 null,则这些方法都将抛出 NullPointerException。