Collection List Set和Map用法与区别

时间:2023-03-08 21:29:04
Collection List Set和Map用法与区别
labels:Collection List Set和Map用法与区别 java 散列表 集合

Collection           接 口的接口      对 象的集合   
├   List                    子接口         按进入先后有序保存      可 重复   
│├   LinkedList                 接口实现类      链表      插入删除      没有同步      线程不安全   
│├   ArrayList                   接口实现类      数组      随机访问      没有同步      线程不安全   
│└   Vector                       接口实现类      数组                     同步             线程安全   
│       └   Stack
└   Set                    子接口          仅接收一次,并做内部排序

├   HashSet

│       └   LinkedHashSet
└   TreeSet

对 于   List   ,关心的是顺序,   它 保证维护元素特定的顺序(允许有相同元素),使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在   List 中的位置,类似于数组下标)来访问   List  中的元素。

对 于   Set   ,只关心某元素是否属于   Set   (不   允 许有相同元素   ),而不关心它的顺序。

Map                 接 口         键值对的集合   
├   Hashtable                   接口实现类                     同步                线程安全   
├   HashMap                    接口实现类                     没有同步       线程不安全

│├   LinkedHashMap

│└   WeakHashMap

├   TreeMap
└   IdentifyHashMap

对于   Map   , 最大的特点是键值映射,且为一一映射,键不能重复,值可以,所以是用键来索引值。   方法   put(Object key, Object value)   添 加一个“值”   (   想要得东西   )   和与“值”相关联的“键”  (key) (   使用它来查找   )   。方法   get(Object key)   返回与给定“键”相关联的“值”。

Map   同样对每个元素保存一份,但这是基于   "   键   "   的,   Map   也有内置的排序,因而不关心元素添加的顺序。如果添加元素的顺序对你 很重要,应该使用   LinkedHashSet   或 者   LinkedHashMap.

对 于效率,   Map   由于采用了哈希散列,查找元素时明显比   ArrayList   快。

但 我有一个自己的原则想法:复杂的问题简单化。即把很多晦涩难懂的问题用通俗直白的话,一下子就看明白了,而不是大段大段的写。不得不指出的是现在部分所谓 的“专家”往往把简单的问题复杂化,让人看了生畏,甚至望而却步,以此来显示他的高深莫测,当然也可能有别的用意,那我就不得而知了。

更为精炼的总结:

Collection   是对象集合,   Collection   有两个子接口   List   和   Set

List   可以通过下标   (1,2..)   来取得值,值可以重复

而   Set   只能通过游标来取值,并且值是不能重复的

ArrayList   ,   Vector   ,   LinkedList   是   List   的实现类

ArrayList   是线程不安全的,   Vector   是线程安全的,这两个类底层都是由数组实现的

LinkedList   是线程不安全的,底层是由链表实现的

Map   是键值对集合

HashTable   和   HashMap   是   Map   的实现类     
HashTable   是线程安全的,不能存储   null   值     
HashMap   不是线程安全的,可以存储   null   值

所 以,如果你是想在一个很短的时间来弄明白这些问题,比如   1~2   分 钟。没有也不想花大量时间于此,那么建议你现在就可以收兵走人了。

如 果你想对此做一个详细的了解,请继续看下去。

众 所周知,   Java   来源于   C++   ,屏蔽了其底层实现,简化了对底层实现的管理,使开发者专注于上层功 能的实现。在   C/C++   里关于数据的存储需要程序员非常清楚,而   Java   程序员可以完全不管这些,那么,   Java   是怎么管理的呢?其实   Java   还是需要面临这些问题,只不过经过封装后,变得面目全非。所以对于像 我这种从   C/C++   转向   Java   的人还需要一段时间适应,   Collection   、   List  、   Set   、   Map   等概念还需要一个接受的过程。其实到后来发现,不管是什么语言,其底 层存储不外乎数组、线性表、栈、队列、串、树和图等数据结构。想明白了这些,一切都敞亮了。

