Java集合概述

在编程时,常常需要集中存放多个数据。可以使用数组来保存多个对象,但数组长度不可变化,一旦在初始化数组时指定了数组长度,这个数组长度就是不可变的,如果需要保存数量变化的数据,数组就有点无能为力了;而且数组无法保存具有映射关系的数据,如:成绩表:语文-100,数学-99,这种数据看上去像两个数组,但这两个数组的元素之间有一定的关联关系。
为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。所有的集合类都位于Java.util包下,后来为了处理多线程环境下的并发安全问题,java5还在java.util.concurrent包下提供了一些多线程支持的集合类。
集合类和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合类只能保存对象(实际上只是保存对象的引用变量,但通常习惯上认为集合里保存的是对象)
java的集合类主要由两个接口派生而出:Collection和Map。Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。

该图是Collection接口和子接口及其实现类的继承树
改图显示了Collection体系里的集合,其中Set(无序集合、元素不可重复),queue(队列),List(有序集合,元素可以重复)。Set和List接口是Collection接口派生出的两个子接口,它们分别代表无序集合和有序集合;Queue是Java提供的队列实现,有点类似与List。

如下图所示是Map体系的继承树,所有的Map实现类用于保存具有映射关系的数据(也就是前面介绍的关联数组)
下图显示了Map接口的众多实现类,这些实现类在功能、用法上存在一定的差异,但他们都有一个功能特征:Map保存的每项数据都是key-value对,也就是由key和value两个值组成。好比前边的成绩单:语文-100,数学-100,每项成绩都有两个值组成,即科目名和成绩。对于一张成绩表面而言,科目通常不会重复,而成绩是可重复的,通常习惯根据科目来查阅成绩,而不会根据成绩来查询科目。Map与此类似,Map里面的key是不可重复的,key用于标识集合里的每项数据,如果需要查阅Map中的数据时,总是根据Map的key来获取。
在这里插入图片描述

Set、Queue、List、Map,可以把Java所有集合分成三大类,其中Set
集合类似于一个罐子,把一个对象添加到Set集合时,Set集合无法记住添加这个元素的顺序,所有Set集合里的元素不能重复(否则系统无法准确识别这个元素);List集合非常像一个数组,他可以记住每次添加元素的顺序,且List的长度可变。Map集合像一个罐子,只是它里面的每项数据都由两个值组成。
在这里插入图片描述
如果访问List集合中的元素,可以直接根据元素的索引来访问;如果访问Map集合中的元素,可以根据每项元素的key来访问其value;如果访问Set集合中的元素,则只能根据元素本身来访问(这便是Set集合里元素不允许重复的原因)

Set集合

Set集合类似于一个罐子,程序可以依次把多个对象“丢进”Set集合,而set集合通常不能记住元素的添加顺序。Set集合与Collection基本相同,没有提供任何额外的方法。实际上Set就是Collection,只是行为略有不同(Set不允许重复元素)
Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败,add()方法返回失败,且新元素不会被加入。

HashSet类

HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。
HashSet具有以下特点:

  • 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化
  • HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改HashSet集合时,则必须通过代码来保证其同步
  • 集合元素值可以是null

当向HashSet集合中存入一个元素时,HashSet会调用该对象的HashCode()方法来得到该对象的HashCode值,然后根据该HashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals()方法比较返回true,但他们的HashCode()方法返回值不相等,HashSet将会把他们存储在不同的位置,依然可以添加成功。
HashSet集合判断两个元素是否相等的标准是两个对象通过equals()方法比较相等,并且两个对象的HashCode()方法返回值也相等。

class A
//类A的equals()方法总是返回true,但没有重写hashCode方法()
{
   public boolean equals(Object obj)
   {
     return true
   }
}
//类B的HashCode()方法总是返回1,但没有重写 其equals()方法
class B
{
   public  int hashCode()
   {
      return 1;
   }
}
//类C的hashCode方法()总是返回2,且重写equals()方法总是返回true
class C
{
   public int hashCode()
   {
      return 2;
   }
   public boolean equals(Object obj)
   {
      return true;
   }
}
public class HashSetTest
{
   public static void main(String[] args)
   {
      HashSet books = new HashSet();
      //分别向books集合中添加两个A对象、两个B对象、两个C对象
      books.add(new A());
      books.add(new A());
      books.add(new B());
      books.add(new B());
      books.add(new C());
      books.add(new C());
      System.out.println(books);
   }
}

运行上述程序

[B@1,B@1,C@2,A@5483cd,A@9931f5]

可以看出,即使两个A对象通过equals()方法比较返回true、B对象的HashCode()返回相同值,但HashSet依然将他们当成两个对象。
如果两个对象通过equals()方法比较返回,这两个对象的HashCode值也应该相同。当把一个对象的两个方法中的一个重写时,另外一个也需要重写。
如果两个对象通过equals()方法比较返回true,但这两个对象的HashCode()方法返回不同的HashCode值时,将会导致HashSet会把这两个对象保存在Hash表的不同位置,从而使两个对象都可以添加成功,这就与Set集合的规则冲突
如果两个对象的hashCode()方法返回的HashCode值相同,但他们通过的equals()方法比较返回false时将会更麻烦;因为两个对象的HashCode值相同,HashSet将试图把他们保存在同一个位置,但又不可以(否则将会只剩下一个对象),所以实际上会在这个位置用链式结构来保存多个对象;而HashSet访问集合元素时也是根据元素的HashCode值来快速定位的,如果HashSet中两个以上的元素具有相同的HashCode值,将会导致性能下降。

