持有多个对象-容器

来源:互联网 发布:怎么查看淘宝店铺等级 编辑:程序博客网 时间:2024/05/16 04:12

使用数组,我们已经能够持有多个对象了,但当持有的对象的数目在编译时不能确定,用数组就不能满足要求了,这个时候我们需要集合类,好的程序语言都有已经实现了的集合类库,java也不例外。
编程语言提供的集合类有可能不相同,java提供的集合类,出于行为和性能的考虑,分为了两大种和很多不同的实现。

java提供的容器分为两种collection和map.

Collection是一组独立的元素,其子接口中list必须保持元素的顺序,set不能有重复的元素。
Map是一组键值对儿。能返回键的Set。值的List。这里有个分类图可以很好的体现各个接口的关系:

通过这个分类可以看出容器有四个接口。Collection,Set,List,Map。理想的情况下,我们应该只对接口进行编程,以可以在需要的情况下更改其实现的方式。
在这些容器中,只有Vector和hashtable是线程安全的,其他的可以通过Collections.synchronizedList。

 

下面我们来详细的说一下各种容器的功能和方法:
Collection
Collection的公用方法:

  • add()持有参数。 
  • addAll(Collection c) 把c中的元素都添加进去。
  • clear()移除容器中所有的元素。
  • contains(Object)如果已经持有了则返回true,判断的条件是hashCode()
  • isEmpty()是否为空。
  • remove(Object o)如果o存在则进行移除。
  • size()   持有的元素的个数。
  • toArray()  返回一个数组,包含容器中的所有元素。

 

下面分别讨论每种容器的功能。
Collection,每个位置只持有一个数据,又分为:List和Set
List的主要特性在于能够保持元素插入的顺序:
我们通常使用的有两种List。

  • ArrayList。由数组实现的List.随机访问较快,但是插入删除开销比较大。
  • LinkedList 对按顺序访问进行了优化,随机访问比较慢,但插入删除等操作开销比较小,并提供了很多有利于数据结构的方法:addFirst(),addLast(),getFirst(),getLast(),removeFirst(),和removeLast()等方法,通过这些方法使得LindedList可以当做堆栈、队列和双向队列等数据结构来使用。看一个例子:用LinkedList实现队列

Set的功能方法
存入Set的每个元素都必须是唯一的,因为Set不保存重复的元素。加入Set的元素都必须定义equals()方法以保证对象的唯一性。java还规定了覆盖equals()方法的时候也要覆盖hashCode()使得这两个方法的值能够统一。因此在set之中判断是否相同是按照这两个方法来判断的。

  • HashSet是为快速查找设计的Set,加入HashSet的元素必须定义了hashCode()。
  • TreeSet保证次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。注意这里是保证次序,不是维持顺序,所有包含到TreeSet中的元素必须实现了Comparable接口,以告诉TreeSet以什么方式排序。
  • LinkedHashSet具有hashset的查询速度,并且能维护元素顺序。使用迭代器遍历时会按照插入的顺序显示。

Map的功能和方法。
Map中存储的是键值对儿。

  • HashMap。取代了HashTable。基于散列表的实现。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。
  • LinkedHashMap.遍历时取得的键值对的顺序是插入的顺序。,比HashMap稍慢。
  • TreeMap。查看的时候,他们会按照键进行排序。被排序次序由Comparator决定,与TreeMap类似。

上面对于不同的集合的特点都进行了说明。我们可以根据不同的需要选择不同的容器。在这些容器当中,不管是不能重复的Set还是有键值的Map都需要判断一个对象是否一样,并且还需要对对象进行遍历。进行遍历通常都是比较耗费效率的,我们可以使用二进制遍历的方式,这里就会涉及到散列算法和散列码。
散列算法和散列码。

首先我们来看一下如何来判断对象的唯一性。java中就是通过equals()方法和hashCode()方法来判断的。所以当我们用自定义的类作为Key或者装进Set的时候,默认是按照内存空间作为唯一性判断的,如果我们想改变唯一性就必须覆盖equals()方法和hashCode()方法。并且要保证这两个方法的一致性。给出一个例子:

会发现虽然加入了两次,但是只保留了1个对象。
对于这个equals()方法的现实必须满足下列5个条件:

  • 自反性:x.equals(x)一定返回true.
  • 对称性:如果x.equals(y)返回true,则y.equals(x)也返回true。
  • 传递性:x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)也一定返回true。
  • 一致性:不论x.equals(y)多少次,返回的结果应该是一致的。
  • 对于任何不是null的x,x.equals(null)一定返回false。

好了,到目前为止,我们知道了java中如果一个对象能成为键值则必须覆盖了hashCode()和equals()方法。但是如果只是为了确定一个对象,使用equals()就可以了,hashCode()方法有什么特殊的意义呢?
这个hashCode()方法就是为了得到散列码为了达到快速查询的目的。
首先我们先来看一个自己实现的Map,只是实现最简单的持有key和值的功能

我们可以看到其中的get方法将会遍历所有的list元素,这是很影响效率的。
我们知道根据数组的下标能够最快的定位元素,因此我们最好能够把数组的下标与对象的唯一性关联起来。这时候就是使用到了散列码。看一个比较快速的实现:


这个新的Map的get方法的执行过程就是先计算hashCode。然后找到他所在的桶。再从桶中遍历通过equals方法得到这个对象,这样就能够比全遍历获得比较高的效率。
很显然如果是桶越多则定位会比较快,但是会比较费内存。这个是决定map执行速度的因素之一称之为Capacity.
另外一个因素是load factor,叫负载因子。就是使用的桶和总桶树的比例。当大于这个比例的时候会重新分配已经持有的数据,增加桶的容量。
负载因子小了,可以减少重复率,增加查询速度,初始桶的数目设的比较大也可以减少rehashing的次数,但是会加大占有的内存,所以我们可以设置不同的因素来调整map的效率。通常默认的(16,0.75)已经可以了。

 

总结一下如何选用容器

  • 对于随机查询与迭代遍历,数组比所有的容器快。
  • 随机访问ArrayList的开销小于LinkedList.从中间插入和删除元素,LinkedList要比ArrayList快。
  • HashSet的性能总是比TreeSet好。TreeSet存在的唯一原因是可以维持元素的排序状态。
  • HashMap的效率最高,当需要一个总是排好序的Map的时候,才使用TreeMap.LinkedHashMap比HashMap慢一点,因为他维护散列数据结构的同时还要维护链表保持插入的顺序。


对容器的操作:
容器打印
   容器自带的打印功能已经很好的,collection是[a,b]而Map是{a=b, c=d}的形式。
填充容器
   有工具类Collections。有fill方法进行容器填充,是把容器中所有的元素都替换为这个元素。
   排序sort,与数组的相同。
   还有其他一些max(),min()等方法也是根据Comparator进行比较的。
容器的遍历
使用迭代器Iterator。
容器的类型 1.5版本之前放进容器的对象的类型会丢失,还好现在我们有了参数化类型,也就是泛型的机制。

容器本身都不是线程安全的,这一点等到线程安全的时候再说。

原创粉丝点击