zoukankan      html  css  js  c++  java
  • Java程序设计4——集合类

    1 JAVA集合概述                                                     

      Java集合封装了一系列数据结构比如链表、二叉树、栈、队列等,然后提供了针对这些数据结构的一系列算法比如查找、排序、替换,使编程难度大大降低。(这句话有可能是非法的,因为个人对算法目前不是太了解,并不了解Java有没有实现哪些数据结构。但是说在这里想给那些畏难算法与数据结构这门课程的人一丝信心,嫑以为非要懂算法和数据结构才能编程,不懂这门课程编也能编程,并不是所有的问题都要自己来实现一个数据结构。如果因为觉得编程一定要懂算法和数据结构,进而对编程产生一种心理阴影,没有信心,那是很错误的!你总需要先用一门语言来练练手,然后才能去学习算法和数据结构。"懂算法和数据结构=会编程",完全是那些所谓的专家来吓唬那些新手入门者的,找一本语言介绍书好好练练,编程并不是你想的那么难!)这样的话,只需要利用提供的数据结构存储需要用的数据,然后利用数据结构上提供的算法就可以对数据进行简单的处理。链表、二叉树、栈、队列在JAVA里面统称为集合,就是把数据统统塞进这些集合里面,这些集合的结构可能是不同的。Java集合将这些数据结构分成如下几类。

    注意: 这些集合类里面存储的数据都是指向堆内存对象的对象栈内存变量

    区别:

      Java的集合类分为四种:Set,List、Map和Queue四种体系,即MSQL设计者可以根据需要使用这四种体系里面的数据结构,然后处理数据。
      Map:代表有映射关系的集合,也就是key-value这样的数据集合,比如{001——张三,002——李四}
      Set:代表无序、不可重复的集合,比如{1,2,3}。注意:无序、不可重复
      Queue:代表一种队列集合的实现
      List:代表添加时候有序存储可重复的集合{1,1,2,3}

    1.1 Java集合继承树                                      

      在处理数据时候经常处理到元素个数不确定(数组长度是固定的,初始化后就无法改变),有关联的数据{001——张三,002——李四},为了解决这些问题,Java提供了集合类,集合类负责保存、盛装这些数据,因此集合类也被称为容器类,所有的集合类都存放在java.util包中
      集合类和数组的区别:
      数组可以保存基本数据类型或者对象,但是集合类智能保存指向堆内存数据的栈内存对象变量(不过很多书上会直接说集合类里面装的都是对象,这样可能会好理解一些)。
      MSQL接口类的继承拓扑图:
      提供的四大接口类有如下继承结构的拓扑图,Collection和Map是两个根类,由这两个根类派生出一系列的接口类。

      其中Collection派生SQL,Map派生其他的接口类。MSQL(记法M-SQL,像一种编程语言一样):Map,Set,Queue,List这四种集合类最常用的是SML(记法:AI领域的一种语言),也就是Set,Map,List,这三种接口类最常用实现是,Set的HashSet,Map的HashMap,List的ArrayList,也就是HHA(2HA,2哈——啊哈)如下图的颜色重的标注出来是常用的

    1.2 Collection和Iterator接口                       

    1.2.1 Collection 接口

      下面如果没有特别说明,SQL并不指数据库语言SQL,而是代表Set,Queue,List三个Collection的子接口类。

    Collection接口下面定义了若干方法,这些方法可以用于SQL三种集合接口类。注意:集合接口模板类里面存储的是栈对象变量
    boolean add(Object o):该方法用于像一个集合接口类添加一个对象变量o指向的对象,如果添加成功,也就是集合类被改变成功,返回true,否则返回false

    boolean addAll(Collection c):假设调用该接口的是集合接口类a,调用该接口后,会将集合接口类c的对象全部添加到集合接口类a中,元素间用逗号隔开

    void clear():清空集合里面的所有元素,使元素个数size为0

    boolean contains(Object o):检测集合里面是否含有对象元素o,有true,无false

    boolean containsAll(Collection c):假定调用该接口的是集合a,那么调用该接口后会检测集合a里面是否包含集合c的全部元素,有true,无false

    boolean isEmpty():返回集合是否为空,相当于int size()方法返回值为0和不为0的情况

    boolean remove(Object o):删除集合里面指定元素o,删除成功返回true,失败false

    boolean removeAll(Collection c):假设调用该接口的是集合a,调用该接口后,会从集合a里面找出集合c里面没有的元素,也就是找出差集,如果删除了一个或一个以上的元素,该方法返回true

    boolean retainAll(Collection c):假设调用该接口的是集合a,调用该接口后,删除a中c没有的元素,也就是把a编程a和c的交集,如果操作成功,返回true

    int size():返回集合里面元素个数

    Object[] toArray():把集合转换成一个数组,所有的集合元素变成对应的数组元素,这样就可以用数组的方法来访问元素

    Iterator iterator():返回一个Iterator对象用于遍历集合里面的所有元素,也就是一个迭代器,这个迭代器可以用于查询集合类元素,利用System.out的println查询同样可以打印出集合的所有数据,但是这样的查询是不可控制的,也就是说要么查询出所有,要么不查询,使用迭代器可以根据需要进行查询。

    1.2.2 Iterator接口

      Iterator接口隐藏了Collection集合类的底层实现,提供了若干方法对Collection集合类进行处理。
    方法:

    boolean hasNext():如果被迭代的集合仍然有元素没有被遍历(迭代),则返回true,就是说如果集合里面迭代一次后剩余的元素不止一个,则返回true。
    Object next():返回集合里下一个元素,注意迭代器每次只返回一个,不像println那样一次返回所有
    void remove():删除集合里上一次next方法返回的元素,比如说如果等于某个值,就可以删除这个元素,这样就可以控制集合返回结果
    看一段代码:
     

     1 //创建一个集合
     2   Collection books = new HashSet();
     3   books.add("轻量级J2EE企业应用实战");
     4   books.add("Struts2权威指南");
     5   books.add("基于J2EE的Ajax宝典");
     6   //获取books集合对应的迭代器
     7   Iterator it = books.iterator();
     8   while(it.hasNext()){
     9    //it.next()方法返回的数据类型是Object类型,需要强制类型转换
    10    String book = (String)it.next();//book代表的是每次返回的一个元素
    11 //   it.remove();//remove删除每次next方法返回的元素,按照此循环,如果每次返回后都进行删除,那么最后就不返回任何一个结果,因为返回一个删掉一个
    12    //如果next方法返回的元素与Struts2权威指南一样,则删除
    13 //   if(book.equals("Struts2权威指南")){
    14 // 
    15 //    it.remove();
    16 //   }
    17    //对book变量赋值,不会改变改变集合本身
    18 //   System.out.println(book);
    19    //book = "测试字符串";
    20   }
    21   System.out.println(books);

     强烈注意

      Iterator接口类仅用于遍历集合,Iterator本身并不提供盛装对象的能力,如果需要创建Iterator对象,则必须与有一个可以被它迭代的集合,没有集合的迭代器没有存在价值。也就是说Iterator必须依附于Collection对象,有一个Iterator对象,则必然有一个与之关联的Collection对象供其迭代。
    代码倒数第二行有一个book赋值代码,但是输出集合books时候,输出结果没有任何改变,可以得到一个结论:当使用Iterator对集合进行迭代输出时候,Iterator并没有指向堆内存的集合,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素没有任何改变。

    墙裂注意

    1. 删除集合里面的元素只能通过迭代器的remove方法,也就是it.remove()才可以删除集合里面的元素,通过调用集合自身的方法来删除集合元素值也就是说books.remove(book)将会出错,引发的异常是java.util.ConcurrentModificationException异常,或者简单的说,迭代过程中,不能通过除了迭代器之外的方式来修改集合,也就是说在迭代过程中,只有迭代器有修改集合的权限,其他方式包括集合本身都没有修改集合自身元素的权限。(其实更本质的是集合类变量本身只是指向堆内存的数据,迭代时候,相当于堆内存的使用权交给了迭代器,集合类本身当然没有修改权限)迭代器采用的是快速失败机制,一旦在迭代过程中,检测到该集合已经被修改(通常是其他线程修改),程序立即引发ConcurrentModificationException异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题。

    第二for循环遍历集合元素

    前面已经介绍了for循环有两种方是,一种是标准的,另一种是被很多书称之为foreach循环(这种叫法很容易让人误解循环的关键字是foreach,但其实依然是for),这里我称之为第二for循环(表示第二种for循环),同样使用在用for循环迭代输出集合类“中”也不能修改集合元素。同样使用该种循环方式按照前面的语法
    for(循环变量类型 循环变量:集合类)

    代码如下:

     1 //创建一个集合
     2 Collection lan = new HashSet();
     3 //往集合里面装东西
     4 lan.add("Englis");
     5 lan.add("Chinese");
     6 lan.add("Castellano");
     7 lan.add("Deutsch");
     8 //使用第二for循环输出集合里面的元素
     9 for(Object lang:lan){
    10 String lgu = (String)lang;//集合里面存储的都是对象变量
    11 System.out.print(lgu);
    12 }

    2 Set集合类                                                     

    Set集合特点:Set集合接口类的元素特征是无序、不重复

      Set集合与Collection基本完全一样,它没有提供任何额外的方法,实际上Set就是Collection,只是行为不同(Set不允许重复元素)。
      Set不允许重复元素,如果相同元素加入同一个Set集合中则会添加失败,判断是否相同的标准是使用equals方法,而不是==方法,所以比较严格。

    例如:

    1 Set books = new HashSet();
    2 books.add(new String("English"));
    3 //再次添加一个不同的对象
    4 books.add(new String("English"));

    由于Set是一个集合类,集合类里面应该装的是不同对象,上述代码里面明显装的是不同对象,但是Set集合判断是否是同一个对象的标准是equals方法,所以不能添加(有点怪怪的)

    上面介绍的是Set集合的通用知识,因此完全适合后面介绍的HashSet、TreeSet、EnumSet三个实现类,只是三个实现类各有特色。

    2.1 HashSet——LinkedHashSet类     

    2.1.1 HashSet接口                     

      HashSet是Set接口的典型实现,大多数时候使用Set集合就是使用这个实现类,HashSet按Hash算法存储集合中的元素,因此具有很好的存取和查找性能。
    HashSet特点
      1. 不能保证元素的排列顺序,顺序有可能发送变化
      2. HashSet不是同步的,如果多个线程同时访问一个Set集合或者一个HashSet,当2条或2条以上的线程同时修改了HashSet集合时,必须通过代码来保证同步。
      3. 集合元素值可以是null

      当向HashSet集合中存入一个元素,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值来决定该对象在HashSet中存储位置。如果有两个元素通过equals方法比较返回true,但他们的hashCode()方法返回值不相等,HashSet会把他们存储在不同位置,也就可以添加成功。
    简单的说:HashSet集合通过两个条件来判断是否存储要插入的元素。一个是通过equals判断元素值是否相等,另一个是通过hashCode()方法判断Hash值是否相等。只有两个都相等了,才说明要插入的元素相等。

     1 import java.util.*;
     2 /**
     3  * Description:
     4  * <br/>Copyright (C), 2005-2008, Yeeku.H.Lee
     5  * <br/>This program is protected by copyright laws.
     6  * <br/>Program Name:
     7  * <br/>Date:
     8  * @author  Yeeku.H.Lee kongyeeku@163.com
     9  * @version  1.0
    10  */
    11 
    12 //类A的equals方法总是返回true,但没有重写其hashCode()方法
    13 class A{
    14  public boolean equals(Object obj){
    15   return true;
    16  }
    17 }
    18 //类B的hashCode()方法总是返回1,但没有重写其equals()方法
    19 class B{
    20  public int hashCode(){
    21   return 1;
    22  }
    23 }
    24 //类C的hashCode()方法总是返回2,但没有重写其equals()方法
    25 class C{
    26  public int hashCode(){
    27   return 2;
    28  }
    29  public boolean equals(Object obj){
    30   return true;
    31  }
    32 }
    33 public class TestHashSet{
    34  public static void main(String[] args) {
    35   HashSet books = new HashSet();
    36   //分别向books集合中添加2个A对象,2个B对象,2个C对象
    37   books.add(new A());
    38   books.add(new A());
    39   books.add(new B());
    40   books.add(new B());
    41   books.add(new C());
    42   books.add(new C());
    43   System.out.println(books);
    44  }
    45 }

      上面的程序中向books集合中添加了2个A对象、2个B对象、2个C对象,其中C类重写了equals()方法,总是返回true,hashCode()方法总是返回2,这将导致HashSet会把2个C对象当成同一个对象。运行上述可以得到
    [B@1,B@1,C@2,A@343,A@kd33]
    需要注意的是
      如果需要把一个对象放入HashSet中时,如果重写该对象对应类的equals()方法,也应该重写起hashCode()方法,其规则是:如果2个对象通过equals方法比较返回true,这两个hashCode应该也相同,这样就不会添加两个同样的对象。
      如果两个对象通过equals方法返回true,但是通过hashCode返回不同的,那么将会将两个对象保存在不同位置,从而都可以添加成功,这样会违反Set集合的特征(虽然可以添加成功)。
      如果equals方法返回false,但hashCode值却是一样的,这样更加极品。因为hash值一样,HashSet试图把它们保存在同一个位置(但实际不能这样做,否认则将覆盖其中一个),所以处理比较复杂,而且HashSet访问元素根据hash值访问,如果HashSet中包含元素有相同的hashCode值,将导致性能下降。
      HashSet的使用hashCode值去查询元素,hashCode就相当于数组索引,但为什么不使用数组呢?因为数组元素索引是连续的,并且长度固定,无法自由增加数组长度。而HashSet访问元素,可以先算出hashCode值,然后去对应位置取出元素。

     重写hashCode方法原则:
     当两个对象通过equals方法返回true时,对象的hashCode应该也要相等
     对象中用作equals比较标准的熟悉,都应该用来计算hashCode值
     重写hashCode()方式
     第一步:

     对象内每个有意义的熟悉f(即每个用作equals()比较标准的属性)计算出一个int类型的hashCode值,计算方法如下:
     不同类型属性取得hashCode值方式
     属性类型      计算方式
     boolean       hashCode=(f?0:1);
     整数类型(byte,short,char,int) hashCode=(int)f;
     long       hashCode=(int)(f^(f>>>32))
     float       hashCode=Float.floatToIntBits(f)
     double       long l = Double.doubleTolongBits(f);
     hashCode = (int)(l^(l>>>32));
     普通引用类型     hashCode = f.hashCode();
    第二步:

    用第一步计算出来的对象的多个hashCode值组合计算出一个最终的hashCode,作为对象的hashCode值返回
    return f2.hashCode() + (int)f2;
    为了避免直接相加产生的偶然相等(两个对象的f1、f2不等,但他们的和刚好相等。例如6=2+4=3+3),可以通过为各属性乘以一个质数再相加:
    return f1.hashCode()*17 + (int)f2*13;
    当向HashSet中添加可变对象时,必须十分小心,如果修改HashSet集合中的对象,有可能导致该对象与集合中其他对象相等,从而导致HashSet无法准确访问对象。也就是说添加时候会做出检查,修改时候是不会检查元素之间是否相同的。所以如果修改导致了与其他对象相等,那么HashSet无法准确访问该对象。

    2.1.2 LinkedHashSet接口                   

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

     注意:LinkedHashSet集合特点是有序的,并且顺序和添加时候顺序一致。

     2.2 SortedSet——TreeSet接口         

    TreeSet是SortedSet接口的唯一实现,正如SortedSet名字所述,TreeSet可以确保元素处于排序状态。TreeSet提供了如下方法

    方法:             

    Comparator comparator():返回当前Set使用的Comparator,或者null(表示自然方式排序)
    Object first():返回集合中的第一个元素
    Object last():返回集合中的最后一个元素
    Object lower(Object e):返回集合中与e相比的最大元素,e不一定是TreeSet里面的元素,也就是下确界
    Object higher(Object e):返回集合中与e相比的最小元素,也就是上确界,e不一定是TreeSet里面的元素

    SortedSet subSet(fromElement,toElement):返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)

    SortedSet headSet(toElement):返回此Set的子集,由小于toElement的元素组成
    SortedSet tailSet(fromElement):返回此Set的子集,由大于等于fromElement的元素组成

    总的来说:TreeSet提供了:访问第一个最后一个,上确界、下确界(前一个后一个),以及截取子TreeSet的方法

    区别:    

      同样是提供了排序的存储,LinkedHashSet与TreeSet是有本质区别的。LinkedHashSet的顺序是插入元素时候的顺序,TreeSet提供的是元素值的顺序

      TreeSet支持两种排序方法:自然排序和定制排序,默认是自然排序

    2.2.1 自然排序      

      TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间大小关系,然后集合元素按照升序排列,这种方式就是自然排序。
      Java提供了一个Comparable接口,该接口定义一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小。当一个对象调用该方法与另一个对象进行比较,例如obj1.compareTo(obj2),如果该方法返回0,则表示这两个对象相等;如果返回一个正整数,则表明obj1大于obj2,如果返回是一个负整数,则表示obj1小于obj2。

      Java的一些常用类已经实现了Comparable接口,并提供了比较大小的标准。下面是实现了Comparable接口的常用类。
      BigDecimal、BigInteger以及所有数值型对应的包装类:按它们对应的数值大小进行比较
      Character:按字符的UNICODE值进行比较
      Boolean:true对应的包装类实例大于false对应的包装类实例
      String:按字符串字符的UNICODE值进行比较
      Date、Time:后面的时间、日期比前面的时间、日期大。
      如果试图把一个对象添加进TreeSet时,该对象的类必须实现Comparable接口,否则程序抛出异常。另外在实现compareTo(Object obj)方法时,需要将被比较对象obj强制类型转换成相同类型,因为只有相同类的实例才会比较大小。比如日期和字符串就不能直接比较。
      对TreeSet集合而言:判断两个对象不相等的标准是:两个对象通过equals方法比较false,或通过compareTo(Object obj)比较没有返回0——即使两个对象是同一个对象。
      类似地:当需要把一个对象放入TreeSet中时,重写该对象对应类的equals()方法时,应该保证该方法与compareTo(Object obj)方法有一致的结果,其规则是:如果两个对象通过equals方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较返回0.

      如果两个对象通过equals方法返回true,但是通过compareTo(Object obj)返回不同的,那么将会将两个对象保存在不同位置,从而都可以添加成功,这样会违反Set集合的特征(虽然可以添加成功)。
      如果equals方法返回false,但compareTo(Object obj)值却是一样的,这样更加极品。因为比较相等,TreeSet试图把它们保存在同一个位置(但实际不能这样做,否认则将覆盖其中一个),所以处理比较复杂。
      如果向TreeSet中添加一个可变对象后,并且后面程序修改了该可变 对象的属性,导致它与其他对象的大小顺序发生了改变,但TreeSet不会再次调整它们的顺序,甚至可能导致TreeSet中保存这两个对象,它们通过equals方法比较返回true,通过compareTo(Object obj)方法比较返回0.

    2.2.2 定制排序       

      可以对TreeSet集合类进行自定义排序,比如降序。可以使用Comparator接口帮助,该接口里面有一个int compare(T o1,T o2)方法用于比较o1和o2大小,比较原理痛compareTo()一样。
      如果实现定制排序,需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。

    2.3 EnumSet类                               

      EnumSet是一个专为枚举类设计的集合类,EnumSet中所有值都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。EnumSet集合元素也是有序的,EnumSet以枚举值在Enum类定义顺序来决定集合元素的顺序。也就是不以元素值大小决定,根据添加顺序决定。
      EnumSet在内部以为向量的形式存储,这种存储形式紧凑、高效,占用内存小。
      EnumSet集合不允许加入null元素,如果插入null元素将抛出空指针异常。如果只是测试是否出现null元素或删除null元素都不会抛出异常,只是删除操作将返回false,因为没有任何null元素被删除。
       EnumSet类没有暴露任何构造器来创建该类的实例,程序应该通过它提供的static方法来创建EnumSet对象。它提供了如下常用static方法来创建EnumSet对象。

    2.3.1 EnumSet创建方法           

    static EnumSet allOf(Class elementType):创建一个包含指定枚举类里所有枚举值的EnumSet集合
    static EnumSet complementOf(EnumSet s):假设调用该方法的是EnumSet a,调用后的新EnumSet的集合元素是a+s,结果放在a中
    static EnumSet copyOf(Collection c):使用一个普通集合来创建EnumSet集合
    static EnumSet copyOf(EnumSet s):创建一个与指定EnumSet具有相同元素类型、相同集合元素的EnumSet
    static EnumSet noneOf(Class elementType):创建一个元素类型为指定枚举类型的空EnumSet
    static EnumSet of(E first,E...rest):创建一个包含一个或多个枚举值的EnumSet,传入的多个枚举值必须属于同一个枚举类。
    static EnumSet range(E from ,E to):创建包含从from枚举值,到to枚举值范围内所有枚举值的EnumSet集合

    注意:

    EnumSet复制另一个EnumSet集合中所有元素创建新的EnumSet,或复制另一个Collection集合中所有元素来创建新的EnumSet,当复制Collection集合中所有元素创建新的EnumSet时,要求Collection集合中所有元素必须是同一个枚举类型的枚举值

      HashSet和TreeSet是Set的两个典型实现,那如何选择HashSet和TreeSet呢?HashSet的性能总是比TreeSet好(特别是最常用的添加、查询元素等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet
      HashSet还有一个子类:LinkedHashSet,对于普通插入、删除操作,LinkedHashSet比HashSet要略慢一点:这是有维护链表所带来的额外开销,不过有了链表,遍历LinkedHashSet会更快
      EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
      必须指出的是,Set的三个实现类HashSet、TreeSet和EnumSet都是线程不安全的,如果有多条线程同时访问一个Set集合,并且有超过一条线程修改了该Set集合,则必须手动保证该Set集合的同步性。通常可以通过Collections工具类synchronizedSortedSet方法来包装该Set集合。次操作最好在创建时进行,以防止对Set集合的意外非同步访问。

    3.Queue集合类                                                    

    3.1概述                                

      Queue用于模拟了队列这种数据结构,队列是指"先进先出"(FIFO)的容器。队列的头部保存在队列中时间最长的元素,队列的尾部保存在队列中时间最短的元素。

    3.2队列                             

      队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
      在队列这种数据结构中,最先插入的元素将是最先被删除的元素;反之最后插入的元素将是最后被删除的元素,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。
      队列空的条件:front=rear
      队列满的条件: rear = MAXSIZE
      队列不允许随机访问队列中的元素。

    3.3 Queue接口定义的方法                

      void add(Object e):将制定元素加入此队列的尾部。
      Object element():获取队列头部的元素,但是不删除该元素。
      boolean offer(Object e):将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Object e)方法更好。
      Object peek():获取队列头部的元素,但是不删除该元素,如果此队列为空,则返回null
      Object poll():获取队列头部的元素,并删除该元素,如果队列为空,则返回为null
      Object remove():获取队列头部的元素,并删除该元素。

    3.4 Queue的实现类                          

    Queue有两个常用的实现类:LinkedList和PriorityQueue,下面介绍着两个类。

    3.4.1 LinkedList实现类    

      LinkedList类是一个奇怪的类,它是List接口的实现类——这意味着它是一个List集合,可以根据索引来随机访问集合中的元素。除此之外,LinkedList还实现了Deque接口,Deque接口是Queue接口的子接口,它代表一个双向队列,Deque有如下方法。
    void addFirst(Object e):将制定元素插入该双向队列的开头
    void addLast(Object e):将制定元素插入该双向队列的末尾
    Iterator descendingIterator():返回以该双向队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素。
    Object getFirst():获取、但不删除双向队列的第一个元素
    Object getLast():获取、但不删除双向队列的最后一个元素
    boolean offerFirst(Object e):将指定的元素插入该双向队列的开头
    boolean offerLast(Object e):将指定的元素插入该双向队列的末尾
    Object poolFirst():获取、并删除该双向队列的第一个元素;如果此双向队列为空,则返回null
    Object poolLast():获取、并删除该双向队列的最后一个元素;如果此双向队列为空,则返回null
    Object peekFirst():获取、但不删除该双向队列的第一个元素;如果此双向队列为空,则返回null
    Object peekLast():获取、但不删除该双向队列的最后一个元素;如果此双向队列为空,则返回null
    Object pop():pop出该双向队列所表示的栈中第一个元素
    void push(Object e):将一个元素push进该双向队列所表示的栈中(即该双向队列的头部)
    Object removeFirst():获取并删除该双向队列的第一个元素
    Object removeFirstOccurrence(Object o):删除该双向队列的第一次出现元素o
    removeLast():获取并删除该双向队列的最后一个元素
    removeLastOccurrence(Object o):删除该双向队列的最后一次出现的元素o

    LinkedList不仅可以当成双向队列使用,也可以当成"栈"(但其实不是栈)使用,因为该类里面有pop和push两个方法,除此之外,LinkedList实现了List接口,所以被当成List使用。

      LinkedList有上述方法让它可以当做双向队列、栈和List集合用。LinkedList是个相当强大的集合类。
      LinkedList与ArrayList、Vector的实现机制完全不同,ArrayList、Vector内部以数组形式保存集合中的元素,因此随机访问集合元素上性能较好,而LinkedList以链表形式保存集合,所以随机访问集合元素较差,但插入、删除元素时性能出色。
      通常编程时候无需理会ArrayList和LinkedList性能差异,只需要知道LinkedList集合不仅提供了List功能,还有双向队列及栈功能,在一些性能比较敏感的地方,可能需要慎重选择哪个List实现。下面是三者性能差异:
            类别        实现机制        随机访问排名     迭代排名   插入排名   删除排名
            数组      连续内存区保存元素        1         不支持      不支持   不支持
            ArrayList   内部以数组保存元素        2          2        2      2
            Vector    内部以数组保存元素        3          3         3     3
            LinkedList  内部以链表保存元素       4          1         1     1
      从上表看出:因为数组以一块连续内存区保存所有数组元素,所以数组在随机访问时性能最好。所有内部数组作为底层实现的集合在随机访问时也有较好的性能;而内部以链表作为底层实现的集合在插入、删除操作时有很好的性能,以链表作为底层实现的集合也比数组作为底层实现的集合性能好。

     1 package chapter3excercise;
     2 
     3 import java.util.*;
     4 
     5 public class TestPerformance {
     6 public static void main(String[] args){
     7 //创建一个字符串数组
     8 String[] test1 = new String[200000];
     9 //动态初始化数组
    10 for(int i = 0;i < test1.length;i++){
    11 test1[i] = String.valueOf(i);
    12 }
    13 ArrayList al = new ArrayList();
    14 for(int i = 0;i < test1.length;i++){
    15 al.add(test1[i]);
    16 }
    17 LinkedList ll = new LinkedList();
    18 for(int i = 0;i < test1.length;i++){
    19 ll.add(test1[i]);
    20 }
    21 long start = System.currentTimeMillis();
    22 for(Iterator it = al.iterator();it.hasNext();){
    23 it.next();
    24 }
    25 System.out.println("迭代ArrayList元素所需要的时间:" + 
    26 (System.currentTimeMillis() - start));
    27 start = System.currentTimeMillis();
    28 for(Iterator it = ll.iterator();it.hasNext();){
    29 it.next();
    30 }
    31 System.out.println("迭代LinkedList元素所需要的时间:" + 
    32 (System.currentTimeMillis() - start));
    33 }
    34 }

      多次运行上面程序会发现,迭代ArrayList集合的时间略大于迭代LinkedList集合的时间。因此,关于使用List集合有如下建议:
      如果需要遍历List集合元素,对于ArrayList、Vector集合,则应该使用随机访问方法(get)来遍历集合元素,这样性能更好。对于LinkedList集合,则应该采用迭代器来遍历集合元素
      如果需要经常执行插入、删除操作来改变List集合的大小,则应该使用LinkedList集合,而不是ArrayList。使用ArrayList、Vector集合将需要经常重新分配内部数组的大小,其时间开销常常是使用LinkedList时时间开销的几十倍,效果很差。
      如果有多条线程需要同时访问List集合中元素,可以考虑使用Vector这个同步实现。

    3.4.2 PriorityQueue实现类    

      PriorityQueue是一个比较标准的队列实现类,之所以说它是比较标准的队列实现,而不是绝对标准的队列实现是因为:PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序。因此因此当调用peek方法或者poll方法来取出队列中的元素时,并不是取出最先进入队列的元素,而是取出队列最小的元素。从这个意义上看,PriorityQueue已经违反了队列的最基本的规则:先进先出。

      PriorityQueue不允许插入null元素,它还需要对队列元素进行排序,队列元素有两种排序方式:
    自然排序:采用自然顺序的PriorityQueue集合中的元素必须实现了Comparable接口,而且应该是同一类的多个实例,否则可能导致ClassCastException异常
    定制排序:创建PriorityQueue队列时,传入一个Comparator对象,该对象负责对队列中所有元素排序,采用定制排序时不要求队列元素实现Comparable接口
    PriorityQueue队列对元素的要求与前面TreeSet对元素的要求基本一致,可以参考TreeSet处理

    4.List接口和ListIterator接口                             

      List作为Collection接口的子接口,可以使用Collection接口里的全部方法,而且List是有序集合,因此List集合里面增加了一些根据索引来操作集合元素的方法:
      void add(int index,Object element):将元素element插入在List集合index处
      boolean addAll(int index,Collection c):将集合c的所有元素都插入在List集合index处
      Object get(int index):返回集合index索引处的元素
      int indexOf(Object o):返回对象o在List集合中出现的位置索引
      int lastIndexOf(Object o):返回对象o在List集合中最后一次出现的位置索引
      Object remove(int index):删除并返回index索引处的元素
      Object set(int index ,Object element):将index索引处的元素替换成element对象,返回新元素
      List subList(int fromIndex,int toIndex):返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素的子集合。
      所有List实现类都可以调用这些方法实现对集合元素的操作,相对于Set集合,List可以根据索引来插入、替换和删除集合元素。
      与Set只提供了一个iterator()方法不同,List还额外提供了一个listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法。

      ListIterator接口在Iterator接口基础上增加了如下方法:
      boolean hasPrevious():返回该迭代器关联的集合是否还有上一个元素
      Object previous():返回该迭代器的上一个元素
      void add():在指定位置插入一个元素
      拿ListIterator与普通Iterator进行对比,容易发现ListIterator增加了向前迭代的功能(Iterator只能向后迭代),而且ListIterator还可以通过add方法向List集合添加元素(Iterator只能删除元素)。
      正向迭代是从正向迭代输出元素,反向迭代是从反向迭代输出元素。

    4.1 ArrayList和Vector实现类           

      ArrayList和Vector是List的两个典型实现类,完全支持List接口的全部功能。
      ArrayList和Vector类是基于数组实现的List类,所以ArrayList和Vector类封装了一个动态再分配的Object[]数组。每个ArrayList或Vector对象有一个capacity属性,这个capacity表示它们所封装的Object[]数组长度。当想ArrayList或Vector中添加元素时,其capacity会自动增加。
      通常无需关心ArrayList或Vector的capacity属性,但如果想ArrayList集合或Vector集合中添加大量元素时,可以使用ensureCapacity方法一次性增加capacity,可以减少增加分配次数,提高性能。
      如果开始就知道ArrayList集合或Vector集合需要保存多少个元素,可以创建时候就指定capacity大小。如果不指定,则capacity属性默认值为10
    此外,ArrayList和Vector集合提供了两个方法操作capacity属性。
      void ensureCapacity(int minCapacity):将ArrayList或Vector集合中的capacity增加minCapacity
      void trimToSize():调整ArrayList或Vector集合的capacity为列表当前大小。程序可调用该方法来减少ArrayList或Vector集合对象存储空间
      ArrayList或Vector在用法上几乎完全相同,但Vector是一个古老的集合最开始Java没有提供系统的集合框架,所以Vector提供了一些方法名很长的方法。   ArrayList开始就作为List的主要实现类,因此没有那些方法名很长的方法,实际上Vector有很多缺点,通常尽量少用Vector实现类。
      ArrayList和Vector的显著区别是ArrayList是线程不安全的,当多线程访问同一个ArrayList集合时,如果超过一条修改了ArrayList集合,则需要手动保证集合的同步性,而Vector集合是线程安全的,无需保证集合的同步性。Vector性能低于ArrayList性能。
      Vector还提供了Stack子类,用于模拟"栈"这种数据结构,栈通常是后进先出进栈出栈的都是Object,所以取出栈里的元素需要做类型转换。
      Object peek():返回"栈"的第一个元素,但并不将该元素pop出栈
      Object pop():返回栈的第一个元素,并将该元素pop出栈
      void push(Object item):将一个元素push进栈,最后一个进栈的元素总是位于栈顶。
      数组的工具类Arrays里面提供了asList(Object...a)方法,该方法可以把一个数组或指定个数的对象转换成一个List集合,这个List集合既不是ArrayList实现类,也不是Vector实现类的实例,而是Arrays的内部类ArrayList的实例
      Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除集合里的元素。

      

    5.Map集合                                                                        

    5.1 Map集合特点                 

      Map用于保存具有映射关系的数据,因此Map集合保存着两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value,key和value都可以是任何引用类型的数据。Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。key和value之间存在单向一对一关系,即通过指定的key,总能找到唯一的、确定的value。从Map中取出数据时,只要给出指定的key,就可以取出对应的value。也就是说Map保存的元素是键值对。如果把Map里的key放在一起看,它们就组成了一个Set集合(key是没有顺序,key与key之间不能重复),事实上Map确实包含了一个keySet()方法,用于返回Map所有key组成的Set集合。

      不仅如此Map里key集合和Set集合里元素的存储形式也很像,Map子类和Set子类在名字上也几乎相似。Set接口下有HashSet、LinkedHashSet、SortedSet(接口)、TreeSet、EnumSet等实现类和子接口,而Map接口下则有HashMap、LinkedHashMap、SortedMap(接口)、TreeMap、EnumMap等实现类和子接口。正如名字暗示,Map的实现类和子接口中key集存储形式和对应的Set集合存储形式完全相同。如果把Map所有的value放在一起看,它们非常类似于一个List:元素与元素之间可以重复,每个元素通过索引来查找,只是Map中的索引不再使用整数值,而是另一个对象来作为索引。如果需要从List集合取出元素,需要提供该元素的数字索引,如果需要从Map中取出元素,需要提供该元素的key索引,因此Map也被称为字典或关联数组。

    5.2 Map接口方法                   

      Map中包括一个内部实现类:Entry。该类封装了一个key-value对,Entry包含三个方法:
      Object getKey():返回该Entry里包含的key值
      Object getValue():返回Entry里面包含的value值
      Object setValue(V value):设置该Entry里包含的value值,并返回新设置的value值
      可以把Map理解成一个特殊的Set,只是该Set里包含的集合元素是Entry类对象,而不是普通对象

    5.3Map接口的内部实现类      

      Map中包括一个内部实现类:Entry。该类封装了一个key-value对,Entry包含三个方法:   Object getKey():返回该Entry里包含的key值   Object getValue():返回Entry里面包含的value值   Object setValue(V value):设置该Entry里包含的value值,并返回新设置的value值   可以把Map理解成一个特殊的Set,只是该Set里包含的集合元素是Entry类对象,而不是普通对象

    5.4 HashMap和Hashtable

      HashMap和Hashtable是Map接口的典型实现类,它们之间的关系类似于ArrayList和Vector关系:Hashtable是个古老的Map实现类,它有两个繁琐的方法elements()和keys(),现在基本不用了。

    Hashtable和HashMap的区别

      Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable性能好,但多线程访问Map对象时,Hashtable实现类更好 Hashtable不允许使用null作为key和table,如果把null值放进Hashtable中,将会引发NullPointerException异常,但HashMap可以使用null作为key或value,但由于key不能重复,所以HashMap里最多只有一项key-value对的key为null,但可以有无数多项key-value对的value为null。key类似Set集合的,所以无序、禁止重复key,而value类似List所以可以重复,顺序和key是对应的。 代码:

     1 package chapter7;
     2 
     3 import java.util.HashMap;
     4 
     5 public class NullInHashMapTest {
     6 public static void main(String[] args){
     7 HashMap hm = new HashMap();
     8 //将两个key值为null的放在key-value键值对中
     9 hm.put(null, null);
    10 hm.put(null, null);
    11 //将一个value值为null的放入key-value键值对中
    12 hm.put('a', null);
    13 System.out.println(hm);
    14 }
    15 }
    16 
    17 输出结果:{null=null, a=null}

      根据输出结果可以看出HashMap重写了toString()方法,实际所有Map实现类都重写了toString()方法,调用Map对象的toString()方法总是返回如下格式字符串 {key1=value1,key2=value2}

      Hashtable从类名上就可以看出是一个古老的类,命名甚至都没有遵守Java的命名规范:每个类的单词首字母大写。后来也没有改成HashTable,否则将有大量程序需要改写。尽量少用Hashtable类,即使需要创建线程安全的Map实现类,可以通过Collections工具类,把HashMap变成线程安全的,无须使用Hashtable实现类。

      为了成功在HashMap、Hashtable中存储、获取对象,用作key的对象必须实现hashCode方法和equals方法。HashMap、Hashtable判断两个key相等的标准也是key通过equals方法返回true,两个key的hashCode相等。判断value相等的标准是equals返回true即可,不需要hashCode判断。

     1 package chapter7;
     2 
     3 import java.util.Hashtable;
     4 
     5 //定义类A,该类根据A对象的count属性来判断两个对象是否相等,计算hashCode值
     6 //只要两个A对象的count相等,则它们通过equals比较也相等,其hashCode值也相等
     7 
     8 class AA{
     9 int count;
    10 public AA(int count){
    11 this.count = count;
    12 }
    13 public boolean equals(Object obj){
    14 if (obj == this){
    15 return true;
    16 }
    17 if(obj!=null && obj.getClass() == AA.class){
    18 AA a = (AA)obj;
    19 if(this.count == a.count){
    20 return true;
    21 }
    22 
    23 }
    24 return false;
    25 }
    26 public int hashCode(){
    27 return this.count;
    28 }
    29 }
    30 //定义类B,B对象与任何对象通过equals方法比较都相等
    31 class BB{
    32 public boolean equals(Object obj){
    33 return true;
    34 }
    35 }
    36 public class HashtableTest {
    37 public static void main(String[] args){
    38 Hashtable ht = new Hashtable();
    39 ht.put(new AA(60000),"English");
    40 ht.put(new AA(87653),"Castellano");
    41 ht.put(new AA(1232),new B());
    42 System.out.println(ht);
    43 //只要两个对象通过equals比较返回true,Hashtable就认为它们是相等的value。
    44 //因为Hashtable中有一个B对象,它与任何对象通过equals比较都相等,所以下面输出true。
    45 System.out.println(ht.containsValue("测试字符串"));
    46 //只要两个A对象的count属性相等,它们通过equals比较返回true,且hashCode相等
    47 //Hashtable即认为它们是相同的key,所以下面输出true。
    48 System.out.println(ht.containsKey(new AA(87653)));
    49 //下面语句可以删除最后一个key-value对
    50 ht.remove(new AA(1232));
    51 for(Object key:ht.keySet()){
    52 System.out.print(key + "---->");
    53 System.out.print(ht.get(key) + "\n");
    54 }
    55 }
    56 }

      程序最后展示了如何遍历Map中的全部key-value对:调用Map对象的keySet方法返回全部key组成的Set对象,通过遍历Set的元素(就是Map的全部key)就可以遍历Map中的所有键值对。

      与HashSet类似的是,尽量不要使用可变对象作为HashMap、Hashtable的key,如果确实需要使用可变对象作为HashMap、Hashtable的key,则尽量不要在程序中修改作为key的可变对象。

      HashSet有一个子类是LinkedHashSet,HashMap则有一个子类:LinkedHashMap;LinkedHashMap也使用双向链表来维护key-value对的次序,该链表定义了迭代次序,迭代顺序与插入顺序时候保持一致。

      LinkedHashMap可以避免需要对HashMap、Hashtable里的key-value对进行排序(只要插入key-value对时保持顺序即可)。同时可以避免使用TreeMap所增加的成本。LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能,但在迭代访问Map里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。下面程序示范了LinkedHashMap的功能:迭代输出LinkedHashMap的元素时,将会按添加key-value对相同顺序输出。

    Properties类                           

      Properties类是Hashtable类的子类,该文件处理属性文件(ini文件就是一种属性文件)。Properties类可以把Map对象和属性文件关联起来,从而把Map对象中的key-value对写入属性文件,也可以把属性文件中的属性名=属性值加载到Map对象中。由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的key、value都是字符串类型,该类提供了如下三个方法来修改Properties里的key、value值。

    方法:

      String getProperty(String key):获取Properties中指定属性名对应的属性值,类似于Map的get(Object key)方法

      String getProperty(String key,String defaultValue):该方法与前一个方法基本类似,该方法多一个功能,如果Properties中不存在指定key时,该方法返回默认值

      Object setProperty(String key,String value):设置属性值,类似Hashtable的put方法 除此之外,它还提供了两个读、写属性文件的方法

      void load(InputStream inStream):从属性文件(以输入流表示)中加载属性名=属性值,把加载到的属性名=属性值对追加到Properties里(由于Properties是Hashtable的子类,它不保证key-value对之间的次序)。

      void store(OutputStream out,String comments):将Properties中的key-value对写入指定属性文件(以输出流表示)。

    5.5 SortedMap和TreeMap

      正如Set接口派生出了SortedSet子接口,SortedSet接口有一个TreeSet实现类,Map接口也派生了一个SortedMap子接口,SortedMap也有一个TreeMap实现类。 与TreeSet类似的是,TreeMap也是基于红黑树对TreeMap中所有key进行排序,从而保证TreeMap中的键值对处于有序状态。类似也有两种排序方式。

    排序方式

      自然排序:TreeMap的所有key必须实现Comparable接口,而且所有key应该是同一个类对象,否则抛出ClassCastException异常   定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有key进行排序,采用定制排序时不要求Map的key实现Comparable接口   类似地,TreeSet中判断两个元素相等的标准,TreeMap中判断两个key相等的标准也是两个key通过equals比较返回true,而通过compareTo方法返回0,TreeMap即认为这两个key是相等的。   如果想使用自定义的类作为TreeMap的key,且想让TreeMap工作良好,重写该类的equals方法和compareTo方法时应该有一致的返回结果:即两个key通过equals方法比较返回true时,通过compareTo方法返回0,否则处理会有性能问题,具体可以参考TreeSet。

    TreeMap方法   

      与TreeSet类似的是,TreeMap提供了系列根据key顺序访问Map中key-value对方法。
      Map.Entry firstEntry():返回该Map中最小key所对应的键值对,如果该Map为空,则返回null
      Object firstKey():返回该Map中的最小key值,如果Map为空,则返回null
      Map.Entry lastEntry():返回该Map中最大key所对应的key-value对,如果该Map为空,或不存在这样的key-value则返回null
      Object lastKey():返回该Map中的最大key值,如果Map为空,或不存在这样的key都返回null
      Map.Entry higherEntry(Object key):返回该Map中key的上确界对应的键值对
      Object higherKey(Object key):返回key的上确界
      Map.Entry lowerEntry(Object key):返回该Map中key的下确界对应的键值对
      Object lowerKey(Object key):返回key的下确界
      NavigableMap subMap(Object fromKey,boolean fromInclusive,Object toKey,boolean toInclusive):返回该Map的子Map,其key的范围从fromKey(是否包括取决于第二个参数)  到toKey(是否包括取决于第四个参数)
      NavigableMap tailMap(Object fromKey,boolean inclusive):返回该Map的子Map,其key的范围从fromKey(是否包括取决于第二个参数)
      NavigableMap headMap(Object toKey,boolean inclusive):返回该Map的子Map,其key的范围小于toKey(是否包括取决于第二个参数)
      SortedMap subMap(Object fromKey,Object toKey):返回该Map的子Map,其key的范围从fromKey(包括)到toKey(不包括)
      SortedMap tailMap(Object fromKey):返回该Map的子Map,其key的范围从fromKey(不包括)
      SortedMap headMap(Object toKey):返回该Map的子Map,其key的范围小于toKey(不包括)

      上面的看起来方法挺多:也就是第一个、前一个、后一个、最后一个键值对方法,并提供了截取子TreeMap的方法

    5.6 WeakHashMap实现类

      WeakHashMap与HashMap用法基本类似,但与HashMap区别在于,HashMap的key保留对实际对象的强引用,这意味着只要该HashMap对象不被销毁,该HashMap对象所有key所引用的对象不会被垃圾回收,HashMap也不会自动删除这些key所对应的key-value对象;但WeakHashMap的key只保留对实际对象的弱引用,这意味着如果该HashMap对象所有key所引用的对象没有被其他强引用变量所引用,key所引用的对象可能被垃圾回收,HashMap有可能自动删除这些key所对应的key-value对象   WeakHashMap中的每个key对象保存了实际对象的弱引用,因此,当垃圾回收了该key所对应的实际对象之后,WeakHashMap会自动删除key-value对。

     1 package chapter7;
     2 
     3 import java.util.*;
     4 
     5 public class TestWeakHashMap {
     6 public static void main(String[] args){
     7 //添加一个集合,并添加匿名对象(无变量引用)
     8 WeakHashMap whm = new WeakHashMap();
     9 whm.put(new String("English"), "80");
    10 whm.put(new String("Chemistry"), "80");
    11 whm.put(new String("Java"), "80");
    12 System.out.println(whm);
    13 //添加一个有变量引用的对象
    14 whm.put("Deutsch", "99");
    15 System.out.println("垃圾回收前:" + whm);
    16 //通知系统进行垃圾回收
    17 System.gc();
    18 //输出垃圾回收后的结果
    19 System.out.println("垃圾回收后:" + whm);
    20 //对比HashMap垃圾处理机制
    21 HashMap hm = new HashMap();
    22 hm.put(new String("English"), "80");
    23 hm.put(new String("Chemistry"), "80");
    24 hm.put(new String("Java"), "80");
    25 System.out.println(hm);
    26 //添加一个有变量引用的对象
    27 hm.put("Deutsch", "99");
    28 System.out.println("垃圾回收前:" + hm);
    29 //通知系统进行垃圾回收
    30 System.gc();
    31 //输出垃圾回收后的结果
    32 System.out.println("垃圾回收后:" + hm);
    33 
    34 }
    35 }
    36 执行结果如下:
    37 {Java=80, English=80, Chemistry=80}
    38 垃圾回收前:{Java=80, English=80, Deutsch=99, Chemistry=80}
    39 垃圾回收后:{Deutsch=99}
    40 {Chemistry=80, Java=80, English=80}
    41 垃圾回收前:{Chemistry=80, Deutsch=99, Java=80, English=80}
    42 垃圾回收后:{Chemistry=80, Deutsch=99, Java=80, English=80}

    5.7 IdentityHashMap实现类

      这个Map实现类的实现机制与HashMap基本相似,但它在处理两个key相等时比较独特:在IdentityHashMap中,当且仅当两个key严格相等(key1==key2)时,IdentityHashMap才认为两个key相等,对于普通HashMap而言,只要key1和key2通过equals方法比较返回true,且hashCode值相同即可。   IdentityHashMap是一个特殊的Map实现,它有意违反Map的通常规范:在IdentityHashMap中,当且仅当两个key严格相等(key1==key2)时,IdentityHashMap才认为两个key相等 IndetityHashMap提供了与HashMap基本类似的方法,null作为key和value,不保证键值对之间的顺序。

     1 package chapter7;
     2 
     3 import java.util.*;
     4 /**
     5 * Description:
     6 * <br/>Copyright (C), 2005-2008, Yeeku.H.Lee
     7 * <br/>This program is protected by copyright laws.
     8 * <br/>Program Name:
     9 * <br/>Date:
    10 * @author Yeeku.H.Lee kongyeeku@163.com
    11 * @version 1.0
    12 */
    13 public class TestIdentityHashMap{
    14 public static void main(String[] args){
    15 IdentityHashMap ihm = new IdentityHashMap();
    16 //下面两行代码将会向IdentityHashMap对象中添加2个key-value对
    17 ihm.put(new String("语文") , 89);
    18 ihm.put(new String("语文") , 78);
    19 ihm.put("java" , 93);
    20 ihm.put("java" , 98);
    21 System.out.println(ihm);
    22      }
    23 }

    注意:最后添加的两个key为java的键值对是一样的,因为java采用的缓存机制,对于同一个字符串,不新建新的对象来浪费内存

    5.8 EnumMap集合类

      EnumMap是一个与枚举类一起使用的Map实现,EnumMap中所有key都必须是单个枚举类的枚举值。创建EnumMap时必须显式或隐式指定它对应的枚举类。EnumMap在内部以数组形式保存,所以这种实现形式非常紧凑、高效。

      EnumMap根据key的定义时顺序来维护键值对的次序,当使用keySet()、entrySet()、values()等方法来遍历EnumMap时即可看到这种顺序与插入时候顺序是一致的。

      EnumMap不允许使用null昨晚key值,但允许使用null作为value,如果试图使用null作为key将抛出NullPointerException异常,如果只是查询是否包含值为null的key,或者删除使用删除值为null的key都不会抛出异常。

      与创建普通Map有区别的是,创建EnumMap时必须指定一个枚举类,从将该EnumMap和指定枚举类关联起来。

      对于Map的常用实现类而言,HashMap和Hashtable的效率大致相同,因为实现机制几乎完全一样,但通常HashMap通常比Hashtable快一点,因为Hashtable额外实现同步操作。 TreeMap通常比HashMap、Hashtable要慢(尤其是插入、删除键值对时候更慢),因为TreeMap需要额外的红黑树操作来维护key之间的次序,但是用TreeMap有一个好处:TreeMap中的键值对总是有序的,无序专门进行排序操作。当TreeMap被填充后,可以调用keySet(),取得key组成的Set,然后是用toArray()生成key的数组,接下来是用Arrays的binarySearch()方法在已排序的数组中快速地查询对象。当然,通常只有在某些情况下无法使用HashMap的时候才这么做,因为HashMap正是为快速查询而设计的,通常使用Map时候首选HashMap,除非需要一个总是排好序的Map时才使用TreeMap。           LinkedHashMap比HashMap慢一点,因为它需要维护链表来保持Map中key的顺序。IndentityHashMap性能没有特别出色之处,EnumMap性能最好,但它只能使用同一个枚举类的枚举值作为key。

    5.9 HashSet和HashMap的性能选择

      对于HashSet及其子类而言,它们采用hash算法来决定集合中元素的存储位置,并通过hash算法来增加集合容积的大小;对于HashMap、Hashtable及其子类而言,它们采用hash算法来决定Map中key的存储,并通过hash算法来增加key Set容积的大小Hash表里可以存储元素的位置被称为"桶(bucket)",通常情况下,单个"桶"里存储一个元素,此时有最好的性能:hash算法可以根据hashCode值计算出"桶"的存储位置,接着从"桶"中取出元素,但hash表的状态为open:当发送hash冲突时候,单个桶会存储多个元素,这些元素以链表形式存储,必须按顺序搜索。如下图

    因为HashSet、HashMap、Hashtable都是用hash算法来决定其元素(对HashMap则是key)的存储,因此HashSet、HashMap的hash表包含如下属性:

    Hash表属性  

    容量(capacity):hash表中桶的数量

    初始化容量(initial capacity):创建hash表时桶的数量。HashMap和HashSet都允许在构造器中指定初始化容量

    尺寸(size):当前hash表中记录的数量

    负载因子(load factor):负载因子等于size/capacity。负载因子为0,表示空的hash表,0.5 表示半满的hash表,以此类推,轻负载的hash表具有冲突少、适宜插入与查询的特点(但是使用Iterator迭代元素时候会变慢)

      除此之外,hash表里还有一个负载极限,负载极限在[0,1]的数值,负载极限决定了hash表中的最大填满程度。当hash表中的负载因子到达指定的负载极限时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,也被称为rehashing

      HashSet与HashMap、Hashtable的构造器允许指定一个负载极限,HashSet与HashMap、Hashtable默认的负载极限为0.75, 表明当该hash表被填到3/4 时,hash表会发生rehashing

      默认负载极限值0.75 是时间和空间成本上的一种折中:较高的负载极限可以降低hash表所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的操作;较低的负载极限会增加查询数据性能,但会增加hash表所占用的内存空间。通常无须改变HashSet和HashMap的负载极限值。通常不需要将初始化容量设置太高。

    5.10 操作集合工具类Collections

      Java提供了一个操作Set、List和Map等集合的工具类:Collections,该工具类提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不可变、对集合对象实现同步控制等方法。

    5.10.1 排序操作

      Collections提供了如下几个方法对List集合元素进行排序   static void reverse(List list):反转指定List集合中元素的顺序   static void shuffle(List list):对List集合元素进行随机排序(shuffle方法模拟了洗牌动作)   static void sort(List list):根据元素的自然顺序对指定List集合的元素按升序进行排序   static void sort(List list,Comparator c):根据指定Comparator产生的顺序对List集合的元素进行排序   static void swap(List list,int i,int j):将指定List集合中i处元素和j处元素进行交换   static void rotate(List list,int distance):将指定集合中i处元素和list.length-i-1 处的元素进行交换

    5.10.2查找,替换操作              

      Collections还提供了如下用于查找、替换集合元素的常用方法   static int binarySearch(List list,Object key):使用二分搜索法搜索指定List集合,以获得指定对象在List集合中的索引,如果要该方法可以正常工作,必须保证List中的元素已经处于有序状态

      static Object max(Collection coll):根据元素的自然顺序,返回给定集合中的最大元素   static Object max(Collection coll,Comparator comp):根据指定Comparator产生的顺序,返回给定集合的最大元素   static Object min(Collection coll):根据元素的自然顺序,返回给定集合中的最小元素   static Object min(Collection coll,Comparator comp):根据指定Comparator产生的顺序,返回给定集合的最小元素   static void fill(List list,Object obj):使用指定元素obj替换指定List集合中的所有元素   static int frequency(Collection c,Object o):返回指定集合中等于指定对象的元素数量   static int indexOfSubList(List source,List target):返回子List对象在母List对象中第一次出现的位置索引;如果母List中没有这样的子List,则返回-1

      static int lastIndexOfSubList(List source,List target):返回子List对象在母List对象中最后一次出现的位置索引;如果母List中没有这样的子List,则返回-1   static boolean replaceAll(List list,Object oldVal,Object newVal):使用一个新值newVal替换List对象所有的旧值oldVal

    5.10.3 同步控制                       

      Collections类中提供了多个synchronizedXxx方法(Xxx代表集合类名称),该方法返回指定集合对象对应的同步对象,从而可以解决多线程并发访问集合时的线程安全问题。 如前所述,Java常用的集合框架中推荐使用的三个实现类:HashSet、ArrayList和HashMap都是线程不安全的,如果有多条线程访问它们,而且有超过一条线程试图修改它们,则可能出现错误。Collections提供了多个静态方法用于创建同步集合。

    5.10.4 设置不可变集合             

    Collections提供了如下三类方法来返回一个不可变的集合,Xxx代表集合类名称   emptyXxx():返回一个空的、不可变的集合对象,此处的集合可以是List、Set、Map   singleonXxx():返回一个只包含指定对象(只有一个或一项元素)的、不可变的集合对象,此处的集合可以是List、Set、Map   unmodifiableXxx:返回指定集合对象的不可变视图。此处的集合可以是List、Set、Map 通过这样的方法就可以创建"只读"版本的集合,对象不可修改

    5.10.5 烦琐的:Enumeration

      Enumeration接口是Iterator迭代器的古老版本,目前都使用Iterator,保留Enumeration主要是为了照顾那些古老的程序。

    6.小结                                                                    

      本部分详细介绍了Java集合框架的相关知识。

      本部分从Java集合框架体系开始讲起,概述了Java集合框架的三个主要体系:Set、List和Map,简述集合在编程中的重要性。详细讲述了Set、Queue、List、Map接口及各实现类的详细用法,深入分析了各实现类的机制差异,给出选择集合实现类的原则,最后给出了Collections工具类的基本用法。

  • 相关阅读:
    React class & function component 的区别
    Webpack 4 + React + Typescript 搭建启动模版
    JavaScript的数据类型及其检测
    react SyntheticEvent 合成事件机制
    数组的排序
    Ajax 和 Ajax的封装
    go 结构的方法总结
    go 结构的方法2
    go struct 的方法1
    go 函数闭包
  • 原文地址:https://www.cnblogs.com/people/p/3067086.html
Copyright © 2011-2022 走看看