一、容器(   Collection   )   接口   
     容器(   Collection   )是最基本的集合接口,一个容器(   Collection   )保存一组对象(  Object   ),即对象是容器的元素(   Elements   )。一些   Collection   允许相同的元素而另一些不行。一些能排序而另一些不行。   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(); //  得到下一个元素   
       }

由   Collection   接 口派生的两个接口是   List   和   Set   。   List   按对象进入的顺序保存对象,不做排序或编辑操作。   Set   对每个对象只接受一次,并使用自己内部的排序方法   (   通常,你只关心某个元素是否属于   Set,   而不关心它的顺序   --   否则应该使用   List)   。

1   ,   List   接口   
     List   是 有序的   Collection   ,次序是   List   最重要的特点:它保证维护元素特定的顺序。使用此接口能够精确的控制 每个元素插入的位置。用户能够使用索引(元素在   List   中 的位置,类似于数组下标)来访问   List   中 的元素,这类似于   Java   的 数组。和下面要提到的   Set   不 同,   List   允许有相同的元素。   
     除了具有   Collection   接口必备的   iterator()   方法外,   List   还提供一个   listIterator()  方法,返回一个   ListIterator   接口,和标准的   Iterator   接口相比,   ListIterator   多了一些  add()   之类的方法,允许添加,删除,设定元素,   还 能向前或向后遍历。   
  实现   List   接 口的常用类有   LinkedList   ,   ArrayList   ,   Vector   和   Stack   。其中,最常用的是   LinkedList   和   ArrayList   两个。

LinkedList   类   
        LinkedList   实现了   List   接口,允许   null   元素。此外   LinkedList   提供额外的  addFirst(), addLast(), getFirst(), getLast(), removeFirst(), removeLast(), insertFirst(), insertLast()   方 法在   LinkedList   的首部或尾部,这些方法(没有在任何接口或基类中定义 过)使  LinkedList   可被用作堆栈(   stack   ),队列(   queue   )或双向队列(   deque   )。

注意   LinkedList   没 有同步方法。如果多个线程同时访问一个   List   , 则必须自己实现访问同步。一种解决方法是在创建   List   时 构造一个同步的   List   :   
       List list = Collections.synchronizedList(new LinkedList(...));

特点:对顺序访问进行了优化,向   List   中间插入与删除的开销并不大。随机访问则相对较慢。   (  使用   ArrayList   代 替。   )

ArrayList   类   
     ArrayList   是 由数组实现的   List   ,并且实现了可变大小的数组。它允许所有元素,包括   null  。   ArrayList   没有同步。   size   ,   isEmpty   ,   get   ,   set   方法运行时间为常数。但是   add  方法开销为分摊的常数,添加   n   个元素需要   O(n)   的 时间。其他的方法运行时间为线性。   
     每个   ArrayList   实例都有一个容量(   Capacity   ),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自 动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用   ensureCapacity   方 法来增加   ArrayList   的容量以提高插入效率。   
  和   LinkedList   一 样,   ArrayList   也是非同步的(   unsynchronized   )。

特点:允许对元素进行快速随机访问,但是向   List   中间插入与移除元素的速度很慢。   ListIterator  只应该用来由后向前遍历   ArrayList,   而不是用来插入和移除元素。因为那比   LinkedList   开销要大很多。

Vector   类   
        Vector   非常类似   ArrayList   ,但是   Vector   是同步的。由   Vector   创建的   Iterator  ,虽然和   ArrayList   创建的   Iterator   是同一接口,但是,因为   Vector   是同步的,当一个  Iterator   被创建而且正在被使用,另一个线程改变了   Vector   的状态(例如,添加或删除了一些元素),这时调用   Iterator   的方法时将抛出   ConcurrentModificationException   , 因此必须捕获该异常。

       Stack  类:   Stack   继 承自   Vector   ,实现一个后进先出的堆栈。   Stack   提供   5   个额外的方法使得   Vector   得 以被当作堆栈使用。基本的   push   和   pop  方法,还有   peek   方 法得到栈顶的元素,   empty   方 法测试堆栈是否为空,   search   方 法检测一个元素在堆栈中的位置。   Stack  刚 创建后是空栈。

