Stack和Queue:后进先出和先进先出和Java 实现后进先出(LIFO)及Stac和Java集合类: SetListMapQueue使用场景梳理 - .Little Hann

Posted 郭风朴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Stack和Queue:后进先出和先进先出和Java 实现后进先出(LIFO)及Stac和Java集合类: SetListMapQueue使用场景梳理 - .Little Hann相关的知识,希望对你有一定的参考价值。

Queue是先进先出的集合而Stack是后进先出的集合。这两个集合在日常的工作中也经常会用到。Queue相当我们去银行柜台排队,大家依次鱼贯而行。Stack象我们家中洗碗,最后洗好的碗叠在最上面,而下次拿的时候是最先拿到最后叠上去的碗。了解了这样场景,就很容易明白Stack和Queue可用在哪里了。

比如我们为医院作一个排队叫号的系统,那肯定是选择Queue对象处理。如果我们要为出牌或下棋准备一个场景,那肯定是选择Stack,因为通过Stack至少可用提供用户悔棋啊。

以下是Queue的代码演示:

System.Collections.Queue q = new System.Collections.Queue();

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

q.Enqueue(i);//入队

System.Console.WriteLine(q.Count);

while (q.Count > 0)

System.Console.WriteLine(q.Dequeue());//出队

运行的结果如图3.1.27所示:

图3.1.27

以下是Stack的代码演示:

System.Collections.Stack s = new System.Collections.Stack();

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

s.Push(i);//入栈

System.Console.WriteLine(s.Count);

while (s.Count > 0)

System.Console.WriteLine(s.Pop());//出栈

虽然放置元素的次序和Queue一样,但取出的顺序正好相反,如图3.1.28:

图3.1.28


在Java中Stack类表示后进先出(LIFO)的对象堆栈。栈是一种非常常见的数据结构,它采用典型的先进后出的操作方式完成的。每一个栈都包含一个栈顶,每次出栈是将栈顶的数据取出,如下:


        Stack通过五个操作对Vector进行扩展,允许将向量视为堆栈。这个五个操作如下:

            操作                                          说明

empty()

测试堆栈是否为空。

peek()

查看堆栈顶部的对象,但不从堆栈中移除它。

pop()

移除堆栈顶部的对象,并作为此函数的值返回该对象。

push(E item)

把项压入堆栈顶部。

search(Object o)

返回对象在堆栈中的位置,以 1 为基数。

        Stack继承Vector,他对Vector进行了简单的扩展:

[java] view plain copy print ?
  1. public class Stack<E> extends Vector<E>  

        Stack的实现非常简单,仅有一个构造方法,五个实现方法(从Vector继承而来的方法不算与其中),同时其实现的源码非常简单

[java] view plain copy print ?
  1. /** 
  2.      * 构造函数 
  3.      */  
  4.     public Stack()   
  5.       
  6.   
  7.     /** 
  8.      *  push函数:将元素存入栈顶 
  9.      */  
  10.     public E push(E item)   
  11.         // 将元素存入栈顶。  
  12.         // addElement()的实现在Vector.java中  
  13.         addElement(item);  
  14.   
  15.         return item;  
  16.       
  17.   
  18.     /** 
  19.      * pop函数:返回栈顶元素,并将其从栈中删除 
  20.      */  
  21.     public synchronized E pop()   
  22.         E    obj;  
  23.         int    len = size();  
  24.   
  25.         obj = peek();  
  26.         // 删除栈顶元素,removeElementAt()的实现在Vector.java中  
  27.         removeElementAt(len - 1);  
  28.   
  29.         return obj;  
  30.       
  31.   
  32.     /** 
  33.      * peek函数:返回栈顶元素,不执行删除操作 
  34.      */  
  35.     public synchronized E peek()   
  36.         int    len = size();  
  37.   
  38.         if (len == 0)  
  39.             throw new EmptyStackException();  
  40.         // 返回栈顶元素,elementAt()具体实现在Vector.java中  
  41.         return elementAt(len - 1);  
  42.       
  43.   
  44.     /** 
  45.      * 栈是否为空 
  46.      */  
  47.     public boolean empty()   
  48.         return size() == 0;  
  49.       
  50.   
  51.     /** 
  52.      *  查找“元素o”在栈中的位置:由栈底向栈顶方向数 
  53.      */  
  54.     public synchronized int search(Object o)   
  55.         // 获取元素索引,elementAt()具体实现在Vector.java中  
  56.         int i = lastIndexOf(o);  
  57.   
  58.         if (i >= 0)   
  59.             return size() - i;  
  60.           
  61.         return -1;  
  62.       

        Stack的源码很多都是基于Vector,所以这里不再累述,更



