Java集合使用详解

集合类

(Collection, List, Set, Map)

u     Collection – 对象之间没有指定的顺序,允许重复元素。

u     Set – 对象之间没有指定的顺序,不允许重复元素

u     List– 对象之间有指定的顺序,允许重复元素,并引入位置下标。

u     Map – 接口用于保存关键字(Key)和数值(Value)的集合,集合中的每个对象加入时都提供数值和关键字。Map 接口既不继承 Set 也不继承 Collection。

 

List、Set、Map共同的实现基础是Object数组

Collection

├List

│├LinkedList

│├ArrayList

│└Vector

│ └Stack

└Set

Map

├Hashtable

├HashMap

└WeakHashMap

除了四个历史集合类外,还引入了六个集合实现,如下表所示。

接口

实现

历史集合类

Set

HashSet

 

 

TreeSet

 

List

ArrayList

Vector

 

LinkedList

Stack

Map

HashMap

Hashtable

 

TreeMap

Properties

ListSetMap将持有对象一律视为Object型别。
Collection
ListSetMap都是接口,不能实例化。

1.2        Collection

集合必须只有对象,集合中的元素不能是基本数据类型。

Collection接口支持如添加和除去等基本操作。设法除去一个元素时,如果这个元素存在,除去的仅仅是集合中此元素的一个实例。

u    booleanadd(Object element)

u    booleanremove(Object element)

Collection接口还支持查询操作:

u    int size()

u    booleanisEmpty()

u    booleancontains(Object element)

u    Iteratoriterator()

组操作 :Collection 接口支持的其它操作,要么是作用于元素组的任务,要么是同时作用于整个集合的任务。

u     boolean containsAll(Collection collection)

u     boolean addAll(Collection collection)

u     void clear()

u     void removeAll(Collection collection)

u     void retainAll(Collection collection)

containsAll() 方法允许您查找当前集合是否包含了另一个集合的所有元素,即另一个集合是否是当前集合的子集。其余方法是可选的,因为特定的集合可能不支持集合更改。 addAll() 方法确保另一个集合中的所有元素都被添加到当前的集合中,通常称为并。 clear() 方法从当前集合中除去所有元素。 removeAll() 方法类似于 clear() ,但只除去了元素的一个子集。 retainAll() 方法类似于 removeAll() 方法,不过可能感到它所做的与前面正好相反:它从当前集合中除去不属于另一个集合的元素,即交。

 

我们看一个简单的例子,来了解一下集合类的基本方法的使用:

import java.util.*;

public class CollectionToArray {

       public static voidmain(String[] args) {            

              Collection collection1=newArrayList();//创建一个集合对象

              collection1.add("000");//添加对象到Collection集合中

              collection1.add("111");

              collection1.add("222");

              System.out.println("集合collection1的大小:"+collection1.size());

              System.out.println("集合collection1的内容:"+collection1);

              collection1.remove("000");//从集合collection1中移除掉 "000" 这个对象

              System.out.println("集合collection1移除 000 后的内容:"+collection1);

              System.out.println("集合collection1中是否包含000 :"+collection1.contains("000"));

              System.out.println("集合collection1中是否包含111 :"+collection1.contains("111"));

              Collection collection2=newArrayList();

              collection2.addAll(collection1);//将collection1 集合中的元素全部都加到collection2中

              System.out.println("集合collection2的内容:"+collection2);

              collection2.clear();//清空集合 collection1 中的元素

              System.out.println("集合collection2是否为空 :"+collection2.isEmpty());

              //将集合collection1转化为数组

              Object s[]= collection1.toArray();

              for(inti=0;i<s.length;i++){

                     System.out.println(s[i]);

              }

       }

}

运行结果为:

集合collection1的大小:3

集合collection1的内容:[000, 111, 222]

集合collection1移除 000 后的内容:[111,222]

集合collection1中是否包含000 :false

集合collection1中是否包含111 :true

集合collection2的内容:[111, 222]

集合collection2是否为空 :true

111

222

1.2.2         迭代器

   迭代器(Iterator)的概念,也是出于一种设计模式就是为达成此目的而形成的。所以Collection不提供get()方法。如果要遍历Collectin中的元素,就必须用Iterator。

下面,我们看一个对于迭代器的简单使用:

 

import java.util.ArrayList;

import java.util.Collection;

import java.util.Iterator;

 

public class IteratorDemo {

   public static void main(String[] args) {

      Collection collection = new ArrayList();

      collection.add("s1");

      collection.add("s2");

      collection.add("s3");

      Iterator iterator =collection.iterator();//得到一个迭代器

      while (iterator.hasNext()) {//遍历

         Object element =iterator.next();

         System.out.println("iterator = " + element);

      }

      if(collection.isEmpty())

         System.out.println("collection is Empty!");

      else

         System.out.println("collection is not Empty! size="+collection.size());

      Iterator iterator2 =collection.iterator();

      while (iterator2.hasNext()) {//移除元素

         Object element = iterator2.next();

         System.out.println("remove: "+element);

         iterator2.remove();

      }    

      Iterator iterator3 =collection.iterator();

      if (!iterator3.hasNext()){//察看是否还有元素

         System.out.println("没有元素");

      } 

      if(collection.isEmpty())

         System.out.println("collection is Empty!");

      //使用collection.isEmpty()方法来判断

   }

}

程序的运行结果为:

iterator = s1

iterator = s2

iterator = s3

collection is not Empty! size=3

remove: s1

remove: s2

remove: s3

没有元素

collection is Empty!

可以看到,Java的Collection的Iterator 能够用来,:

1)      使用方法 iterator() 要求容器返回一个Iterator .第一次调用Iterator 的next() 方法时,它返回集合序列的第一个元素。

2)      使用next() 获得集合序列的中的下一个元素。

3)      使用hasNext()检查序列中是否元素。

4)      使用remove()将迭代器新返回的元素删除。

需要注意的是:方法删除由next方法返回的最后一个元素,在每次调用next时,remove方法只能被调用一次 。

大家看,Java 实现的这个迭代器的使用就是如此的简单。Iterator(跌代器)虽然功能简单,但仍然可以帮助我们解决许多问题,同时针对List 还有一个更复杂更高级的ListIterator

1.3        List

List就是列表的意思,它是Collection 的一种,继承了 Collection 接口,以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。List是按对象的进入顺序进行保存对象,而不做排序或编辑操作。它除了拥有Collection接口的所有的方法外还拥有一些其他的方法。

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

u      void add(intindex, Object element) :添加对象element到位置index上

u      booleanaddAll(int index, Collection collection) :在index位置后添加容器collection中所有的元素

u      Object get(intindex) :取出下标为index的位置的元素

u      intindexOf(Object element) :查找对象element 在List中第一次出现的位置

u      intlastIndexOf(Object element) :查找对象element 在List中最后出现的位置

u      Objectremove(int index) :删除index位置上的元素

u      Object set(intindex, Object element) :将index位置上的对象替换为element 并返回老的元素。

先看一下下面表格:

 

在“集合框架”中有两种常规的List实现:ArrayList和LinkedList。使用两种 List实现的哪一种取决于您特定的需要。如果要支持随机访问,而不必在除尾部的任何位置插入或除去元素,那么,ArrayList提供了可选的集合。但如果,您要频繁的从列表的中间位置添加和除去元素,而只要顺序的访问列表元素,那么,LinkedList实现更好。

而LinkedList添加了一些处理列表两端元素的方法,使用这些新方法,您就可以轻松的把 LinkedList 当作一个堆栈、队列或其它面向端点的数据结构。

我们再来看另外一个使用LinkedList 来实现一个简单的队列的例子

import java.util.*;

 

public class ListExample {

  public static void main(String args[]) {

    LinkedList queue = new LinkedList();

    queue.addFirst("Bernadine");

    queue.addFirst("Elizabeth");

    queue.addFirst("Gene");

   queue.addFirst("Elizabeth");        

    queue.addFirst("Clara");

    System.out.println(queue);

    queue.removeLast();

    queue.removeLast();

    System.out.println(queue);

  }

}