2   ,   Set   接口   
     Set   具 有与   Collection   完全一样的接口,因此没有任何额外的功能,不像前面有 几个不同的  List   。实际上   Set   就是   Collection   ,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行 为)。其次,   Set   是一种不包含重复的元素的   Collection   ,加入   Set   的元素必须定义  equals()   方法以确保对象的唯一性   (   即 任意的两个元素   e1   和   e2   都有  e1.equals(e2)=false  ),与   List   不 同的是,   Set   接口不保证维护元素的次序。最后,   Set   最多有一个   null   元素。 
  很明显,   Set   的 构造函数有一个约束条件,传入的   Collection   参 数不能包含重复的元素。   
  请注意:必须小心操作可变对象(   Mutable Object   )。如果一个   Set   中的可变元素改变了自身状态导致   Object.equals(Object)=true   将 导致一些问题。

HashSet   

为快速查找设计的   Set   。 存入   HashSet   的对象必须定义   hashCode()   。

LinkedHashSet   类: 具有   HashSet   的查询速度,且内部使用链表维护元素的顺序   (   插入的次序   )   。 于是在使用迭代器遍历   Set   时, 结果会按元素插入的次序显示。

TreeSet  

保存次序的   Set,  底层为树结构。使用它可以从   Set   中 提取有序的序列。

二、   Map   接口   
     请注意,   Map   没有继承   Collection   接口,   Map   提供   key   到   value   的映射,你可以通过“键”查找“值”。一个   Map   中不能包含相同的   key   ,每个   key 只能映射一个   value   。   Map  接口提供   3   种集合的视图,   Map   的 内容可以被当作一组   key   集 合,一组   value   集合,或者一组   key-value   映射。

方法   put(Object key, Object value)   添加一个“值”   (   想要得东西   )   和 与“值”相关联的“键”   (key) (   使 用它来查找   )   。方法   get(Object key)   返回与给定“键”相关联的“值”。可以用   containsKey()   和   containsValue()   测试   Map   中是否包含某个“键”或“值”。   标 准的  Java   类库中包含了几种不同的 Map   :   HashMap, TreeMap, LinkedHashMap, WeakHashMap, IdentityHashMap   。它们都有同样的基本接口   Map   ,但是行为、效率、排序策略、保存对象的生命周期和判定“键”等价的 策略等各不相同。

Map   同 样对每个元素保存一份,但这是基于   "   键   "   的,   Map   也 有内置的排序,因而不关心元素添加的顺序。如果添加元素的顺序对你很重要,应该使用   LinkedHashSet   或者  LinkedHashMap.

执行效率是   Map   的 一个大问题。看看   get()   要 做哪些事,就会明白为什么在   ArrayList   中 搜索“键”是相当慢的。而这正是   HashMap   提 高速度的地方。   HashMap   使 用了特殊的值,称为“散列码”   (hash code)   , 来取代对键的缓慢搜索。“散列码”是“相对唯一”用以代表对象的   int  值, 它是通过将该对象的某些信息进行转换而生成的(在下面总结二:需要的注意的地方有更进一步探讨)。所有   Java   对象都能产生散列码,因为   hashCode()   是定义在基类   Object   中的方法   。  HashMap   就是使用对象的   hashCode()   进行快速查询的。此方法能够显著提高性能。