本文主要关注Java编程中涉及到的各种集合类,以及它们的使用场景

相关学习资料

http://files.cnblogs.com/LittleHann/java%E9%9B%86%E5%90%88%E6%8E%92%E5%BA%8F%E5%8F%8Ajava%E9%9B%86%E5%90%88%E7%B1%BB%E8%AF%A6%E8%A7%A3%28collection%E3%80%81list%E3%80%81map%E3%80%81set%29.rar
http://blog.sina.com.cn/s/blog_a345a8960101k9vx.html
http://f51889920.iteye.com/blog/1884810

目录

1. Java集合类基本概念
2. Java集合类架构层次关系
3. Java集合类的应用场景代码

1. Java集合类基本概念

在编程中,常常需要集中存放多个数据。从传统意义上讲,数组是我们的一个很好的选择,前提是我们事先已经明确知道我们将要保存的对象的数量。一旦在数组初始化时指定了这个数组长度,这个数组长度就是不可变的,如果我们需要保存一个可以动态增长的数据(在编译时无法确定具体的数量),java的集合类就是一个很好的设计方案了。

集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。所以的集合类都位于java.util包下,后来为了处理多线程环境下的并发安全问题,java5还在java.util.concurrent包下提供了一些多线程支持的集合类。

在学习Java中的集合类的API、编程原理的时候,我们一定要明白,"集合"是一个很古老的数学概念,它远远早于Java的出现。从数学概念的角度来理解集合能帮助我们更好的理解编程中什么时候该使用什么类型的集合类。

Java容器类类库的用途是"保存对象",并将其划分为两个不同的概念:

1) Collection
一组"对立"的元素,通常这些元素都服从某种规则
   1.1) List必须保持元素特定的顺序
   1.2) Set不能有重复元素
   1.3) Queue保持一个队列(先进先出)的顺序
2) Map
一组成对的"键值对"对象

Collection和Map的区别在于容器中每个位置保存的元素个数:

1) Collection 每个位置只能保存一个元素(对象)
2) Map保存的是"键值对",就像一个小型数据库。我们可以通过""找到该键对应的""

2. Java集合类架构层次关系