运行程序产生了以下输出。请注意,Set不同的是List允许重复。

[Clara, Elizabeth, Gene,Elizabeth, Bernadine]

[Clara, Elizabeth, Gene]

ListIterator 接口

ListIterator接口继承Iterator接口以支持添加或更改底层集合中的元素,还支持双向访问

我们看一个List的例子:

import java.util.*;

 

public class ListIteratorTest {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add("aaa");

      list.add("bbb");

      list.add("ccc");

      list.add("ddd");  

      System.out.println("下标0开始:"+list.listIterator(0).next());//next()

      System.out.println("下标1开始:"+list.listIterator(1).next());

      System.out.println("List 1-3:"+list.subList(1,3));//子列表

      ListIterator it =list.listIterator();//默认从下标0开始

      //隐式光标属性add操作 ,插入到当前的下标的前面

      it.add("sss");

      while(it.hasNext()){

         System.out.println("next Index="+it.nextIndex()+",Object="+it.next());

      }    

      //set属性

      ListIterator it1 =list.listIterator();

      it1.next();

      it1.set("ooo");

      ListIterator it2 =list.listIterator(list.size());//下标

      while(it2.hasPrevious()){

         System.out.println("previous Index="+it2.previousIndex()+",Object="+it2.previous());

      }

   }

}

程序的执行结果为:

下标0开始:aaa

下标1开始:bbb

子List 1-3:[bbb, ccc]

next Index=1,Object=aaa

next Index=2,Object=bbb

next Index=3,Object=ccc

next Index=4,Object=ddd

previous Index=4,Object=ddd

previous Index=3,Object=ccc

previous Index=2,Object=bbb

previous Index=1,Object=aaa

previous Index=0,Object=ooo

1.4        Map

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

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

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

u       Object put(Object key,Object value):用来存放一个键-值对Map

u       Object remove(Object key):根据key(),移除一个键-值对,并将值返回

u       voidputAll(Map mapping) :将另外一个Map中的元素存入当前的Map

u       void clear() :清空当前Map中的元素

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

u       Object get(Object key) :根据key()取得对应的值