Hashtable   类   
     Hashtable   继 承   Map   接口,实现一个   key-value   映射的哈希表。任何非空(   non-null  )的对象都可作为   key   或者   value   。   
  添加数据使用   put(key, value)   ,取出数据使用   get(key)   , 这两个基本操作的时间开销为常数。   
     Hashtable  通过初始化容量   (initial capacity)  和负载因子   (load factor)   两个参数调整性能。通常缺省的   load factor 0.75   较好地实现了时间和空间的均衡。增大   load factor   可以节省空间但相应的查找时间将增大,这会影响像   get   和   put   这样的操作。   
     使用   Hashtable   的简单示例如下,将   1   ,   2   ,   3   放到   Hashtable   中, 他们的   key  分别是   ”one”   ,   ”two”   ,   ”three”   :   
       Hashtable numbers = new Hashtable();
       numbers.put(“one”, new Integer(1));
       numbers.put(“two”, new Integer(2));
       numbers.put(“three”, new Integer(3));
  要取出一个数,比如   2   , 用相应的   key   :   
       Integer n = (Integer)numbers.get(“two”);
       System.out.println(“two = ” + n);
     由于作为   key   的对象将通过计算其散列函数来确定与之对应的   value   的位置,因此任何作为  key   的对象都必须实现   hashCode   方法和   equals   方法。   hashCode   方法和   equals   方法继承自根类   Object   ,如果你用自定义的类当作   key   的话,要相当小心,按照散列函数的定义,如果两个对象相同,即   obj1.equals(obj2)=true   ,则它们的   hashCode   必须相同,但如果两个对象不同,则它们的   hashCode   不一定不同,如果两个不同对象的   hashCode   相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以 尽量定义好的   hashCode()   方法,能加快哈希表的操作。   
  如果相同的对象有不同的   hashCode   , 对哈希表的操作会出现意想不到的结果(期待的   get   方 法返回   null   ),要避免这种问题,只需要牢记一条:要同时复写   equals   方法和   hashCode   方法,而不要只写其中一个。   
     Hashtable   是 同步的。

HashMap   类   
        HashMap   和   Hashtable   类似,也是基于散列表的实现。不同之处在于   HashMap   是非同步的,并且允许   null   ,即   null value   和   null key   。将   HashMap   视为   Collection   时(  values()   方法可返回   Collection   ),插入和查询“键值对”的开销是固定的,但其迭代子操作时间开销和   HashMap  的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将   HashMap   的初始化容量   (initial capacity)   设得过高,或者负载因子   (load factor)   过低。

     LinkedHashMap  类:类似于   HashMap   , 但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用   (LRU)   的 次序。只比   HashMap 慢一点。而在迭代访问时发而更快,因为它使用链表维护 内部次序。

WeakHashMap   类: 弱键(   weak key   )   Map   是一种改进的   HashMap   ,它是为解决特殊问题设计的,对   key   实行   “   弱引用   ”   , 如果一个   key   不再被外部所引用(没有   map   之外的引用),那么该   key   可以被垃圾收集器   (GC)   回收。

TreeMap   

基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序   (   次序由   Comparabel   或  Comparator   决定   )   。   TreeMap   的 特点在于,你得到的结果是经过排序的。   TreeMap   是 唯一的带有   subMap()   方法的   Map   ,它可以返回一个子树。

IdentifyHashMap   

使用   ==   代 替   equals()   对“键”作比较的   hash map   。专为解决特殊问题而设计。

总结一:比较

1   , 数组   (Array)   , 数组类   (Arrays)

Java   所有“存储及随机访问一连串对象”的做法,   array   是最有效率的一种。但缺点是容量固定且无法动态改变。   array   还有一个缺点是,无法判断其中实际存有多少元素,   length   只是告诉我们   array  的容量。

Java   中有一个数组类   (Arrays)   ,专门用来操作   array   。数组类   (arrays)   中拥有一组   static  函数。

equals()   :比较两个   array   是否相等。   array   拥有相同元素个数,且所有对应元素两两相等。

fill()   :将值填入   array   中。

