zoukankan      html  css  js  c++  java
  • 【Java学习笔记】集合框架的学习

    作者:gnuhpc
    出处:http://www.cnblogs.com/gnuhpc/

    1.集合框架是什么? 
    在Java语言中,Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(具体实现接口的类)。所有抽象出来的数据结构和操作(算法)统称为Java集合框架(Java Collection Framework)。Java程序员在具体应用时,不必考虑数据结构和算法实现细节,只需要用这些类创建出来一些对象,然后直接应用就可以了。这样就大大提高了编程效率。



     
                                                        Java 2集合框架图

    集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。 

    抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。 

    实现类:8个实现类(实线表示),对接口的具体实现。

    在很大程度上,一旦您理解了接口,您就理解了框架。虽然您总要创建接口特定的实现,但访问实际集合的方法应该限制在接口方法的使用上;因此,允许您更改基本的数据结构而不必改变其它代码。


     

                                                        Java 2简化集合框架图

     

    • Collection 接口是一组允许重复的对象。
    • Set 接口继承 Collection,但不允许重复,使用自己内部的一个排列机制。
    • List 接口继承 Collection,允许重复,以元素安插的次序来放置元素,不会重新排列。
    • Map接口是一组成对的键值对象,即所持有的是key-value pairs。Map中不能有重复的key。拥有自己的内部排列机制。
    • 容器中的元素类型都为Object。从容器取得元素时,必须把它转换成原来的类型。

     

    2.collection常用接口 
    boolean add(E e) 
              确保此 collection 包含指定的元素(可选操作)。 
    boolean addAll(Collection<? extends E> c) 
              将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。 
    void clear() 
              移除此 collection 中的所有元素(可选操作)。 
    boolean contains(Object o) 
              如果此 collection 包含指定的元素,则返回 true。 
    boolean containsAll(Collection<?> c) 
              如果此 collection 包含指定 collection 中的所有元素,则返回 true。 
    boolean equals(Object o) 
              比较此 collection 与指定对象是否相等。 
    int hashCode() 
              返回此 collection 的哈希码值。 
    boolean isEmpty() 
              如果此 collection 不包含元素,则返回 true。 
    Iterator<E> iterator() 
              返回在此 collection 的元素上进行迭代的迭代器。 
    boolean remove(Object o) 
              从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。 
    boolean removeAll(Collection<?> c) 
              移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。 
    boolean retainAll(Collection<?> c) 
              仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。 
    int size() 
              返回此 collection 中的元素数。 
    Object[] toArray() 
              返回包含此 collection 中所有元素的数组。 
    <T> T[] toArray(T[] a) 
              返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同。 

    所有实现了Collection接口的容器类都有一个为其子接口的iterator方法用以返回一个实现了Iterator接口的对象Iterator对象称作迭代器,用以方便的实现对容器内元素的遍历操作.
    Iterator接口定义了如下方法:
    boolean hasNext() 
              如果仍有元素可以迭代,则返回 true。 
    E next() 
              返回迭代的下一个元素。 
    void remove() 
              从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
    下面举例说明Collection和迭代器的一些常见用法:
    /**
    * 测试Collection接口中方法的常见用法
    * 测试迭代器的简单用法
    */
    package com.basic.collection;

    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Iterator;

    /**
    * @author johnston678
    *
    * @version 2009-05-06
    */
    public class TestCollection {

        /**
         * @param args
         */
        public static void main(String[] args) {
            
            Collection c = new HashSet();
            c.add("hello");
            //c.add(new Integer(100));
            System.out.println(c.size());
            c.remove("hello");
            System.out.println(c);
            c.add("Tom");
            c.add("Jack");
            Iterator i = c.iterator();
            while (i.hasNext()) {
                //next()返回值为Object类型,需要转换为相应的类型
                String str = (String) i.next();
                System.out.println(str);
            }
        }
    }

    以上的while也可以使用高级的for循环:
    for(string str :c)  {
        System.out.println("elements: " + str);
    }

    3.扩展的collection--set接口的实现 
    以处理集合,其中元素必须唯一。

    Set实现可分为通用Set实现和专有Set实现两类。

    通用Set实现

    Java提供了三个通用 Set 实现—— HashSet,TreeSet和LinkedHashSet。

    HashSet采用散列函数对元素进行排序,是专门为快速查询而设计的。存入HashSet的对象必须定义hashCode()。

    值得注意的是:由于 HashSet在内存中数据空间为线性分配。因此,如果初始的容量过大则会浪费大量的空间和时间;如果初始容量过小将会因为增容而不得不复制已有的数据集到新缓冲区中,从而浪费时间。如果你不指定初始容量,默认为16,当容量不足时以成倍的形式增加容量。下列代码创建了一个初始容量为64的 HashSet:

    Set<String> s = new HashSet<String>(64);

    TreeSet采用红黑树的数据结构进行排序元素,能保证元素的次序,使用它可以从Set中提取有序的序列。

    HashSet比TreeSet提供更快的访问速度,但无法保证访问顺序。用HashSet还是TreeSet取决于你是否需要顺序访问。

    LinkedHashSet以链表的形式实现哈希表,它能提按照插入顺序迭代访问。

    专有Set实现

    专有Set实现有两种——EnumSet 和 CopyOnWriteArraySet。

    EnumSet是为枚举类的Set提供的高性能Set。其成员在内部表示为位向量,具有很好的空间和时间性能。其迭代器能按其自然顺序(声明枚举常量的顺序)遍历这些元素。

    EnumSet类提供了一个静态的函数range,用以简单的创建其实例:

    for (Day d : EnumSet.range(Day.MONDAY, Day.FRIDAY))
        System.out.println(d);

    此外,也可通过of函数来创建包含指定枚举的EnumSet:

            EnumSet.of(Style.BOLD, Style.ITALIC)

    CopyOnWriteArraySet 在内部通过copy-on-write数组实现。对Set的add, set, 及remove操作都会创建数组的副本。迭代操作甚至可以和插入删除操作同时进行。和大多数Set实现不同,add, remove和 contains操作需要的时间和set的大小成比例。该实现仅适用于那些很少修改,而需要经常进行迭代操作的地方。 

    4.Map实现 

    通用Map 实现
    通用Map实现有三个——HashMap,TreeMap和LinkedHashMap。

    和Set实现类似,如果你需要进行SortedMap操作或按key大小顺序迭代操作,或者快速得到第一个值或者第二个值,请使用TreeMap。如果你需要最快的查询速度而不关心迭代顺序,请使用HashMap。如果你需要接近HashMap的性能,同时需要按照插入顺序迭代,请使用LinkedHashMap。

    专有Map实现
    专有Map实现有三个——EnumMap, WeakHashMap和IdentityHashMap。

    EnumMap在内部以数组方式实现,能为枚举提供高性能和线程安全的查询操作。如果想实现枚举类型的Map,EnumMap无疑是性能最佳的选择。

    WeakHashMap也是Map接口的一种实现,它的Key值存储的只是对象的弱引用。这意味着:当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除,因此,因此该类的行为与其他的 Map 实现有所不同。

    IdentityHashMap通过哈希表实现 Map 接口,比较键(和值)时使用引用相等性代替对象相等性。在 IdentityHashMap 中,当且仅当 (k1==k2) 时,才认为两个键 k1 和 k2 相等(在正常 Map 实现(如 HashMap)中,当且仅当满足下列条件时才认为两个键 k1 和 k2 相等:(k1==null ? k2==null : e1.equals(e2)))。

    此类不是通用 Map 实现!此类实现 Map 接口时,它有意违反 Map 的常规协定,该协定在比较对象时强制使用 equals 方法。此类设计仅用于其中需要引用相等性语义的罕见情况。

    此类的典型用法是拓扑保留对象图形转换,如序列化或深层复制。要执行这样的转换,程序必须维护用于跟踪所有已处理对象引用的"节点表"。节点表一定不等于不同对象,即使它们偶然相等也如此。此类的另一种典型用法是维护代理对象。例如,调试设施可能希望为正在调试程序中的每个对象维护代理对象。



    5.Queue 
    Queue是通过把对象添加(称为enqueuing的操作) 到Queue的后部并通过从Queue的前部提取对象而从Queue中移除(称为dequeuing的操作)来模拟。

    add()向队列中添加元素,出错则抛出异常,offer()也是向队列中添加元素,但是出错时返回错误码。
    remove()取得队列中的元素并且删除该元素,出错则抛出异常,offer()也是取得队列中的元素并且删除该元素,但是出错时返回错误码。
    element()和poll()返回下一个元素,但是不删除。

    Queue有几个子类是线程安全的,而有三个是不安全的,或者说适用于单线程的。先介绍这三个线程不安全的吧。

    PriorityQueue--当你添加元素到Queue中时,实现了自动排序。根据你使用的PriorityQueue的不同构造器,Queue元素的顺序要么基于他们的自然顺序要么通过PriorirtyQueue构造器传入的Comparator来确定。你可以指定大小,若你在乎效率的话,但是它其实是会自动改变大小的。

    举例:
    import java.util.PriorityQueue;

    public class CollectionThirteen {
        public static void main(String arg[]) {
            PriorityQueue<String> queue = new PriorityQueue<String>();
            queue.add("charlie");
            queue.add("baker");
            queue.add("dog");
            queue.add("fox");
            queue.add("able");
            queue.add("easy");
            while(queue.size() > 0) {
                String str = queue.poll();
                System.out.println(str);
            }
        }
    }


    ArrayDeque--扩展AbstractCollection和实现Deque接口,允许动态双端队列,可以用迭代器,并且提供了先进后出的方法,可以用于模拟stack。而Stack是Vector的一个子类,它实现了标准的后进先出堆栈。在JDK5中更新为泛型。尽管Stack没有被建议不使用,但随着Java SE6的发布,ArrayDeque是一个更好的选择。 
    例如:在本例中你要是想模拟stack,那么可以使用push方法压入元素,然后用pop取出,否则就是先进先出
    import java.util.ArrayDeque;

    public class CollectionFourteen {
        public static void main(String arg[]) {
            ArrayDeque<String> queue = new ArrayDeque<String>();
            queue.add("charlie");
            queue.add("baker");
            queue.add("dog");
            queue.add("fox");
            queue.add("able");
            queue.add("easy");
            while(queue.size() > 0) {
                String str = queue.poll();
                System.out.println(str);
            }
        }
    }

    LinkedList--扩展AbstractSequentialList实现链表。实现了List,Deque和Queue接口,可以实现链表和stack(因为有push和pop方法)。 
    例如:本例与上例基本一致,改变为stack的方式也相同。
    import java.util.LinkedList;

    public class CollectionFifteen {
        public static void main(String arg[]) {
            LinkedList<String> queue = new LinkedList<String>();
            queue.add("charlie");
            queue.add("baker");
            queue.add("dog");
            queue.add("fox");
            queue.add("able");
            queue.add("easy");
            while(queue.size() > 0) {
                String str = queue.poll();
                System.out.println(str);
            }
        }
    }

    6.集合的转化 
    以下这个例子是将TreeSet中的元素转化为数组。
    import java.util.TreeSet;

    public class CollectionSixteen {
        public static void main(String arg[]) {
            new CollectionSixteen();
        }
        CollectionSixteen() {
            TreeSet<String> set = new TreeSet<String>();
            set.add("Orange");
            set.add("Peach");
            set.add("Apple");
            set.add("Banana");
            set.add("Strawberry");
            set.add("Pear");
            Object obj[] = set.toArray();
            for(int i=0; i<obj.length; i++)
                System.out.println((String)obj[i]);
        }
    }

    这个其实等同于:
    import java.util.TreeSet;

    public class CollectionSeventeen {
        public static void main(String arg[]) {
            new CollectionSeventeen();
        }
        CollectionSeventeen() {
            TreeSet<String> set = new TreeSet<String>();
            set.add("Orange");
            set.add("Peach");
            set.add("Apple");
            set.add("Banana");
            set.add("Strawberry");
            set.add("Pear");
            String str[] = new String[set.size()];
            set.toArray(str);
            for(int i=0; i<str.length; i++)
                System.out.println(str[i]);//与上例唯一的不同就是在使用的时候不用在类型转换了。
        }
    }

    我们再看一个关于HashSet到TreeSet的转化:
    import java.util.HashSet;
    import java.util.TreeSet;

    public class CollectionEighteen {
        public static void main(String arg[]) {
            new CollectionEighteen();
        }
        CollectionEighteen() {
            HashSet<String> hashSet = new HashSet<String>();
            hashSet.add("Agamemnon");
            hashSet.add("Cato");
            hashSet.add("Socrates");
            hashSet.add("Plato");
            hashSet.add("Zeno");
            hashSet.add("Thucydides");
            hashSet.add("Archimedes");
            for(String str : hashSet)
                System.out.println(str);
            System.out.println();
            TreeSet<String> treeSet = new TreeSet<String>(hashSet);
            for(String str : treeSet)
                System.out.println(str);
        }
    }

    7.线程安全 

    ConcurrentSkipListMap--实现ConcurrentNavigableMap接口,可量测的,并发的,可跳跃的列表实现。
    ConcurrentHashMap--该实现提供了相同的基本线程安全的 Map 功能,但它大大提高了并发性。 
    ConcurrentLinkedQueue--是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们(先进先出),所以只要不需要知道队列的大小(自动调整大小),ConcurrentLinkedQueue对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。
    SynchronousQueue--是最简单的。它没有内部容量。它就像线程之间的手递手机制。在队列中加入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在消费者和生产者之间传递,永远不会加入到阻塞队列中。可以有多个生产者和消费者,但是都会被自动同步。
    LinkedBlockingQueue--一个由链接节点支持的可选有界队列。当一个消费者试图读取一个空的LinkedBlockingQueue时,它不返回,read方法阻塞等待生产者的出现。
    例如:
    import java.util.concurrent.LinkedBlockingQueue;

    public class CollectionNineteen {
        public static void main(String arg[]) {
            LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
            Sender sender = new Sender(queue);
            sender.start();
            Receiver receiver = new Receiver(queue);
            receiver.start();
        }
    }
    --------------------------------------
    import java.util.concurrent.LinkedBlockingQueue;

    public class Sender extends Thread {
        LinkedBlockingQueue<Integer> queue;
        Sender(LinkedBlockingQueue<Integer> queue) {
            this.queue = queue;
        }
        public void run() {
            while(true) {
                for(int i=0; i<=5; i++) {
                    Integer integer = new Integer(i);
                    System.out.println("Sender: " + integer);
                    queue.add(integer);
                    try {
                        sleep(2000L);
                    } catch(InterruptedException e) {
                        return;
                    }
                }
            }
        }
    }
    ---------------------------------
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.LinkedBlockingQueue;

    public class Receiver extends Thread {
        LinkedBlockingQueue<Integer> queue;
        Receiver(LinkedBlockingQueue<Integer> queue) {
            this.queue = queue;
        }
        public void run() {
            while(true) {
                Integer integer;
                try {
                    integer = queue.poll(60L,TimeUnit.SECONDS);
                    System.out.println("Received: " + integer);
                    sleep(integer.intValue() * 1000L);
                } catch(InterruptedException e) {
                    return;
                }
            }
        }
    }
    +++++++++++++++++++++++++++++++++++++++++++++++++++++

    ArrayBlockingQueue--一个由数组支持的有界队列。ArrayBlockingQueue 和 LinkedBlockingQueue 几乎相同,只是在底层存储实现方面有所不同,LinkedBlockingQueue 并不总是有容量界限。无大小界限的 LinkedBlockingQueue 类在添加元素时永远不会有阻塞队列的等待(至少在其中有 Integer.MAX_VALUE 元素之前不会,这个时候采用的容量限制即是Integer.MAX_VALUE)。

    注意不要混淆Collection和Collections,前者是集合框架的基础接口,后者是这个框架下的帮助类,提供一些静态方法实现对各种集合的搜索、排序、线程安全化等操作。



    参考文献:http://www.cnblogs.com/TianFang/archive/2007/10/18/929646.html

    作者:gnuhpc
    出处:http://www.cnblogs.com/gnuhpc/

  • 相关阅读:
    20165204 Java第五周学习总结
    20165204Java第四周学习
    20165204Java第四周课上补做
    20175213 2018-2019-2 《Java程序设计》第6周学习总结
    类定义(课下选做) 20175213
    2018-2019-2 20175213实验一 《Java开发环境的熟悉》实验报告
    20175213 2018-2019-2 《Java程序设计》第4周学习总结
    # 20175213 2018-2019-2 《Java程序设计》第1周学习总结
    # 20175213 2018-2019-2 《Java程序设计》第2周学习总结
    20175213 2018-2019-2 《Java程序设计》第3周学习总结
  • 原文地址:https://www.cnblogs.com/gnuhpc/p/2822253.html
Copyright © 2011-2022 走看看