u       boolean containsKey(Object key) :判断Map中是否存在某键(key

u       boolean containsValue(Object value):判断Map中是否存在某值(value)

u       int size():返回Map 键-值对的个数

u       boolean isEmpty() :判断当前Map是否为空

最后一组方法允许您把键或值的组作为集合来处理。

u       publicSet keySet() :返回所有的键(key),并使用Set容器存放

u       public Collection values() :返回所有的值(Value),并使用Collection存放

u       publicSet entrySet() 返回一个实现Map.Entry接口的元素Set

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

下面我们看一个简单的例子:

import java.util.*; 

public class MapTest {

public static void main(String[] args) {

      Map map1 = new HashMap();

      Map map2 = new HashMap();

      map1.put("1","aaa1");

      map1.put("2","bbb2");

      map2.put("10","aaaa10");

      map2.put("11","bbbb11");

//根据键 "1" 取得值:"aaa1"

      System.out.println("map1.get(\"1\")="+map1.get("1"));

// 根据键 "1" 移除键值对"1"-"aaa1"

      System.out.println("map1.remove(\"1\")="+map1.remove("1"));

      System.out.println("map1.get(\"1\")="+map1.get("1"));

      map1.putAll(map2);//map2全部元素放入map1

      map2.clear();//清空map2

      System.out.println("map1 IsEmpty?="+map1.isEmpty());

      System.out.println("map2 IsEmpty?="+map2.isEmpty());

      System.out.println("map1 中的键值对的个数size = "+map1.size());

      System.out.println("KeySet="+map1.keySet());//set

      System.out.println("values="+map1.values());//Collection

      System.out.println("entrySet="+map1.entrySet());    

      System.out.println("map1 是否包含键:11 = "+map1.containsKey("11"));  

      System.out.println("map1 是否包含值:aaa1 = "+map1.containsValue("aaa1"));

   }

}

运行输出结果为:

map1.get("1")=aaa1

map1.remove("1")=aaa1

map1.get("1")=null

map1 IsEmpty?=false

map2 IsEmpty?=true

map1 中的键值对的个数size = 3

KeySet=[10, 2, 11]

values=[aaaa10, bbb2, bbbb11]

entrySet=[10=aaaa10, 2=bbb2, 11=bbbb11]

map1 是否包含键:11 = true

map1 是否包含值:aaa1 = false

在该例子中,我们创建一个HashMap,并使用了一下Map接口中的各个方法。

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

我们再看看排序的Map是如何使用:

import java.util.*;

 

public class MapSortExample {

  public static void main(String args[]) {

    Map map1 = new HashMap();

    Map map2 = new LinkedHashMap();

    for(int i=0;i<10;i++){

      double s=Math.random()*100;//产生一个随机数,并将其放入Map

       map1.put(new Integer((int) s)," "+i+" 个放入的元素:"+s+"\n");

       map2.put(new Integer((int) s)," "+i+" 个放入的元素:"+s+"\n");

    }

   

    System.out.println("未排序前HashMap"+map1);

    System.out.println("未排序前LinkedHashMap"+map2);

    //使用TreeMap来对另外的Map进行重构和排序

    Map sortedMap = new TreeMap(map1);

    System.out.println("排序后:"+sortedMap);

    System.out.println("排序后:"+new TreeMap(map2));

  }

}

该程序的一次运行结果为:

未排序前HashMap:{64=第 1 个放入的元素:64.05341725531845

, 15=第 9 个放入的元素:15.249165766266382

, 2=第 4 个放入的元素:2.66794706854534

, 77=第 0 个放入的元素:77.28814965781416

, 97=第 5 个放入的元素:97.32893518378948

, 99=第 2 个放入的元素:99.99412014935982

, 60=第 8 个放入的元素:60.91451419025399

, 6=第 3 个放入的元素:6.286974058646977

, 1=第 7 个放入的元素:1.8261658496439903

, 48=第 6 个放入的元素:48.736039522423106

}

未排序前LinkedHashMap:{77=第 0 个放入的元素:77.28814965781416

, 64=第 1 个放入的元素:64.05341725531845

, 99=第 2 个放入的元素:99.99412014935982

, 6=第 3 个放入的元素:6.286974058646977

, 2=第 4 个放入的元素:2.66794706854534

, 97=第 5 个放入的元素:97.32893518378948

, 48=第 6 个放入的元素:48.736039522423106

, 1=第 7 个放入的元素:1.8261658496439903

, 60=第 8 个放入的元素:60.91451419025399

, 15=第 9 个放入的元素:15.249165766266382

}

排序后:{1=第 7 个放入的元素:1.8261658496439903

, 2=第 4 个放入的元素:2.66794706854534

, 6=第 3 个放入的元素:6.286974058646977

, 15=第 9 个放入的元素:15.249165766266382

, 48=第 6 个放入的元素:48.736039522423106

, 60=第 8 个放入的元素:60.91451419025399

, 64=第 1 个放入的元素:64.05341725531845

, 77=第 0 个放入的元素:77.28814965781416

, 97=第 5 个放入的元素:97.32893518378948

, 99=第 2 个放入的元素:99.99412014935982

}

排序后:{1=第 7 个放入的元素:1.8261658496439903

, 2=第 4 个放入的元素:2.66794706854534

, 6=第 3 个放入的元素:6.286974058646977

, 15=第 9 个放入的元素:15.249165766266382

, 48=第 6 个放入的元素:48.736039522423106

, 60=第 8 个放入的元素:60.91451419025399

, 64=第 1 个放入的元素:64.05341725531845

, 77=第 0 个放入的元素:77.28814965781416

, 97=第 5 个放入的元素:97.32893518378948

, 99=第 2 个放入的元素:99.99412014935982

}

从运行结果,我们可以看出,HashMap的存入顺序和输出顺序无关。而LinkedHashMap 则保留了键值对的存入顺序。TreeMap则是对Map中的元素进行排序。在实际的使用中我们也经常这样做:使用HashMap或者LinkedHashMap 来存放元素,当所有的元素都存放完成后,如果使用则是需要一个经过排序的Map的话,我们再使用TreeMap来重构原来的Map对象。这样做的好处是:因为HashMap和LinkedHashMap 存储数据的速度比直接使用TreeMap 要快,存取效率要高。当完成了所有的元素的存放后,我们再对整个的Map中的元素进行排序。这样可以提高整个程序的运行的效率,缩短执行时间。

这里需要注意的是,TreeMap中是根据键(Key)进行排序的。而如果我们要使用TreeMap来进行正常的排序的话,Key 中存放的对象必须实现Comparable 接口。

我们再回到Map中来,Java提高的API中除了上面介绍的几种Map比较常用以为还有一些Map,大家可以了解一下:

u   WeakHashMap: WeakHashMap 是 Map 的一个特殊实现,它只用于存储对键的弱引用。当映射的某个键在 WeakHashMap 的外部不再被引用时,就允许垃圾收集器收集映射中相应的键值对。使用 WeakHashMap 有益于保持类似注册表的数据结构,其中条目的键不再能被任何线程访问时,此条目就没用了。

u   IdentifyHashMap: Map的一种特性实现,关键属性的hash码不是由hashCode()方法计算,而是由System.identityHashCode 方法计算,使用==进行比较而不是equals()方法。

通过简单的对与Map中各个常用实现类的使用,为了更好的理解Map,下面我们再来了解一下Map的实现原理。

1.4.4         实现原理

   下面我们以HashMap为例,对Map的实现机制作一下更加深入一点的理解。Hash,一般翻译做“散列”,也有直接音译为"哈希"的,我们建立一个HashTable(哈希表),该表的长度为N,然后我们分别在该表中的格子中存放不同的元素。每个格子下面存放的元素又是以链表的方式存放元素。哈希码就是将对象的信息经过一些转变形成一个独一无二的int值,这个值存储在一个array中。我们都知道所有存储结构中,array查找速度是最快的。所以,可以加速查找。发生冲突时,让array指向多个values。即,数组每个位置上又生成一个梿表。
使用keySet()抽取key序列,将map中的所有keys生成一个Set
使用values()抽取value序列,将map中的所有values生成一个Collection

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

 

u      当添加一个新的元素Entry的时候,首先我们通过一个Hash函数计算出这个Entry元素的Hash值hashcode。通过该hashcode值,就可以直接定位出我们应该把这个Entry元素存入到Hash表的哪个格子中,如果该格子中已经存在元素了,那么只要把新的Entry元存放到这个链表中即可。

u      如果要查找一个元素Entry的时候,也同样的方式,通过Hash函数计算出这个Entry元素的Hash值hashcode。然后通过该hashcode值,就可以直接找到这个Entry是存放到哪个格子中的。接下来就对该格子存放的链表元素进行逐个的比较查找就可以了。

 举一个比较简单的例子来说明这个算法的运算方式:

假定我们有一个长度为8的Hash表(可以理解为一个长度为8的数组)。在这个Hash表中存放数字:如下表

0

1

2

3

4

5

6

7

假定我们的Hash函数为:

Hashcode = X%8   -------- 对8 取余数

其中X就是我们需要放入Hash表中的数字,而这个函数返回的Hashcode就是Hash码。

假定我们有下面10个数字需要依次存入到这个Hash表中:

11 , 23 , 44 , 9 , 6 , 32 , 12 , 45 , 57 , 89

通过上面的Hash函数,我们可以得到分别对应的Hash码:

11――3 ; 23――7 ;44――4 ;9――1;6――6;32――0;12――4;45――5;57――1;89――1;

计算出来的Hash码分别代表,该数字应该存放到Hash表中的哪个对应数字的格子中。如果改格子中已经有数字存在了,那么就以链表的方式将数字依次存放在该格子中,如下表:

0

1

2

3

4

5

6

7

32

9

 

11

44

45

6

23

 

57

 

 

12

 

 

 

 

89

 

 

 

 

 

 

Hash表和Hash算法的特点就是它的存取速度比数组差一些,但是比起单纯的链表,在查找和存储方面却要好很多。

1.4.5       重写hashCode()

我们看一个简单的例子,就能更加清楚的理解上面的意思。假定我们写了一个类:Person (人),我们判断一个对象“人”是否指向同一个人,只要知道这个人的身份证号一直就可以了。

先看我们没有实现hashCode的情况:

import java.util.*;

//身份证类

class Code{

   final int id;//身份证号码已经确认,不能改变

   Code(int i){

      id=i;

   }

   //身份号号码相同,则身份证相同

   public boolean equals(Object anObject) {

      if (anObject instanceof Code){

         Code other=(Code) anObject;

         return this.id==other.id;

      }

      return false;

   }

   public String toString() {

      return "身份证:"+id;

   } 

}

//人员信息类

class Person {

   Code id;// 身份证

   String name;// 姓名

   public Person(String name, Code id) {

      this.id=id;

      this.name=name;

   }

   //如果身份证号相同,就表示两个人是同一个人

   public boolean equals(Object anObject) {

      if (anObject instanceof Person){

         Person other=(Person)anObject;

         return this.id.equals(other.id);

      }

      return false;

   }

   public String toString() {

      return "姓名:"+name+" 身份证:"+id.id+"\n";

   }

}

public class HashCodeEx {

   public static void main(String[] args) {

      HashMap map=new HashMap();

      Person p1=new Person("张三",new Code(123));

      map.put(p1.id,p1);//我们根据身份证来作为key值存放到Map

      Person p2=new Person("李四",new Code(456));

      map.put(p2.id,p2);

      Person p3=new Person("王二",new Code(789));

      map.put(p3.id,p3);

      System.out.println("HashMap 中存放的人员信息:\n"+map);

      // 张三改名为:张山但是还是同一个人。

      Person p4=new Person("张山",new Code(123));

      map.put(p4.id,p4);

      System.out.println("张三改名后 HashMap 中存放的人员信息:\n"+map);

      //查找身份证为:123 的人员信息

      System.out.println("查找身份证为:123 的人员信息:"+map.get(new Code(123)));

   }

}

运行结果为:

HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:123=姓名:张三身份证:123

, 身份证:789=姓名:王二 身份证:789

}

张三改名后 HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:123=姓名:张三身份证:123

, 身份证:123=姓名:张山身份证:123

, 身份证:789=姓名:王二 身份证:789

}

查找身份证为:123 的人员信息:null

上面的例子的演示的是,我们在一个HashMap中存放了一些人员的信息。并以这些人员的身份证最为人员的“键”。当有的人员的姓名修改了的情况下,我们需要更新这个HashMap。同时假如我们知道某个身份证号,想了解这个身份证号对应的人员信息如何,我们也可以根据这个身份证号在HashMap中得到对应的信息。

而例子的输出结果表示,我们所做的更新和查找操作都失败了。失败的原因就是我们的身份证类:Code 没有覆写hashCode()方法。这个时候,当查找一样的身份证号码的键值对的时候,使用的是默认的对象的内存地址来进行定位。这样,后面的所有的身份证号对象new Code(123) 产生的hashCode()值都是不一样的。所以导致操作失败。

下面,我们给Code类加上hashCode()方法,然后再运行一下程序看看:

//身份证类

class Code{

   final int id;//身份证号码已经确认,不能改变

   Code(int i){

      id=i;

   }

   //身份号号码相同,则身份证相同

   public boolean equals(Object anObject) {

      if (anObject instanceof Code){

         Code other=(Code) anObject;

         return this.id==other.id;

      }

      return false;

   }

   public String toString() {

      return "身份证:"+id;

   } 

   //覆写hashCode方法,并使用身份证号作为hash