sort()   :用来对   array   进行排序。

binarySearch()   :在排好序的   array   中寻找元素。

System.arraycopy()   :   array   的复制。

若 编写程序时不知道究竟需要多少对象,需要在空间不足时自动扩增容量,则需要使用容器类库,   array  不适用。

2   , 容器类与数组的区别

容 器类仅能持有对象引用(指向对象的指针),而不是将对象信息   copy   一 份至数列某位置。一旦将对象置入容器内,便损失了该对象的型别信息。

3   , 容器   (Collection)   与   Map   的 联系与区别

Collection   类型,每个位置只有一个元素。

Map   类型,持有   key-value  对   (pair)   ,像个小型数据库。

Collections   是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜 索、排序、线程完全化等操作。相当于对   Array   进 行类似操作的类——   Arrays   。

如,   Collections.max(Collection coll);  取   coll   中最大的元素。

Collections.sort(List list);  对   list   中 元素排序

List   ,   Set   ,   Map   将持有对象一律视为   Object   型别。

Collection   、   List   、   Set   、   Map   都是接口,不能实例化。继承自它们的   ArrayList, Vector, HashTable, HashMap   是 具象   class   ,这些才可被实例化。

vector   容器确切知道它所持有的对象隶属什么型别。   vector   不进行边界检查。

总结二:需要注意的地方

1   、   Collection   只 能通过   iterator()   遍历元素,没有   get()   方法来取得某个元素。

2   、   Set   和   Collection   拥有一模一样的接口。但排除掉传入的   Collection   参数重复的元素。

3   、   List   , 可以通过   get()   方法来一次取出一个元素。使用数字来选择一堆对象中的一个,  get(0)...   。   (add/get)

4   、   Map   用   put(k,v) / get(k)   ,还可以使用   containsKey()/containsValue()   来 检查其中是否含有某个   key/value   。

HashMap   会利用对象的   hashCode   来快速找到   key   。

哈 希码   (hashing)   就是将对象的信息经过一些转变形成一个独一无二的   int   值,这个值存储在一个  array   中。我们都知道所有存储结构中,   array   查找速度是最快的。所以,可以加速查找。发生碰撞时,让   array   指向多个   values   。即,数组每个位置上又生成一个梿表。

5   、   Map   中 元素,可以将   key   序列、   value   序列单独抽取出来。

使 用   keySet()   抽取   key   序列,将   map   中的所有   keys   生成一个   Set   。

使 用   values()   抽取   value   序列,将   map   中的所有   values   生成一个   Collection   。

为 什么一个生成   Set   ,一个生成   Collection   ?那是因为,   key   总是独一无二的,   value   允许重复。

总结三:如何选择   
从效率角度:

在 各种   Lists   ,对于需要快速插入,删除元素,应该使用   LinkedList   (可用   LinkedList   构造堆栈   stack   、队列   queue   ),如果需要快速随机访问元素,应该使用   ArrayList   。最好的做法是以  ArrayList   作为缺省选择。   Vector   总是比   ArrayList   慢,所以要尽量避免使用。

在 各种   Sets   中,   HashSet   通常优于   HashTree   (插入、查找)。只有当需要产生一个经过排序的序列,才用   TreeSet   。   HashTree   存在的唯一理由:能够维护其内元素的排序状态。

在 各种   Maps   中   HashMap   用于快速查找。

最 后,当元素个数固定,用   Array   , 因为   Array   效率是最高的。

所 以结论:最常用的是   ArrayList   ,   HashSet   ,   HashMap   ,   Array   。

更近一步分析:

如 果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。   
要特别注意对哈希表的操作,作为   key   的 对象要同时正确复写   equals   方 法和   hashCode   方法。  
尽量返回接口而非实际的类型,如返回   List   而非   ArrayList   ,这样如果以后需要将   ArrayList   换成   LinkedList   时,客户端代码不用改变。这就是针对抽象编程。