1. Interface Iterable
迭代器接口,这是Collection类的父接口。实现这个Iterable接口的对象允许使用foreach进行遍历,也就是说,所有的Collection集合对象都具有"foreach可遍历性"。这个Iterable接口只有一个方法: iterator()。它返回一个代表当前集合对象的泛型<T>迭代器,用于之后的遍历操作
1.1 Collection
Collection是最基本的集合接口,一个Collection代表一组Object的集合,这些Object被称作Collection的元素。Collection是一个接口,用以提供规范定义,不能被实例化使用
  1) Set
  Set集合类似于一个罐子,"丢进"Set集合里的多个对象之间没有明显的顺序。Set继承自Collection接口,不能包含有重复元素(记住,这是整个Set类层次的共有属性)。
  Set判断两个对象相同不是使用"=="运算符,而是根据equals方法。也就是说,我们在加入一个新元素的时候,如果这个新元素对象和Set中已有对象进行注意equals比较都返回false,   则Set就会接受这个新元素对象,否则拒绝。
  因为Set的这个制约,在使用Set集合的时候,应该注意两点:1) 为Set集合里的元素的实现类实现一个有效的equals(Object)方法、2) 对Set的构造函数,传入的Collection参数不能包含重复的元素
    1.1) HashSet
    HashSet是Set接口的典型实现,HashSet使用HASH算法来存储集合中的元素,因此具有良好的存取和查找性能。当向HashSet集合中存入一个元素时,HashSet会调用该对象的hash     Code()方法来得到该对象的hashCode值,然后根据该HashCode值决定该对象在HashSet中的存储位置。
    值得主要的是,HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法的返回值相等
      1.1.1) LinkedHashSet
      LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但和HashSet不同的是,它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的       。当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。
      LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时(遍历)将有很好的性能(链表很适合进行遍历)
    1.2) SortedSet	
    此接口主要用于排序操作,即实现此接口的子类都属于排序的子类
      1.2.1) TreeSet
      TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态
    1.3) EnumSet
    EnumSet是一个专门为枚举类设计的集合类,EnumSet中所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式、或隐式地指定。EnumSet的集合元素也是有序     的,它们以枚举值在Enum类内的定义顺序来决定集合元素的顺序
  2) List
  List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许加入重复元素,因为它可以通过索引来访问指定位置的集合元素。List集合默认按元素   的添加顺序设置元素的索引
    2.1) ArrayList
    ArrayList是基于数组实现的List类,它封装了一个动态的增长的、允许再分配的Object[]数组。
    2.2) Vector
    Vector和ArrayList在用法上几乎完全相同,但由于Vector是一个古老的集合,所以Vector提供了一些方法名很长的方法,但随着JDK1.2以后,java提供了系统的集合框架,就将     Vector改为实现List接口,统一归入集合框架体系中
      2.2.1) Stack
      Stack是Vector提供的一个子类,用于模拟""这种数据结构(LIFO后进先出)
    2.3) LinkedList
    implements List<E>, Deque<E>。实现List接口,能对它进行队列操作,即可以根据索引来随机访问集合中的元素。同时它还实现Deque接口,即能将LinkedList当作双端队列     使用。自然也可以被当作"栈来使用"
  3) Queue
  Queue用于模拟"队列"这种数据结构(先进先出 FIFO)。队列的头部保存着队列中存放时间最长的元素,队列的尾部保存着队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,   访问元素(poll)操作会返回队列头部的元素,队列不允许随机访问队列中的元素。结合生活中常见的排队就会很好理解这个概念
    3.1) PriorityQueue
    PriorityQueue并不是一个比较标准的队列实现,PriorityQueue保存队列元素的顺序并不是按照加入队列的顺序,而是按照队列元素的大小进行重新排序,这点从它的类名也可以     看出来
    3.2) Deque
    Deque接口代表一个"双端队列",双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可以当成队列使用、也可以当成栈使用
      3.2.1) ArrayDeque
      是一个基于数组的双端队列,和ArrayList类似,它们的底层都采用一个动态的、可重分配的Object[]数组来存储集合元素,当集合元素超出该数组的容量时,系统会在底层重       新分配一个Object[]数组来存储集合元素
      3.2.2) LinkedList
1.2 Map
Map用于保存具有"映射关系"的数据,因此Map集合里保存着两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的valuekeyvalue都可以是任何引用类型的数据。Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较结果总是返回false。
关于Map,我们要从代码复用的角度去理解,java是先实现了Map,然后通过包装了一个所有value都为null的Map就实现了Set集合
Map的这些实现类和子接口中key集的存储形式和Set集合完全相同(即key不能重复)
Map的这些实现类和子接口中value集的存储形式和List非常类似(即value可以重复、根据索引来查找)
  1) HashMap
  和HashSet集合不能保证元素的顺序一样,HashMap也不能保证key-value对的顺序。并且类似于HashSet判断两个key是否相等的标准也是: 两个key通过equals()方法比较返回true、   同时两个key的hashCode值也必须相等
    1.1) LinkedHashMap
    LinkedHashMap也使用双向链表来维护key-value对的次序,该链表负责维护Map的迭代顺序,与key-value对的插入顺序一致(注意和TreeMap对所有的key-value进行排序进行区     分)
  2) Hashtable
  是一个古老的Map实现类
    2.1) Properties 
    Properties对象在处理属性文件时特别方便(windows平台上的.ini文件),Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入到属性文     件中,也可以把属性文件中的"属性名-属性值"加载到Map对象中
  3) SortedMap
  正如Set接口派生出SortedSet子接口,SortedSet接口有一个TreeSet实现类一样,Map接口也派生出一个SortedMap子接口,SortedMap接口也有一个TreeMap实现类
    3.1) TreeMap
    TreeMap就是一个红黑树数据结构,每个key-value对即作为红黑树的一个节点。TreeMap存储key-value对(节点)时,需要根据key对节点进行排序。TreeMap可以保证所有的key-     value对处于有序状态。同样,TreeMap也有两种排序方式: 自然排序、定制排序
  4) WeakHashMap
  WeakHashMap与HashMap的用法基本相似。区别在于,HashMap的key保留了对实际对象的"强引用",这意味着只要该HashMap对象不被销毁,该HashMap所引用的对象就不会被垃圾回收  。但WeakHashMap的key只保留了对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,当垃  圾回收了该key所对应的实际对象之后,WeakHashMap也可能自动删除这些key所对应的key-value  5) IdentityHashMap
  IdentityHashMap的实现机制与HashMap基本相似,在IdentityHashMap中,当且仅当两个key严格相等(key1 == key2)时,IdentityHashMap才认为两个key相等
  6) EnumMap
  EnumMap是一个与枚举类一起使用的Map实现,EnumMap中的所有key都必须是单个枚举类的枚举值。创建EnumMap时必须显式或隐式指定它对应的枚举类。EnumMap根据key的自然顺序(即   枚举值在枚举类中的定义顺序)