   public int hashCode(){

      return id;

   }

}

再次执行上面的HashCodeEx 的结果就为:

HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:789=姓名:王二 身份证:789

, 身份证:123=姓名:张三身份证:123

}

张三改名后 HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:789=姓名:王二 身份证:789

, 身份证:123=姓名:张山身份证:123

}

查找身份证为:123 的人员信息:姓名:张山身份证:123

这个时候,我们发现。我们想要做的更新和查找操作都成功了。

对于Map部分的使用和实现,主要就是需要注意存放“键值对”中的对象的equals()方法和hashCode()方法的覆写。如果需要使用到排序的话,那么还需要实现Comparable 接口中的compareTo()方法。我们需要注意Map中的“键”是不能重复的,而是否重复的判断,是通过调用“键”对象的equals()方法来决定的。而在HashMap中查找和存取“键值对”是同时使用hashCode()方法和equals()方法来决定的。

1.5       Set

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

我们简单的描述一下各个方法的作用:

u     public int size() :返回set中元素的数目,如果set包含的元素数大于Integer.MAX_VALUE,返回Integer.MAX_VALUE

u     public boolean isEmpty() :如果set中不含元素,返回true

u     public booleancontains(Object o) :如果set包含指定元素,返回true

u     public Iterator iterator()

l         返回set中元素的迭代器

l         元素返回没有特定的顺序,除非set是提高了该保证的某些类的实例

u     public Object[] toArray() :返回包含set中所有元素的数组

u     public Object[] toArray(Object[] a):返回包含set中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型

u     public boolean add(Object o) :如果set中不存在指定元素,则向set加入

u     public boolean remove(Object o) :如果set中存在指定元素,则从set中删除

u     public booleanremoveAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素

u     public booleancontainsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一个set,只有是当前set的子集时,方法返回true

u     public booleanaddAll(Collection c) :如果set中中不存在指定集合的元素,则向set中加入所有元素

u     public booleanretainAll(Collection c) :只保留set中所含的指定集合的元素(可选操作)。换言之,从set中删除所有指定集合不包含的元素。 如果指定集合也是一个set,那么该操作修改set的效果是使它的值为两个set的交集

u     public booleanremoveAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素

u     public void clear() :从set中删除所有元素

“集合框架” 支持 Set 接口两种普通的实现:HashSet 和 TreeSet以及LinkedHashSet。下表中是Set的常用实现类的描述:

 

 

在更多情况下,您会使用 HashSet 存储重复自由的集合。同时HashSet中也是采用了Hash算法的方式进行存取对象元素的。所以添加到 HashSet 的对象对应的类也需要采用恰当方式来实现 hashCode() 方法。虽然大多数系统类覆盖了 Object 中缺省的 hashCode() 实现,但创建您自己的要添加到 HashSet 的类时,别忘了覆盖 hashCode()。

对于Set的使用,我们先以一个简单的例子来说明:

import java.util.*;

public class HashSetDemo {

   public static void main(String[] args) {

      Set set1 = new HashSet();

      if (set1.add("a")) {//添加成功

         System.out.println("1 add true");

      }

      if (set1.add("a")) {//添加失败

         System.out.println("2 add true");

      }    

      set1.add("000");//添加对象到Set集合中

      set1.add("111");

      set1.add("222");

      System.out.println("集合set1的大小:"+set1.size());

      System.out.println("集合set1的内容:"+set1);

      set1.remove("000");//从集合set1中移除掉 "000" 这个对象

      System.out.println("集合set1移除 000 后的内容:"+set1);

      System.out.println("集合set1中是否包含000 "+set1.contains("000"));

      System.out.println("集合set1中是否包含111 "+set1.contains("111"));

      Set set2=new HashSet();

      set2.add("111");

      set2.addAll(set1);//set1 集合中的元素全部都加到set2

      System.out.println("集合set2的内容:"+set2);

      set2.clear();//清空集合 set1 中的元素

      System.out.println("集合set2是否为空"+set2.isEmpty());

      Iterator iterator =set1.iterator();//得到一个迭代器

      while (iterator.hasNext()) {//遍历

         Object element =iterator.next();

         System.out.println("iterator = " + element);

      }

      //将集合set1转化为数组

      Object s[]= set1.toArray();

      for(int i=0;i<s.length;i++){

         System.out.println(s[i]);

      }

   }

}

程序执行的结果为:

1 add true

集合set1的大小:4

集合set1的内容:[222, a, 000, 111]

集合set1移除 000 后的内容:[222, a, 111]

集合set1中是否包含000 :false

集合set1中是否包含111 :true

集合set2的内容:[222, a, 111]

集合set2是否为空 :true

iterator = 222

iterator = a

iterator = 111

222

a

111

从上面的这个简单的例子中,我们可以发现,Set中的方法与直接使用Collection中的方法一样。唯一需要注意的就是Set中存放的元素不能重复。

   我们再看一个例子,来了解一下其它的Set的实现类的特性:

import java.util.*;

public class SetSortExample {

  public static void main(String args[]) {

    Set set1 = new HashSet();

    Set set2 = new LinkedHashSet();

    for(int i=0;i<5;i++){

      //产生一个随机数,并将其放入Set

      int s=(int) (Math.random()*100);

       set1.add(new Integer( s));

       set2.add(new Integer( s));

       System.out.println(" "+i+" 次随机数产生为:"+s);

    }

    System.out.println("未排序前HashSet"+set1);

    System.out.println("未排序前LinkedHashSet"+set2);

    //使用TreeSet来对另外的Set进行重构和排序

    Set sortedSet = new TreeSet(set1);

    System.out.println("排序后 TreeSet "+sortedSet);

  }

}

该程序的一次执行结果为:

第 0 次随机数产生为:96

第 1 次随机数产生为:64

第 2 次随机数产生为:14

第 3 次随机数产生为:95

第 4 次随机数产生为:57

未排序前HashSet:[64, 96, 95, 57, 14]

未排序前LinkedHashSet:[96, 64, 14, 95, 57]

排序后 TreeSet :[14, 57, 64, 95, 96]

从这个例子中,我们可以知道HashSet的元素存放顺序和我们添加进去时候的顺序没有任何关系,而LinkedHashSet 则保持元素的添加顺序。TreeSet则是对我们的Set中的元素进行排序存放。

一般来说,当您要从集合中以有序的方式抽取元素时,TreeSet 实现就会有用处。为了能顺利进行,添加到 TreeSet 的元素必须是可排序的。 而您同样需要对添加到TreeSet中的类对象实现 Comparable 接口的支持。对于Comparable接口的实现,在前一小节的Map中已经简单的介绍了一下。我们暂且假定一棵树知道如何保持 java.lang 包装程序器类元素的有序状态。一般说来,先把元素添加到 HashSet,再把集合转换为 TreeSet 来进行有序遍历会更快。这点和HashMap的使用非常的类似。

1.6       总结:集合框架中常用类比较

1、数组
效率高,但容量固定且无法动态改变。
array
还有一个缺点是,无法判断其中实际存有多少元素,length只是告诉我们array的容量。

2Java中有一个Arrays类,专门用来操作arrayArrays中拥有一组static函数,
equals()
:比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。
fill()
:将值填入array中。
sort()
:用来对array进行排序。
binarySearch()
:在排好序的array中寻找元素。
System.arraycopy()
array的复制。

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



2.List的功能方法



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

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


在各种Lists中,最好的做法是以ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList()Vector此类是实现同步的,总是比ArrayList慢,所以要尽量避免使用。