hashCode方法对于HashSet?

hash(哈希表、散列)算法的功能是,它能保证快速查找被检索的对象,hash算法的价值在于速度。当需要查询集合中某个元素时,hash算法可以直接根据该元素的HashCode值计算出该元素的存储位置,从而定位该元素。数组可以包含多个元素,每个元素都有索引,如果需要访问某个数组元素,只需提供该元素的索引,根据该索引计算该元素在内存里的存储位置。(为什么不使用数组,而使用HashSet?因为数组的索引是连续的,数组的长度是固定的,无法自由增加数组的长度。HashSet采用每个元素的HashSet值来计算其存储位置,并且可以自由增加HashSet的长度,根据元素的HashCode值来访问元素。因此,当从HashSet访问元素时,HashSet先计算该元素的HashCode值(也就是调用该对象的HashCode()方法的返回值),然后直接到该HashCode值对应的位置去取出该元素)

LinkedHashSet类

HashSet的一个子类,其也是根据元素的HashCode值来决定元素的存储位置,但他同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。
LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部元素。

TreeSet类

TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
TreeSet不是根据元素的插入顺序进行排序的,而是根据元素实际值的大小来进行排序的。
HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构来存储集合元素。TreeSet支持两种排序方法:自然排序和定制排序,默认情况下,TreeSet采用自然排序。
自然排序
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。
Java提供了一个Compareable接口,该接里定义了一个compareTo(Object obj)方法。该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小。当一个对象调用该方法与另一个对象进行比较时,例如obj1.compareTo(obj2),如果该方法返回0,则表明这两个对象相等;如果该方法返回一个正整数,则表明obj1大于obj2;如果该方法返回一个负整数,则表明obj1小于obj2.
应该注意的是:大部分类在实现compareTo(Object obj)方法时,都需要将被比较对象obj强制类型转换成相同类型,因为只有相同类的两个实例才会比较大小。
(如果希望TreeSet能正常工作,TreeSet只能添加同一种类型的对象)
定制排序
例如降序排列,则可以通过Comparator接口的帮助。该接口里包含一个int compare(T o1,T 02)方法,该方法用于比较o1和o2的大小;如果该方法返回正整数,则表明o1大于o2;如果该方法返回0,则表明相等;如果返回负整数,则表明o1小于o2.

List集合

List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。List集合默认按元素的添加顺序设置元素的索引,例如第一次添加的元素索引为0,第二次添加的元素索引为1····

Queue集合

Queue用于模拟队列这种数据结构,队列通常是指“先进先出”(FIFO)的容器。队列的头部保存在队列中存放时间最长的元素,队列的尾部保存在队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素。通常,队列不允许随机访问队列中的元素。
各种线性表的性能分析
Java提供的List就是一个线性表接口,而ArrayList、LinkedList又是线性表的两种典型:基于数组的线性表和基于链的线性表。Queue代表了队列,Deque代表了双端队列(既可以作为队列使用,也可以作为栈使用),以下为各种实现类的性能分析
LInkedList:不仅提供了List的功能,还提供了双端队列、栈的功能。
一般来说,由于数组以一块连续内存区来保存所有的数组元素,所以数组在随机访问时性能最好,所有的内部以数组作为底层实现的集合在随机访问时性能都比较好;而内部以链表作为底层实现的集合在执行插入、删除操作时有较好的性能,但总的来说,ArrayList的性能比LinkedList的性能要好。
关于使用List集合有如下建议:

  1. 如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法来遍历集合元素,这样性能更好;对于LinkedList集合,则应采用迭代器来遍历集合元素
  2. 如果需要经常执行插入,删除操作来改变包含大量数据的List集合的大小,可考虑使用LinkedList集合、使用ArrayList、Vector集合可能需要经常重新分配内部数组的大小。
  3. 如果有多个线程需要同时访问List集合中的元素,开发者可考虑使用Collections将集合包装成线程安全的集合

各Map实现类的性能分析

对于Map的常用实现类而言,虽然HashMap不和Hashtable的实现机制几乎一样,但由于Hashtable是一个古老的、线程安全的集合,因此HashMap比Hashtable要快
TreeMap通常比HashMap和Hashtable要慢(尤其在插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理key-value对(红黑树的每一个结点就是一个key-value对)
TreeMap的一个好处:其中的key-value对总是处于有序状态,无须进行排序操作。
HashMap是为快速查询设计的(HashMap底层是采用数组来存储key-value对),但程序需要一个总是排好序的Map时,则可以考虑使用TreeMap
LinkedHashMap比HashMap要慢一点,因为他需要维护链表来保持Map中key-value对的添加顺序。IdentityHashMap性能没有特别出色之处,因为其采用与HashMap基本相似的实现,只是它使用==而不是equals()方法来判断元素相等。
EnumMap的性能最好,但它只能使用同一个枚举类的枚举值作为key。

简述各个实现类的优缺点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值