3. Java集合类的应用场景代码

学习了集合类的基本架构框架之后,我们接着来学习它们各自的应用场景、以及细节处的注意事项

0x1: Set

HashSet

import java.util.*; 

//类A的equals方法总是返回true,但没有重写其hashCode()方法。不能保证当前对象是HashSet中的唯一对象
class A

  public boolean equals(Object obj)
  
    return true;
  


//类B的hashCode()方法总是返回1,但没有重写其equals()方法。不能保证当前对象是HashSet中的唯一对象
class B

  public int hashCode()
  
    return 1;
  


//类C的hashCode()方法总是返回2,且有重写其equals()方法
class C

  public int hashCode()
  
    return 2;
  
  public boolean equals(Object obj)
  
    return true;
  

public class HashSetTest

  public static void main(String[] args) 
  
    HashSet books = new HashSet();
    //分别向books集合中添加两个A对象,两个B对象,两个C对象
    books.add(new A());
    books.add(new A());

    books.add(new B());
    books.add(new B());

    books.add(new C());
    books.add(new C());
    System.out.println(books);
  

result:

[B@1, B@1, C@2, A@3bc257, A@785d65]

可以看到,如果两个对象通过equals()方法比较返回true,但这两个对象的hashCode()方法返回不同的hashCode值时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使对象可以添加成功,这就与Set集合的规则有些出入了。所以,我们要明确的是: equals()决定是否可以加入HashSet、而hashCode()决定存放的位置,它们两者必须同时满足才能允许一个新元素加入HashSet
但是要注意的是: 如果两个对象的hashCode相同,但是它们的equlas返回值不同,HashSet会在这个位置用链式结构来保存多个对象。而HashSet访问集合元素时也是根据元素的HashCode值来快速定位的,这种链式结构会导致性能下降。

所以如果需要把某个类的对象保存到HashSet集合中,我们在重写这个类的equlas()方法和hashCode()方法时,应该尽量保证两个对象通过equals()方法比较返回true时,它们的hashCode()方法返回值也相等

LinkedHashSet

import java.util.*; 

public class LinkedHashSetTest

  public static void main(String[] args) 
  
    LinkedHashSet books = new LinkedHashSet();
    books.add("Java");
    books.add("LittleHann");
    System.out.println(books);

    //删除 Java
    books.remove("Java");
    //重新添加 Java
    books.add("Java");
    System.out.println(books);
  

元素的顺序总是与添加顺序一致,同时要明白的是,LinkedHashSetTest是HashSet的子类,因此它不允许集合元素重复

TreeSet

import java.util.*;