在各种Sets中,HashSet通常优于HashTree(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet

 

    HashMap (除了不同步和允许使用 null /值之外, Hashtable 大致相同.)
   Hashtable
此类是实现同步的,不允许使用 null 键值

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

 

Collections是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。
相当于对Array进行类似操作的类——Arrays
如,Collections.max(Collectioncoll); coll中最大的元素。
   Collections.sort(List list);
list中元素排序

 

集合类

(Collection, List, Set, Map)

u     Collection – 对象之间没有指定的顺序,允许重复元素。

u     Set – 对象之间没有指定的顺序,不允许重复元素

u     List– 对象之间有指定的顺序,允许重复元素,并引入位置下标。

u     Map – 接口用于保存关键字(Key)和数值(Value)的集合,集合中的每个对象加入时都提供数值和关键字。Map 接口既不继承 Set 也不继承 Collection。

 

List、Set、Map共同的实现基础是Object数组

Collection

├List

│├LinkedList

│├ArrayList

│└Vector

│ └Stack

└Set

Map

├Hashtable

├HashMap

└WeakHashMap

除了四个历史集合类外,还引入了六个集合实现,如下表所示。

接口

实现

历史集合类

Set

HashSet

 

 

TreeSet

 

List

ArrayList

Vector

 

LinkedList

Stack

Map

HashMap

Hashtable

 

TreeMap

Properties

ListSetMap将持有对象一律视为Object型别。
Collection
ListSetMap都是接口,不能实例化。

1.2        Collection

集合必须只有对象,集合中的元素不能是基本数据类型。

Collection接口支持如添加和除去等基本操作。设法除去一个元素时,如果这个元素存在,除去的仅仅是集合中此元素的一个实例。

u    booleanadd(Object element)

u    booleanremove(Object element)

Collection接口还支持查询操作:

u    int size()

u    booleanisEmpty()

u    booleancontains(Object element)

u    Iteratoriterator()

组操作 :Collection 接口支持的其它操作,要么是作用于元素组的任务,要么是同时作用于整个集合的任务。

u     boolean containsAll(Collection collection)

u     boolean addAll(Collection collection)

u     void clear()

u     void removeAll(Collection collection)

u     void retainAll(Collection collection)

containsAll() 方法允许您查找当前集合是否包含了另一个集合的所有元素,即另一个集合是否是当前集合的子集。其余方法是可选的,因为特定的集合可能不支持集合更改。 addAll() 方法确保另一个集合中的所有元素都被添加到当前的集合中,通常称为并。 clear() 方法从当前集合中除去所有元素。 removeAll() 方法类似于 clear() ,但只除去了元素的一个子集。 retainAll() 方法类似于 removeAll() 方法,不过可能感到它所做的与前面正好相反:它从当前集合中除去不属于另一个集合的元素,即交。

 

我们看一个简单的例子,来了解一下集合类的基本方法的使用:

import java.util.*;

public class CollectionToArray {

       public static voidmain(String[] args) {            

              Collection collection1=newArrayList();//创建一个集合对象

              collection1.add("000");//添加对象到Collection集合中

              collection1.add("111");

              collection1.add("222");

              System.out.println("集合collection1的大小:"+collection1.size());

              System.out.println("集合collection1的内容:"+collection1);

              collection1.remove("000");//从集合collection1中移除掉 "000" 这个对象

              System.out.println("集合collection1移除 000 后的内容:"+collection1);

              System.out.println("集合collection1中是否包含000 :"+collection1.contains("000"));

              System.out.println("集合collection1中是否包含111 :"+collection1.contains("111"));

              Collection collection2=newArrayList();

              collection2.addAll(collection1);//将collection1 集合中的元素全部都加到collection2中

              System.out.println("集合collection2的内容:"+collection2);

              collection2.clear();//清空集合 collection1 中的元素

              System.out.println("集合collection2是否为空 :"+collection2.isEmpty());

              //将集合collection1转化为数组

              Object s[]= collection1.toArray();

              for(inti=0;i<s.length;i++){

                     System.out.println(s[i]);

              }

       }

}

运行结果为:

集合collection1的大小:3

集合collection1的内容:[000, 111, 222]

集合collection1移除 000 后的内容:[111,222]

集合collection1中是否包含000 :false

集合collection1中是否包含111 :true

集合collection2的内容:[111, 222]

集合collection2是否为空 :true

111

222

1.2.2         迭代器

   迭代器(Iterator)的概念,也是出于一种设计模式就是为达成此目的而形成的。所以Collection不提供get()方法。如果要遍历Collectin中的元素,就必须用Iterator。

下面,我们看一个对于迭代器的简单使用:

 

import java.util.ArrayList;

import java.util.Collection;

import java.util.Iterator;

 

public class IteratorDemo {

   public static void main(String[] args) {

      Collection collection = new ArrayList();

      collection.add("s1");

      collection.add("s2");

      collection.add("s3");

      Iterator iterator =collection.iterator();//得到一个迭代器

      while (iterator.hasNext()) {//遍历

         Object element =iterator.next();

         System.out.println("iterator = " + element);

      }

      if(collection.isEmpty())

         System.out.println("collection is Empty!");

      else

         System.out.println("collection is not Empty! size="+collection.size());

      Iterator iterator2 =collection.iterator();

      while (iterator2.hasNext()) {//移除元素

         Object element = iterator2.next();

         System.out.println("remove: "+element);

         iterator2.remove();

      }    

      Iterator iterator3 =collection.iterator();

      if (!iterator3.hasNext()){//察看是否还有元素

         System.out.println("没有元素");

      } 

      if(collection.isEmpty())

         System.out.println("collection is Empty!");

      //使用collection.isEmpty()方法来判断

   }

}

程序的运行结果为:

iterator = s1

iterator = s2

iterator = s3

collection is not Empty! size=3

remove: s1

remove: s2

remove: s3

没有元素

collection is Empty!

可以看到,Java的Collection的Iterator 能够用来,:

1)      使用方法 iterator() 要求容器返回一个Iterator .第一次调用Iterator 的next() 方法时,它返回集合序列的第一个元素。

2)      使用next() 获得集合序列的中的下一个元素。

3)      使用hasNext()检查序列中是否元素。

4)      使用remove()将迭代器新返回的元素删除。

需要注意的是:方法删除由next方法返回的最后一个元素,在每次调用next时,remove方法只能被调用一次 。

大家看,Java 实现的这个迭代器的使用就是如此的简单。Iterator(跌代器)虽然功能简单,但仍然可以帮助我们解决许多问题,同时针对List 还有一个更复杂更高级的ListIterator

1.3        List

List就是列表的意思,它是Collection 的一种,继承了 Collection 接口,以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。List是按对象的进入顺序进行保存对象,而不做排序或编辑操作。它除了拥有Collection接口的所有的方法外还拥有一些其他的方法。

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

u      void add(intindex, Object element) :添加对象element到位置index上

u      booleanaddAll(int index, Collection collection) :在index位置后添加容器collection中所有的元素

u      Object get(intindex) :取出下标为index的位置的元素

u      intindexOf(Object element) :查找对象element 在List中第一次出现的位置

u      intlastIndexOf(Object element) :查找对象element 在List中最后出现的位置

u      Objectremove(int index) :删除index位置上的元素

u      Object set(intindex, Object element) :将index位置上的对象替换为element 并返回老的元素。

先看一下下面表格:

 

在“集合框架”中有两种常规的List实现:ArrayList和LinkedList。使用两种 List实现的哪一种取决于您特定的需要。如果要支持随机访问,而不必在除尾部的任何位置插入或除去元素,那么,ArrayList提供了可选的集合。但如果,您要频繁的从列表的中间位置添加和除去元素,而只要顺序的访问列表元素,那么,LinkedList实现更好。

而LinkedList添加了一些处理列表两端元素的方法,使用这些新方法,您就可以轻松的把 LinkedList 当作一个堆栈、队列或其它面向端点的数据结构。

我们再来看另外一个使用LinkedList 来实现一个简单的队列的例子

import java.util.*;

 

public class ListExample {

  public static void main(String args[]) {

    LinkedList queue = new LinkedList();

    queue.addFirst("Bernadine");

    queue.addFirst("Elizabeth");

    queue.addFirst("Gene");

   queue.addFirst("Elizabeth");        

    queue.addFirst("Clara");

    System.out.println(queue);

    queue.removeLast();

    queue.removeLast();

    System.out.println(queue);

  }

}

