Map、Set、List集合差别及联系详解

时间:2022-09-08 13:52:59

提到集合之前,先说说数组Array和集合的区别:

   (1)数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型)
   (2)JAVA集合可以存储和操作数目不固定的一组数据。
   (3)若程序时不知道究竟需要多少对象,需要在空间不足时自动扩增容量,则需要使用容器类库,array不适用。  
  FYI:使用相应的toArray()和Arrays.asList()方法可以相互转换。

一、集合

  集合类存放于java.util包中。
  集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就是指集合中对象的引用(reference)。
  集合类型主要有3种:set(集)、list(列表)和map(映射)。

一、这三者什么关系呢

Collection
List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
Set
Map
├Hashtable
├HashMap
└WeakHashMap

Map、Set、List集合差别及联系详解

1.Collection接口
  Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个

Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
  如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:

Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
  Object obj = it.next(); // 得到下一个元素
} 

  iterator接口:

Map、Set、List集合差别及联系详解

  由Collection接口派生的两个接口是List和Set。

2.Set

  Set接口同样是Collection接口的一个子接口,它表示数学意义上的集合概念。Set中不包含重复的元素,即Set中不存两个这样的元素e1和e2,使得e1.equals(e2)为true。由于Set接口提供的数据结构是数学意义上集合概念的抽象,因此它需要支持对象的添加、删除,而不需提供随机访问。故Set接口与Collection的接口相同。

  Set接口继承Collection接口,而且它不允许集合中存在重复项。所有原始方法都是现成的,没有引入新方法。具体的Set实现类依赖添加的对象的equals()方法来检查等同性。

  HashSet: 使用HashMap的一个集的实现。虽然集定义成无序,但必须存在某种方法能相当高效地找到一个对象。使用一个HashMap对象实现集的存储和检索操作是在固定时间内实现的.

  TreeSet: 在集中以升序对对象排序的集的实现。这意味着从一个TreeSet对象获得第一个迭代器将按升序提供对象。TreeSet类使用了一个TreeMap.

  为优化 HashSet 空间的使用,您可以调优初始容量和负载因子。TreeSet 不包含调优选项,因为树总是平衡的,保证了插入、删除、查询的性能为log(n)

  HashSet 和 TreeSet 都实现 Cloneable 接口。

  当您要从集合中以有序的方式抽取元素时,TreeSet实现会有用处。为了能顺利进行,添加到TreeSet的元素必须是可排序的。    Set的演示:

import java.util.*;
public class SetExample {
public static void main(String args[]) {
Set set = new HashSet();
set.add("Bernadine");
set.add("Elizabeth");
set.add("Gene");
set.add("Elizabeth");
set.add("Clara");
System.out.println(set); Set sortedSet = new TreeSet(set);
System.out.println(sortedSet);
}
}

运行程序产生了以下输出。请注意重复的条目只出现了一次,列表的第二次输出已按字母顺序排序。

[Gene, Clara, Bernadine, Elizabeth]
[Bernadine, Clara, Elizabeth, Gene]

3.List

  List接口继承了Collection 接口以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。

实际上有两种List: 一种是基本的ArrayList,其优点在于随机访问元素,另一种是更强大的LinkedList,它并不是为快速随机访问设计的,而是具有一套更通用的方法。

  List : 次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用。)一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。    ArrayList : 由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和移除元素。因为那比LinkedList开销要大很多。

  LinkedList : 对顺序访问进行了优化,向List中间插入与删除的开销并不大,随机访问则相对较慢。(使用ArrayList代替。)还具有下列方法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。

  Vector:实现一个类似数组一样的表,自动增加容量来容纳你所需的元素。使用下标存储和检索对象就象在一个标准的数组中一样。你也可以用一个迭代器从一个Vector中检索对象。Vector是唯一的同步容器类!!当两个或多个线程同时访问时也是性能良好的。

  Stsck: 这个类从Vector派生而来,并且增加了方法实现栈!一种后进先出的存储结构。

  面向位置的操作包括插入某个元素或 Collection 的功能,还包括获取、除去或更改元素的功能。在 List 中搜索元素可以从列表的头部或尾部开始,如果找到元素,还将报告元素所在的位置。

void add(int index, Object element)
boolean addAll(int index, Collection collection)
Object get(int index)
int indexOf(Object element)
int lastIndexOf(Object element)
Object remove(int index)
Object set(int index, Object element)
 List 接口不但以位置友好的方式遍历整个列表,还能处理集合的子集:
ListIterator listIterator()
ListIterator listIterator(int startIndex)
List subList(int fromIndex, int toIndex)

  处理subList()时,位于fromIndex的元素在子列表中,而位于toIndex的元素则不是,提醒这一点很重要。以下for-loop 测试案例大致反映了这一点:

for (int i=fromIndex; i<toIndex; i++) {
// process element at position i
}

List用法示例:

Map、Set、List集合差别及联系详解
其中set方法返回的是被替换的内容。

Map、Set、List集合差别及联系详解

4.List和Set对比  

  Linked 改快读慢

  Array 读快改慢

  Hash 两都之间

  Collection是集合接口
    |————Set子接口:无序,不允许重复。
    |————List子接口:有序,可以有重复元素。

区别:Collections是集合类

Set和List对比:
    Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
    List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

Set和List具体子类:
    Set
     |————HashSet:以哈希表的形式存放元素,插入删除速度很快。

List
     |————ArrayList:动态数组
     |————LinkedList:链表、队列、堆栈。

Array和java.util.Vector
    Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。

5.Map

  Map接口不是Collection接口的继承。而是从自己的用于维护键-值关联的接口层次结构入手。按定义,该接口描述了从不重复的键到值的映射。

  我们可以把这个接口方法分成三组操作:改变、查询和提供可选视图。

改变操作允许您从映射中添加和除去键-值对。键和值都可以为null。但是,您不能把Map作为一个键或值添加给自身。

Object put(Object key, Object value)返回值是被替换的值。
Object remove(Object key)
void putAll(Map mapping)
void clear()

查询操作允许您检查映射内容:

    Object get(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()

提供可选视图方法允许您把键或值的组作为集合来处理。

    public Set keySet()
public Collection values()
public Set entrySet()

  因为映射中键的集合必须是唯一的,您用Set支持。因为映射中值的集合可能不唯一,您用Collection支持。最后一个方法返回一个实现Map.Entry接口的元素Set

Map.Entry 接口

  MapentrySet()方法返回一个实现Map.Entry接口的对象集合。集合中每个对象都是底层Map中一个特定的键-值对。

  通过这个集合迭代,您可以获得每一条目的键或值并对值进行更改。但是,如果底层MapMap.Entry接口的setValue()方法外部被修改,此条目集就会变得无效,并导致迭代器行为未定义。

HashMap 类和 TreeMap 类

  “集合框架”提供两种常规的Map实现:HashMapTreeMap。和所有的具体实现一样,使用哪种实现取决于您的特定需要。在Map中插入、删除和定位元素,HashMap是最好的选择。但如果您要按顺序遍历键,那么TreeMap会更好。根据集合大小,先把元素添加到HashMap,再把这种映射转换成一个用于有序键遍历的TreeMap可能更快。使用HashMap要求添加的键类明确定义了hashCode()实现。有了TreeMap实现,添加到映射的元素一定是可排序的。

  为了优化HashMap空间的使用,您可以调优初始容量和负载因子。这个TreeMap没有调优选项,因为该树总处于平衡状态。

  HashMapTreeMap都实现Cloneable接口。

  Hashtable类和Properties类是Map接口的历史实现。

  HashTable: 实现一个映象,所有的键必须非空。为了能高效的工作,定义键的类必须实现hashcode()方法和equal()方法。这个类是前面java实现的一个继承,并且通常能在实现映象的其他类中更好的使用。

  HashMap: 实现一个映象,允许存储空对象,而且允许键是空(由于键必须是唯一的,当然只能有一个)。

  WeakHashMap: 实现这样一个映象:通常如果一个键对一个对象而言不再被引用,键/对象对将被舍弃。这与HashMap形成对照,映象中的键维持键/对象对的生命周期,尽管使用映象的程序不再有对键的引用,并且因此不能检索对象。

  TreeMap: 实现这样一个映象,对象是按键升序排列的。

映射的使用示例:

  以下程序演示了具体Map类的使用。该程序对自命令行传递的词进行频率计数。HashMap起初用于数据存储。后来,映射被转换为TreeMap以显示有序的键列列表。

import java.util.*;

public class MapExample {
  public static void main(String args[]) {
    Map map = new HashMap();
    Integer ONE = new Integer(1);
    for (int i=0, n=args.length; i<n; i++) {
      String key = args[i];
      Integer frequency = (Integer)map.get(key);
      if (frequency == null) {
        frequency = ONE;
      } else {
        int value = frequency.intValue();
        frequency = new Integer(value + 1);
      }
      map.put(key, frequency);
    }
    System.out.println(map);
    Map sortedMap = new TreeMap(map);
    System.out.println(sortedMap);
  }
}

结果:

//无序输出:
{prescribed=1, a=1, time=2, any=1, no=1, shall=1, nor=1, peace=1, owner=1, soldier=1, to=1, the=2, law=1, but=1, manner=1, without=1, house=1, in=4, by=1, consent=1, war=1, quartered=1, be=2, of=3}
//有序输出:
{a=1, any=1, be=2, but=1, by=1, consent=1, house=1, in=4, law=1, manner=1, no=1, nor=1, of=3, owner=1, peace=1, prescribed=1, quartered=1, shall=1, soldier=1, the=2, time=2, to=1, war=1, without=1}

 解疑:

  1、什么是Iterator

  一些集合类提供了内容遍历的功能,通过java.util.Iterator接口。这些接口允许遍历对象的集合。依次操作每个元素对象。当使用 Iterators时,在获得Iterator的时候包含一个集合快照。通常在遍历一个Iterator的时候不建议修改集合本省。

  2、Iterator与ListIterator有什么区别?

  Iterator:只能正向遍历集合,适用于获取移除元素。ListIerator:继承Iterator,可以双向列表的遍历,同样支持元素的修改。

  3、什么是HaspMap和Map?

  Map是接口,Java 集合框架中一部分,用于存储键值对,HashMap是用哈希算法实现Map的类。

  4、HashMap与HashTable有什么区别?对比Hashtable VS HashMap

  两者都是用key-value方式获取数据。Hashtable是原始集合类之一(也称作遗留类)。HashMap作为新集合框架的一部分在Java2的1.2版本中加入。它们之间有一下区别:

  ● HashMap和Hashtable大致是等同的,除了非同步和空值(HashMap允许null值作为key和value,而Hashtable不可以)。

  ● HashMap没法保证映射的顺序一直不变,但是作为HashMap的子类LinkedHashMap,如果想要预知的顺序迭代(默认按照插入顺序),你可以很轻易的置换为HashMap,如果使用Hashtable就没那么容易了。

  ● HashMap不是同步的,而Hashtable是同步的。

  ● 迭代HashMap采用快速失败机制,而Hashtable不是,所以这是设计的考虑点。

  5、在Hashtable上下文中同步是什么意思?

  同步意味着在一个时间点只能有一个线程可以修改哈希表,任何线程在执行hashtable的更新操作前需要获取对象锁,其他线程等待锁的释放。

  6、什么叫做快速失败特性

  从高级别层次来说快速失败是一个系统或软件对于其故障做出的响应。一个快速失败系统设计用来即时报告可能会导致失败的任何故障情况,它通常用来停止正常的操作而不是尝试继续做可能有缺陷的工作。当有问题发生时,快速失败系统即时可见地发错错误告警。在Java中,快速失败与iterators有关。如果一个iterator在集合对象上创建了,其它线程欲“结构化”的修改该集合对象,并发修改异常 (ConcurrentModificationException) 抛出。

  7、怎样使Hashmap同步?

  HashMap可以通过Map m = Collections.synchronizedMap(hashMap)来达到同步的效果。

  8、什么时候使用Hashtable,什么时候使用HashMap

  基本的不同点是Hashtable同步HashMap不是的,所以无论什么时候有多个线程访问相同实例的可能时,就应该使用Hashtable,反之使用HashMap。非线程安全的数据结构能带来更好的性能。

  如果在将来有一种可能—你需要按顺序获得键值对的方案时,HashMap是一个很好的选择,因为有HashMap的一个子类 LinkedHashMap。所以如果你想可预测的按顺序迭代(默认按插入的顺序),你可以很方便用LinkedHashMap替换HashMap。反观要是使用的Hashtable就没那么简单了。同时如果有多个线程访问HashMap,Collections.synchronizedMap()可以代替,总的来说HashMap更灵活。

  9、为什么Vector类认为是废弃的或者是非官方地不推荐使用?或者说为什么我们应该一直使用ArrayList而不是Vector

  你应该使用ArrayList而不是Vector是因为默认情况下你是非同步访问的,Vector同步了每个方法,你几乎从不要那样做,通常有想要同步的是整个操作序列。同步单个的操作也不安全(如果你迭代一个Vector,你还是要加锁,以避免其它线程在同一时刻改变集合).而且效率更慢。当然同样有锁的开销即使你不需要,这是个很糟糕的方法在默认情况下同步访问。你可以一直使用Collections.sychronizedList来装饰一个集合。

  事实上Vector结合了“可变数组”的集合和同步每个操作的实现。这是另外一个设计上的缺陷。Vector还有些遗留的方法在枚举和元素获取的方法,这些方法不同于List接口,如果这些方法在代码中程序员更趋向于想用它。尽管枚举速度更快,但是他们不能检查如果集合在迭代的时候修改了,这样将导致问题。尽管以上诸多原因,Oracle也从没宣称过要废弃Vector。