public class TreeSetTest

    public static void main(String[] args) 
    
  TreeSet nums = new TreeSet();
  //向TreeSet中添加四个Integer对象
  nums.add(5);
  nums.add(2);
  nums.add(10);
  nums.add(-9);

  //输出集合元素,看到集合元素已经处于排序状态
  System.out.println(nums);

  //输出集合里的第一个元素
  System.out.println(nums.first());

  //输出集合里的最后一个元素
  System.out.println(nums.last());

  //返回小于4的子集,不包含4
  System.out.println(nums.headSet(4));

  //返回大于5的子集,如果Set中包含5,子集中还包含5
  System.out.println(nums.tailSet(5));

  //返回大于等于-3,小于4的子集。
  System.out.println(nums.subSet(-3 , 4));
    

与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构来存储集合元素。TreeSet支持两种排序方式: 自然排序、定制排序
1. 自然排序:

TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排序,即自然排序。如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序会抛出异常。

当把一个对象加入TreeSet集合中时,TreeSet会调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置。如果两个对象通过compareTo(Object obj)方法比较相等,新对象将无法添加到TreeSet集合中(牢记Set是不允许重复的概念)。

注意: 当需要把一个对象放入TreeSet中,重写该对象对应类的equals()方法时,应该保证该方法与compareTo(Object obj)方法有一致的结果,即如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较结果应该也为0(即相等)

看到这里,我们应该明白:

1) 对与Set来说,它定义了equals()为唯一性判断的标准,而对于到了具体的实现,HashSet、TreeSet来说,它们又会有自己特有的唯一性判断标准,只有同时满足了才能判定为唯一性
2) 我们在操作这些集合类的时候,对和唯一性判断有关的函数重写要重点关注

 2. 定制排序

TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排序。如果我们需要实现定制排序,则可以通过Comparator接口的帮助(类似php中的array_map回调处理函数的思想)。该接口里包含一个int compare(T o1, T o2)方法,该方法用于比较大小

import java.util.*;

class M

  int age;
  public M(int age)
  
    this.age = age;
  
  public String toString()
  
    return "M[age:" + age + "]";
  


public class TreeSetTest4

  public static void main(String[] args) 
  
    TreeSet ts = new TreeSet(new Comparator()
    
      //根据M对象的age属性来决定大小
      public int compare(Object o1, Object o2)
      
        M m1 = (M)o1;
        M m2 = (M)o2;
        return m1.age > m2.age ? -1
          : m1.age < m2.age ? 1 : 0;
      
    );	
    ts.add(new M(5));
    ts.add(new M(-3));
    ts.add(new M(9));
    System.out.println(ts);
  

看到这里,我们需要梳理一下关于排序的概念

1) equals、compareTo决定的是怎么比的问题,即用什么field进行大小比较
2) 自然排序、定制排序、Comparator决定的是谁大的问题,即按什么顺序(升序、降序)进行排序
它们的关注点是不同的,一定要注意区分

EnumSet

import java.util.*;

enum Season

    SPRING,SUMMER,FALL,WINTER

public class EnumSetTest

    public static void main(String[] args) 
    
  //创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值
  EnumSet es1 = EnumSet.allOf(Season.class);
  //输出[SPRING,SUMMER,FALL,WINTER]
  System.out.println(es1);

  //创建一个EnumSet空集合,指定其集合元素是Season类的枚举值。
  EnumSet es2 = EnumSet.noneOf(Season.class); 
  //输出[]
  System.out.println(es2); 
  //手动添加两个元素
  es2.add(Season.WINTER);
  es2.add(Season.SPRING);
  //输出[SPRING,WINTER]
  System.out.println(es2);

  //以指定枚举值创建EnumSet集合
  EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER); 
  //输出[SUMMER,WINTER]
  System.out.println(es3);

  EnumSet es4 = EnumSet.range(Se

以上是关于Stack和Queue:后进先出和先进先出和Java 实现后进先出(LIFO)及Stac和Java集合类: SetListMapQueue使用场景梳理 - .Little Hann的主要内容,如果未能解决你的问题,请参考以下文章

栈和队列

python实现stack(栈)和队列(queue)

队列Queue FIFO先进先出 栈Stack FILO先进后出

数据结构-栈和队列

39请用代码简答实现stack

栈、队列中“先进先出”,“后进先出”的含义