运行程序产生了以下输出。请注意,Set不同的是List允许重复。

[Clara, Elizabeth, Gene,Elizabeth, Bernadine]

[Clara, Elizabeth, Gene]

ListIterator 接口

ListIterator接口继承Iterator接口以支持添加或更改底层集合中的元素,还支持双向访问

我们看一个List的例子:

import java.util.*;

 

public class ListIteratorTest {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add("aaa");

      list.add("bbb");

      list.add("ccc");

      list.add("ddd");  

      System.out.println("下标0开始:"+list.listIterator(0).next());//next()

      System.out.println("下标1开始:"+list.listIterator(1).next());

      System.out.println("List 1-3:"+list.subList(1,3));//子列表

      ListIterator it =list.listIterator();//默认从下标0开始

      //隐式光标属性add操作 ,插入到当前的下标的前面

      it.add("sss");

      while(it.hasNext()){

         System.out.println("next Index="+it.nextIndex()+",Object="+it.next());

      }    

      //set属性

      ListIterator it1 =list.listIterator();

      it1.next();

      it1.set("ooo");

      ListIterator it2 =list.listIterator(list.size());//下标

      while(it2.hasPrevious()){

         System.out.println("previous Index="+it2.previousIndex()+",Object="+it2.previous());

      }

   }

}

程序的执行结果为:

下标0开始:aaa

下标1开始:bbb

子List 1-3:[bbb, ccc]

next Index=1,Object=aaa

next Index=2,Object=bbb

next Index=3,Object=ccc

next Index=4,Object=ddd

previous Index=4,Object=ddd

previous Index=3,Object=ccc

previous Index=2,Object=bbb

previous Index=1,Object=aaa

previous Index=0,Object=ooo

1.4        Map

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

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

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

u       Object put(Object key,Object value):用来存放一个键-值对Map

u       Object remove(Object key):根据key(),移除一个键-值对,并将值返回

u       voidputAll(Map mapping) :将另外一个Map中的元素存入当前的Map

u       void clear() :清空当前Map中的元素

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

u       Object get(Object key) :根据key()取得对应的值

u       boolean containsKey(Object key) :判断Map中是否存在某键(key

u       boolean containsValue(Object value):判断Map中是否存在某值(value)

u       int size():返回Map 键-值对的个数

u       boolean isEmpty() :判断当前Map是否为空

最后一组方法允许您把键或值的组作为集合来处理。

u       publicSet keySet() :返回所有的键(key),并使用Set容器存放

u       public Collection values() :返回所有的值(Value),并使用Collection存放

u       publicSet entrySet() 返回一个实现Map.Entry接口的元素Set

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

下面我们看一个简单的例子:

import java.util.*; 

public class MapTest {

public static void main(String[] args) {

      Map map1 = new HashMap();

      Map map2 = new HashMap();

      map1.put("1","aaa1");

      map1.put("2","bbb2");

      map2.put("10","aaaa10");

      map2.put("11","bbbb11");

//根据键 "1" 取得值:"aaa1"

      System.out.println("map1.get(\"1\")="+map1.get("1"));

// 根据键 "1" 移除键值对"1"-"aaa1"

      System.out.println("map1.remove(\"1\")="+map1.remove("1"));

      System.out.println("map1.get(\"1\")="+map1.get("1"));

      map1.putAll(map2);//map2全部元素放入map1

      map2.clear();//清空map2

      System.out.println("map1 IsEmpty?="+map1.isEmpty());

      System.out.println("map2 IsEmpty?="+map2.isEmpty());

      System.out.println("map1 中的键值对的个数size = "+map1.size());

      System.out.println("KeySet="+map1.keySet());//set

      System.out.println("values="+map1.values());//Collection

      System.out.println("entrySet="+map1.entrySet());    

      System.out.println("map1 是否包含键:11 = "+map1.containsKey("11"));  

      System.out.println("map1 是否包含值:aaa1 = "+map1.containsValue("aaa1"));

   }

}

运行输出结果为:

map1.get("1")=aaa1

map1.remove("1")=aaa1

map1.get("1")=null

map1 IsEmpty?=false

map2 IsEmpty?=true

map1 中的键值对的个数size = 3

KeySet=[10, 2, 11]

values=[aaaa10, bbb2, bbbb11]

entrySet=[10=aaaa10, 2=bbb2, 11=bbbb11]

map1 是否包含键:11 = true

map1 是否包含值:aaa1 = false

在该例子中,我们创建一个HashMap,并使用了一下Map接口中的各个方法。

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

我们再看看排序的Map是如何使用:

import java.util.*;

 

public class MapSortExample {

  public static void main(String args[]) {

    Map map1 = new HashMap();

    Map map2 = new LinkedHashMap();

    for(int i=0;i<10;i++){

      double s=Math.random()*100;//产生一个随机数,并将其放入Map

       map1.put(new Integer((int) s)," "+i+" 个放入的元素:"+s+"\n");

       map2.put(new Integer((int) s)," "+i+" 个放入的元素:"+s+"\n");

    }

   

    System.out.println("未排序前HashMap"+map1);

    System.out.println("未排序前LinkedHashMap"+map2);

    //使用TreeMap来对另外的Map进行重构和排序

    Map sortedMap = new TreeMap(map1);

    System.out.println("排序后:"+sortedMap);

    System.out.println("排序后:"+new TreeMap(map2));

  }

}

该程序的一次运行结果为:

未排序前HashMap:{64=第 1 个放入的元素:64.05341725531845

, 15=第 9 个放入的元素:15.249165766266382

, 2=第 4 个放入的元素:2.66794706854534

, 77=第 0 个放入的元素:77.28814965781416

, 97=第 5 个放入的元素:97.32893518378948

, 99=第 2 个放入的元素:99.99412014935982

, 60=第 8 个放入的元素:60.91451419025399

, 6=第 3 个放入的元素:6.286974058646977

, 1=第 7 个放入的元素:1.8261658496439903

, 48=第 6 个放入的元素:48.736039522423106

}

未排序前LinkedHashMap:{77=第 0 个放入的元素:77.28814965781416

, 64=第 1 个放入的元素:64.05341725531845

, 99=第 2 个放入的元素:99.99412014935982

, 6=第 3 个放入的元素:6.286974058646977

, 2=第 4 个放入的元素:2.66794706854534

, 97=第 5 个放入的元素:97.32893518378948

, 48=第 6 个放入的元素:48.736039522423106

, 1=第 7 个放入的元素:1.8261658496439903

, 60=第 8 个放入的元素:60.91451419025399

, 15=第 9 个放入的元素:15.249165766266382

}

排序后:{1=第 7 个放入的元素:1.8261658496439903

, 2=第 4 个放入的元素:2.66794706854534

, 6=第 3 个放入的元素:6.286974058646977

, 15=第 9 个放入的元素:15.249165766266382

, 48=第 6 个放入的元素:48.736039522423106

, 60=第 8 个放入的元素:60.91451419025399

, 64=第 1 个放入的元素:64.05341725531845

, 77=第 0 个放入的元素:77.28814965781416

, 97=第 5 个放入的元素:97.32893518378948

, 99=第 2 个放入的元素:99.99412014935982

}

排序后:{1=第 7 个放入的元素:1.8261658496439903

, 2=第 4 个放入的元素:2.66794706854534

, 6=第 3 个放入的元素:6.286974058646977

, 15=第 9 个放入的元素:15.249165766266382

, 48=第 6 个放入的元素:48.736039522423106

, 60=第 8 个放入的元素:60.91451419025399

, 64=第 1 个放入的元素:64.05341725531845

, 77=第 0 个放入的元素:77.28814965781416

, 97=第 5 个放入的元素:97.32893518378948

, 99=第 2 个放入的元素:99.99412014935982

}

从运行结果,我们可以看出,HashMap的存入顺序和输出顺序无关。而LinkedHashMap 则保留了键值对的存入顺序。TreeMap则是对Map中的元素进行排序。在实际的使用中我们也经常这样做:使用HashMap或者LinkedHashMap 来存放元素,当所有的元素都存放完成后,如果使用则是需要一个经过排序的Map的话,我们再使用TreeMap来重构原来的Map对象。这样做的好处是:因为HashMap和LinkedHashMap 存储数据的速度比直接使用TreeMap 要快,存取效率要高。当完成了所有的元素的存放后,我们再对整个的Map中的元素进行排序。这样可以提高整个程序的运行的效率,缩短执行时间。

这里需要注意的是,TreeMap中是根据键(Key)进行排序的。而如果我们要使用TreeMap来进行正常的排序的话,Key 中存放的对象必须实现Comparable 接口。

我们再回到Map中来,Java提高的API中除了上面介绍的几种Map比较常用以为还有一些Map,大家可以了解一下:

u   WeakHashMap: WeakHashMap 是 Map 的一个特殊实现,它只用于存储对键的弱引用。当映射的某个键在 WeakHashMap 的外部不再被引用时,就允许垃圾收集器收集映射中相应的键值对。使用 WeakHashMap 有益于保持类似注册表的数据结构,其中条目的键不再能被任何线程访问时,此条目就没用了。

u   IdentifyHashMap: Map的一种特性实现,关键属性的hash码不是由hashCode()方法计算,而是由System.identityHashCode 方法计算,使用==进行比较而不是equals()方法。

通过简单的对与Map中各个常用实现类的使用,为了更好的理解Map,下面我们再来了解一下Map的实现原理。

1.4.4         实现原理

   下面我们以HashMap为例,对Map的实现机制作一下更加深入一点的理解。Hash,一般翻译做“散列”,也有直接音译为"哈希"的,我们建立一个HashTable(哈希表),该表的长度为N,然后我们分别在该表中的格子中存放不同的元素。每个格子下面存放的元素又是以链表的方式存放元素。哈希码就是将对象的信息经过一些转变形成一个独一无二的int值,这个值存储在一个array中。我们都知道所有存储结构中,array查找速度是最快的。所以,可以加速查找。发生冲突时,让array指向多个values。即,数组每个位置上又生成一个梿表。
使用keySet()抽取key序列,将map中的所有keys生成一个Set
使用values()抽取value序列,将map中的所有values生成一个Collection

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

 

u      当添加一个新的元素Entry的时候,首先我们通过一个Hash函数计算出这个Entry元素的Hash值hashcode。通过该hashcode值,就可以直接定位出我们应该把这个Entry元素存入到Hash表的哪个格子中,如果该格子中已经存在元素了,那么只要把新的Entry元存放到这个链表中即可。

u      如果要查找一个元素Entry的时候,也同样的方式,通过Hash函数计算出这个Entry元素的Hash值hashcode。然后通过该hashcode值,就可以直接找到这个Entry是存放到哪个格子中的。接下来就对该格子存放的链表元素进行逐个的比较查找就可以了。

 举一个比较简单的例子来说明这个算法的运算方式:

假定我们有一个长度为8的Hash表(可以理解为一个长度为8的数组)。在这个Hash表中存放数字:如下表

0

1

2

3

4

5

6

7

假定我们的Hash函数为:

Hashcode = X%8   -------- 对8 取余数

其中X就是我们需要放入Hash表中的数字,而这个函数返回的Hashcode就是Hash码。

假定我们有下面10个数字需要依次存入到这个Hash表中:

11 , 23 , 44 , 9 , 6 , 32 , 12 , 45 , 57 , 89

通过上面的Hash函数,我们可以得到分别对应的Hash码:

11――3 ; 23――7 ;44――4 ;9――1;6――6;32――0;12――4;45――5;57――1;89――1;

计算出来的Hash码分别代表,该数字应该存放到Hash表中的哪个对应数字的格子中。如果改格子中已经有数字存在了,那么就以链表的方式将数字依次存放在该格子中,如下表:

0

1

2

3

4

5

6

7

32

9

 

11

44

45

6

23

 

57

 

 

12

 

 

 

 

89

 

 

 

 

 

 

Hash表和Hash算法的特点就是它的存取速度比数组差一些,但是比起单纯的链表,在查找和存储方面却要好很多。

1.4.5       重写hashCode()

我们看一个简单的例子,就能更加清楚的理解上面的意思。假定我们写了一个类:Person (人),我们判断一个对象“人”是否指向同一个人,只要知道这个人的身份证号一直就可以了。

先看我们没有实现hashCode的情况:

import java.util.*;

//身份证类

class Code{

   final int id;//身份证号码已经确认,不能改变

   Code(int i){

      id=i;

   }

   //身份号号码相同,则身份证相同

   public boolean equals(Object anObject) {

      if (anObject instanceof Code){

         Code other=(Code) anObject;

         return this.id==other.id;

      }

      return false;

   }

   public String toString() {

      return "身份证:"+id;

   } 

}

//人员信息类

class Person {

   Code id;// 身份证

   String name;// 姓名

   public Person(String name, Code id) {

      this.id=id;

      this.name=name;

   }

   //如果身份证号相同,就表示两个人是同一个人

   public boolean equals(Object anObject) {

      if (anObject instanceof Person){

         Person other=(Person)anObject;

         return this.id.equals(other.id);

      }

      return false;

   }

   public String toString() {

      return "姓名:"+name+" 身份证:"+id.id+"\n";

   }

}

public class HashCodeEx {

   public static void main(String[] args) {

      HashMap map=new HashMap();

      Person p1=new Person("张三",new Code(123));

      map.put(p1.id,p1);//我们根据身份证来作为key值存放到Map

      Person p2=new Person("李四",new Code(456));

      map.put(p2.id,p2);

      Person p3=new Person("王二",new Code(789));

      map.put(p3.id,p3);

      System.out.println("HashMap 中存放的人员信息:\n"+map);

      // 张三改名为:张山但是还是同一个人。

      Person p4=new Person("张山",new Code(123));

      map.put(p4.id,p4);

      System.out.println("张三改名后 HashMap 中存放的人员信息:\n"+map);

      //查找身份证为:123 的人员信息

      System.out.println("查找身份证为:123 的人员信息:"+map.get(new Code(123)));

   }

}

运行结果为:

HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:123=姓名:张三身份证:123

, 身份证:789=姓名:王二 身份证:789

}

张三改名后 HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:123=姓名:张三身份证:123

, 身份证:123=姓名:张山身份证:123

, 身份证:789=姓名:王二 身份证:789

}

查找身份证为:123 的人员信息:null

上面的例子的演示的是,我们在一个HashMap中存放了一些人员的信息。并以这些人员的身份证最为人员的“键”。当有的人员的姓名修改了的情况下,我们需要更新这个HashMap。同时假如我们知道某个身份证号,想了解这个身份证号对应的人员信息如何,我们也可以根据这个身份证号在HashMap中得到对应的信息。

而例子的输出结果表示,我们所做的更新和查找操作都失败了。失败的原因就是我们的身份证类:Code 没有覆写hashCode()方法。这个时候,当查找一样的身份证号码的键值对的时候,使用的是默认的对象的内存地址来进行定位。这样,后面的所有的身份证号对象new Code(123) 产生的hashCode()值都是不一样的。所以导致操作失败。

下面,我们给Code类加上hashCode()方法,然后再运行一下程序看看:

//身份证类

class Code{

   final int id;//身份证号码已经确认,不能改变

   Code(int i){

      id=i;

   }

   //身份号号码相同,则身份证相同

   public boolean equals(Object anObject) {

      if (anObject instanceof Code){

         Code other=(Code) anObject;

         return this.id==other.id;

      }

      return false;

   }

   public String toString() {

      return "身份证:"+id;

   } 

   //覆写hashCode方法,并使用身份证号作为hash

   public int hashCode(){

      return id;

   }

}

再次执行上面的HashCodeEx 的结果就为:

HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:789=姓名:王二 身份证:789

, 身份证:123=姓名:张三身份证:123

}

张三改名后 HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:789=姓名:王二 身份证:789

, 身份证:123=姓名:张山身份证:123

}

查找身份证为:123 的人员信息:姓名:张山身份证:123

这个时候,我们发现。我们想要做的更新和查找操作都成功了。

对于Map部分的使用和实现,主要就是需要注意存放“键值对”中的对象的equals()方法和hashCode()方法的覆写。如果需要使用到排序的话,那么还需要实现Comparable 接口中的compareTo()方法。我们需要注意Map中的“键”是不能重复的,而是否重复的判断,是通过调用“键”对象的equals()方法来决定的。而在HashMap中查找和存取“键值对”是同时使用hashCode()方法和equals()方法来决定的。

1.5       Set

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

我们简单的描述一下各个方法的作用:

u     public int size() :返回set中元素的数目,如果set包含的元素数大于Integer.MAX_VALUE,返回Integer.MAX_VALUE

u     public boolean isEmpty() :如果set中不含元素,返回true

u     public booleancontains(Object o) :如果set包含指定元素,返回true

u     public Iterator iterator()

l         返回set中元素的迭代器

l         元素返回没有特定的顺序,除非set是提高了该保证的某些类的实例

u     public Object[] toArray() :返回包含set中所有元素的数组

u     public Object[] toArray(Object[] a):返回包含set中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型

u     public boolean add(Object o) :如果set中不存在指定元素,则向set加入

u     public boolean remove(Object o) :如果set中存在指定元素,则从set中删除

u     public booleanremoveAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素

u     public booleancontainsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一个set,只有是当前set的子集时,方法返回true

u     public booleanaddAll(Collection c) :如果set中中不存在指定集合的元素,则向set中加入所有元素

u     public booleanretainAll(Collection c) :只保留set中所含的指定集合的元素(可选操作)。换言之,从set中删除所有指定集合不包含的元素。 如果指定集合也是一个set,那么该操作修改set的效果是使它的值为两个set的交集

u     public booleanremoveAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素

u     public void clear() :从set中删除所有元素

“集合框架” 支持 Set 接口两种普通的实现:HashSet 和 TreeSet以及LinkedHashSet。下表中是Set的常用实现类的描述:

 

 

在更多情况下,您会使用 HashSet 存储重复自由的集合。同时HashSet中也是采用了Hash算法的方式进行存取对象元素的。所以添加到 HashSet 的对象对应的类也需要采用恰当方式来实现 hashCode() 方法。虽然大多数系统类覆盖了 Object 中缺省的 hashCode() 实现,但创建您自己的要添加到 HashSet 的类时,别忘了覆盖 hashCode()。

对于Set的使用,我们先以一个简单的例子来说明:

import java.util.*;

public class HashSetDemo {

   public static void main(String[] args) {

      Set set1 = new HashSet();

      if (set1.add("a")) {//添加成功

         System.out.println("1 add true");

      }

      if (set1.add("a")) {//添加失败

         System.out.println("2 add true");

      }    

      set1.add("000");//添加对象到Set集合中

      set1.add("111");

      set1.add("222");

      System.out.println("集合set1的大小:"+set1.size());

      System.out.println("集合set1的内容:"+set1);

      set1.remove("000");//从集合set1中移除掉 "000" 这个对象

      System.out.println("集合set1移除 000 后的内容:"+set1);

      System.out.println("集合set1中是否包含000 "+set1.contains("000"));

      System.out.println("集合set1中是否包含111 "+set1.contains("111"));

      Set set2=new HashSet();

      set2.add("111");

      set2.addAll(set1);//set1 集合中的元素全部都加到set2

      System.out.println("集合set2的内容:"+set2);

      set2.clear();//清空集合 set1 中的元素

      System.out.println("集合set2是否为空"+set2.isEmpty());

      Iterator iterator =set1.iterator();//得到一个迭代器

      while (iterator.hasNext()) {//遍历

         Object element =iterator.next();

         System.out.println("iterator = " + element);

      }

      //将集合set1转化为数组

      Object s[]= set1.toArray();

      for(int i=0;i<s.length;i++){

         System.out.println(s[i]);

      }

   }

}

程序执行的结果为:

1 add true

集合set1的大小:4

集合set1的内容:[222, a, 000, 111]

集合set1移除 000 后的内容:[222, a, 111]

集合set1中是否包含000 :false

集合set1中是否包含111 :true

集合set2的内容:[222, a, 111]

集合set2是否为空 :true

iterator = 222

iterator = a

iterator = 111

222

a

111

从上面的这个简单的例子中,我们可以发现,Set中的方法与直接使用Collection中的方法一样。唯一需要注意的就是Set中存放的元素不能重复。

   我们再看一个例子,来了解一下其它的Set的实现类的特性:

import java.util.*;

public class SetSortExample {

  public static void main(String args[]) {

    Set set1 = new HashSet();

    Set set2 = new LinkedHashSet();

    for(int i=0;i<5;i++){

      //产生一个随机数,并将其放入Set

      int s=(int) (Math.random()*100);

       set1.add(new Integer( s));

       set2.add(new Integer( s));

       System.out.println(" "+i+" 次随机数产生为:"+s);

    }

    System.out.println("未排序前HashSet"+set1);

    System.out.println("未排序前LinkedHashSet"+set2);

    //使用TreeSet来对另外的Set进行重构和排序

    Set sortedSet = new TreeSet(set1);

    System.out.println("排序后 TreeSet "+sortedSet);

  }

}

该程序的一次执行结果为:

第 0 次随机数产生为:96

第 1 次随机数产生为:64

第 2 次随机数产生为:14

第 3 次随机数产生为:95

第 4 次随机数产生为:57

未排序前HashSet:[64, 96, 95, 57, 14]

未排序前LinkedHashSet:[96, 64, 14, 95, 57]

排序后 TreeSet :[14, 57, 64, 95, 96]

从这个例子中,我们可以知道HashSet的元素存放顺序和我们添加进去时候的顺序没有任何关系,而LinkedHashSet 则保持元素的添加顺序。TreeSet则是对我们的Set中的元素进行排序存放。

一般来说,当您要从集合中以有序的方式抽取元素时,TreeSet 实现就会有用处。为了能顺利进行,添加到 TreeSet 的元素必须是可排序的。 而您同样需要对添加到TreeSet中的类对象实现 Comparable 接口的支持。对于Comparable接口的实现,在前一小节的Map中已经简单的介绍了一下。我们暂且假定一棵树知道如何保持 java.lang 包装程序器类元素的有序状态。一般说来,先把元素添加到 HashSet,再把集合转换为 TreeSet 来进行有序遍历会更快。这点和HashMap的使用非常的类似。

1.6       总结:集合框架中常用类比较

1、数组
效率高,但容量固定且无法动态改变。
array
还有一个缺点是,无法判断其中实际存有多少元素,length只是告诉我们array的容量。

2Java中有一个Arrays类,专门用来操作arrayArrays中拥有一组static函数,
equals()
:比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。
fill()
:将值填入array中。
sort()
:用来对array进行排序。
binarySearch()
:在排好序的array中寻找元素。
System.arraycopy()
array的复制。

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



2.List的功能方法



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

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


在各种Lists中,最好的做法是以ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList()Vector此类是实现同步的,总是比ArrayList慢,所以要尽量避免使用。

在各种Sets中,HashSet通常优于HashTree(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet

 

    HashMap (除了不同步和允许使用 null /值之外, Hashtable 大致相同.)
   Hashtable
此类是实现同步的,不允许使用 null 键值

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

 

Collections是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。
相当于对Array进行类似操作的类——Arrays
如,Collections.max(Collectioncoll); coll中最大的元素。
   Collections.sort(List list);
list中元素排序

 

此文章来自百度文库。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值