zoukankan      html  css  js  c++  java
  • Java入门(7)Java 集合框架(JCF, Java Collections Framework)

    序言

    List、Map、Set可以看做集合的三大类

    Java集合就像一个容器,可以将多个对象的引用丢进该容器中。

    Collection和Map是Java集合的根接口。

    List

    List代表一种线性表的数据结构, List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。


    ArrayList

    是一种顺序存储的线性表。ArrayList 底层采用数组来保存每个集合元素。线程不安全。ArrayList源码分析

    遍历List集合的三种方法

    List<String> list = new ArrayList<String>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    //方法一:foreach
    for(String attribute : list) {
      System.out.println(attribute);
    }
    //方法二:对于ArrayList来说速度比较快, 用for循环, 以size为条件遍历:
    for(int i = 0 ; i < list.size() ; i++) {
      System.out.println(list.get(i));
    }
    //方法三:集合类的通用遍历方式, 从很早的版本就有, 用迭代器迭代
    Iterator it = list.iterator();
    while(it.hasNext()) {
      System.out.println(it.next());
    }
    View Code

    LinkedList

    是一种链式存储的线性表。其本质上就是一个双向链表,但它不仅实现了 List 接口,还实现了 Deque 接口。

    也就是说LinkedList既可以当成双向链表使用,也可以当成队列使用,还可以当成来使用(Deque 代表双端队列,既具有队列的特征,也具有栈的特征)。线程不安全。

    @Test
        public void test2(){
            Queue<String> queue = new LinkedList<String>();
            queue.offer("Hello");
            queue.offer("World!");
            queue.offer("你好!");
            System.out.println("队列长度:"+queue.size());
            String str;
            while((str=queue.poll())!=null){
                System.out.println(str);
            }
            System.out.println("队列长度:"+queue.size());
        }
    Queue 队列

     Arraylist和Linklist区别:Java 的 List 集合本身就是线性表的实现,其中 ArrayList线性表的顺序存储实现;而LinkedList则是线性表的链式存储实现。

    1、Arraylist

    优点:它是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。

    缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低

    2、Linklist

    优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景。

    缺点:因为LinkedList要移动指针,所以查询操作性能比较低

    适用场景分析:当需要对数据进行多次访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList


    Vector

    向量类具体类:底层数据结构是数组。线程安全。

     

     Vector 其实就是 ArrayList 的线程安全版本, ArrayList 和 Vector 绝大部分方法的实现都是相同的,只是 Vector 的方法增加了 synchronized 修饰。

     ArrayList 的序列化实现比 Vector 的序列化实现更安全,因此 Vector 基本上已经被ArrayList 所代替了。 Vector 唯一的好处是它是线程安全的。


    Stack

    public class Stack<E> extends Vector<E>
    @Test
        public void test2(){
            Stack<String> stack = new Stack<String>();  
            System.out.println("now the stack is " + isEmpty(stack));  
            stack.push("1");  
            stack.push("2");  
            stack.push("3");  
            stack.push("4");  
            stack.push("5");  
            System.out.println("now the stack is " + isEmpty(stack));  
            System.out.println(stack.peek()); //peek 不改变栈的值(不删除栈顶的值)
            System.out.println(stack.pop()); //pop会把栈顶的值删除
            System.out.println(stack.search("1")); //方法调用返回从堆栈中,对象位于顶部的基于1的位置。 
        }
        public static String isEmpty(Stack<String> stack) {  
            return stack.empty() ? "empty" : "not empty";  
        }  
    Stack

    Deque

     

    Java也不再推荐使用Stack 类,而是推荐使用Deque实现类。

    在无需保证线程安全的情况下,程序完全可以使用ArrayDueue来代替Stack 类。

    从 JDK 1.6 开始,Java 为 Deque提供了一个常用的实现类ArrayDeque。就像List集合拥有 ArrayList 实现类一样,Deque集合则拥有ArrayDeque 实现类。

    @Test
        public void test1() {
            ArrayDeque stack = new ArrayDeque();
            // 依次将三个元素push入“栈”,先进后出
            stack.push("疯狂Java讲义");
            stack.push("轻量级Java EE企业应用实战");
            stack.push("疯狂Android讲义");
            System.out.println(stack); // [疯狂Android讲义, 轻量级Java EE企业应用实战, 疯狂Java讲义]
            System.out.println(stack.peek()); // 疯狂Android讲义
            System.out.println(stack); // [疯狂Android讲义, 轻量级Java EE企业应用实战, 疯狂Java讲义]
            System.out.println(stack.pop()); // 疯狂Android讲义
            System.out.println(stack);// [轻量级Java EE企业应用实战, 疯狂Java讲义]
    
            // 当做队列来使用,先进先出
            ArrayDeque queue = new ArrayDeque();
            queue.offer("疯狂Java讲义");
            queue.offer("轻量级JavaEE企业应用实践");
            queue.offer("疯狂Android讲义");
            System.out.println(queue); // [疯狂Java讲义, 轻量级JavaEE企业应用实践, 疯狂Android讲义]
            // 访问队列头部元素,但不将其poll出队列
            System.out.println(queue.peek());
            System.out.println(queue);
            // poll出第一个元素
            System.out.println(queue.poll());
            System.out.println(queue);// [轻量级JavaEE企业应用实践, 疯狂Android讲义]
        }
    ArrayDeque--stack || queue

    Deque 接口代表双端队列这种数据结构。 双端队列已经不再是简单的队列了,它既具有队列的性质先进先出( FIFO),也具有栈的性质( FILO),也就是说双端队列既是队列,也是栈。

    @Test
        public void test1(){
            Deque<String> deque = new LinkedList<String>();
            deque.add("d");
            deque.add("e");
            deque.add("f");
            
            //从队首取出元素,不会删除
            System.out.println("队首取出元素:"+deque.peek());
            System.out.println("队列为:"+deque);
            
            //从队首加入元素(队列有容量限制时用,无则用addFirst)
            deque.offerFirst("c");
            System.out.println("队首加入元素后为:"+deque);
            //从队尾加入元素(队列有容量限制时用,无则用addLast)
            deque.offerLast("g");
            System.out.println("队尾加入元素后为:"+deque);
            
            //队尾加入元素
            deque.offer("h");
            System.out.println("队尾加入元素后为:"+deque);
            
            //获取并移除队列第一个元素,pollFirst()也是,区别在于队列为空时,removeFirst会抛出NoSuchElementException异常,后者返回null
            deque.removeFirst();
            System.out.println("获取并移除队列第一个元素后为:"+deque);
            
            //获取并移除队列第一个元素,此方法与pollLast 唯一区别在于队列为空时,removeLast会抛出NoSuchElementException异常,后者返回null
            deque.removeLast();
            System.out.println("获取并移除队列最后一个元素后为:"+deque);
            
            //获取队列第一个元素.此方法与 peekFirst 唯一的不同在于:如果此双端队列为空,它将抛出NoSuchElementException,后者返回null
            System.out.println("获取队列第一个元素为:"+deque.getFirst());
            System.out.println("获取队列第一个元素后为:"+deque);
            
            //获取队列最后一个元素.此方法与 peekLast 唯一的不同在于:如果此双端队列为空,它将抛出NoSuchElementException,后者返回null
            System.out.println("获取队列最后一个元素为:"+deque.getLast());
            System.out.println("获取队列第一个元素后为:"+deque);
            
            //循环获取元素并在队列移除元素
            while(deque.size()>0){
                System.out.println("获取元素为:"+ deque.pop()+" 并删除");
            }
            System.out.println("队列为:"+deque);
        }
    Deque--LinkedList

    继承关系是:deque => queue => collection=》Iterable

    1.使用队列的时候,new LinkedList的时候为什么用deque接收,不用LinkedList呢?

    答:deque继承queue接口,因为它有两个实现,LinkedList与ArrayDeque。用deque接收是因为向上转型(子类往父类转,会丢失子类的特殊功能)了。可以试试,用get()方法,LinkedList接收才有。

    2.为什么有一个实现还不够,还弄两个呢,它们总有区别吧?

    答:ArrayDeque是基于头尾指针来实现的Deque,意味着不能访问除第一个和最后一个元素。想访问得用迭代器,可以正反迭代。

    ArrayDeque一般优于链表队列/双端队列,有限数量的垃圾产生(旧数组将被丢弃在扩展),建议使用deque,ArrayDeque优先。

    Map

    Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。


    HashMap

    JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。

    总结:HashMap根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,插入、删除和定位元素,HashMap是最好的选择。


    LinkedHashMap

    上篇文章讲了HashMap。HashMap是一种非常常见、非常有用的集合,但在多线程情况下使用不当会有线程安全问题。

    大多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap,不过HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。HashMap的这一缺点往往会带来困扰,因为有些场景,我们期待一个有序的Map。

    这个时候,LinkedHashMap就闪亮登场了,它虽然增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序该迭代顺序可以是插入顺序或者是访问顺序。

    LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。


    Hashtable(已废弃)

    虽然Hashtable比HashMap出现的早一些,但是现在Hashtable基本上已经被弃用了。而HashMap已经成为应用最为广泛的一种数据类型了。造成这样的原因一方面是因为Hashtable是线程安全的,效率比较低。

    HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。Dictionary类是一个已经被废弃的类(见其源码中的注释)。父类都被废弃,自然而然也没人用它的子类Hashtable了。


    TreeMap

    TreeMap是基于红黑树实现的一个保证有序性的Map 基于红黑树,所以TreeMap的时间复杂度是O(log n),如果需要有排序的数据,直接存放进TreeMap中就行,TreeMap自己会给排序,不需要再写排序代码。

    总结:TreeMap取出来的是排序后的键值对。插入、删除需要维护平衡会牺牲一些效率。但如果要按自然顺序或自定义顺序遍历,那么TreeMap会更好。


    TreeMap和HashMap区别

    TreeMap是排序的而HashMap不是。

    HashMap可以允许一个null key和多个null value。而TreeMap不允许null key,但是可以允许多个null value。

    HashMap的底层是Array,所以HashMap在添加,查找,删除等方法上面速度会非常快。而TreeMap的底层是一个Tree结构,所以速度会比较慢。

    另外HashMap因为要保存一个Array,所以会造成空间的浪费,而TreeMap只保存要保持的节点,所以占用的空间比较小。

    HashMap如果出现hash冲突的话,效率会变差,不过在java 8进行TreeNode转换之后,效率有很大的提升。

    TreeMap在添加和删除节点的时候会进行重排序,会对性能有所影响。

    两者都不允许duplicate key,两者都不是线程安全的。

    HashMap通过hashcode对其内容进行快速查找,而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap。

    HashMap通常比TreeMap效率要高一些,一个是哈希表,一个是二叉树,建议多使用HashMap,在需要排序的Map时候才用TreeMap。HashMap的查询速度比TreeMap要快。

    Set

    Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是不能集合里元素不允许重复的原因)。


    HashSet

    集合:底层数据结构是哈希表(是一个元素为链表的数组)

    HashSet内部是以HashMap的key来保存元素的

    HashSet就是限制了功能的HashMap,所以了解HashMap的实现原理,这个HashSet自然就通

    对于HashSet中保存的对象,主要要正确重写equals方法和hashCode方法,以保证放入Set对象的唯一性

    虽说时Set是对于重复的元素不放入,倒不如直接说是底层的Map直接把原值替代了(这个Set的put方法的返回值真有意思)

    HashSet没有提供get()方法,愿意是同HashMap一样,Set内部是无序的,只能通过迭代的方式获得


    TreeSet

    集合:底层数据结构是红黑树(是一个自平衡的二叉树);保证元素的排序方式

    TreeSet实际上是TreeMap实现的。当我们构造TreeSet时;若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。

    TreeMap是一个有序的二叉树,那么同理TreeSet同样也是一个有序的,它的作用是提供有序的Set集合。

    TreeSet中的元素支持2种排序方式:自然排序或者根据创建TreeSet 时提供的Comparator进行排序。这取决于使用的构造方法。


    LinkedHashSet

    集合:底层数据结构由哈希表和链表组成。

    LinkedHashSet是HashSet的一个“扩展版本”,HashSet并不管什么顺序,不同的是LinkedHashSet会维护“插入顺序”。

    HashSet内部使用HashMap对象来存储它的元素,而LinkedHashSet内部使用LinkedHashMap对象来存储和处理它的元素。


    HashSet与TreeSet和LinkedHashSet的区别

    • HashSet的输出顺序是不确定的,但是它的速度最快;
    • TreeSet输出顺序是升序排列的;
    • LinkedHashSet输出顺序就是插入时的顺序

    Iterator(迭代器)

      迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

      Java中的Iterator功能比较简单,并且只能单向移动:

      (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

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

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

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

      Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

    迭代器应用:

    list l = new ArrayList();
    l.add("aa");
    l.add("bb");
    l.add("cc");
    for (Iterator iter = l.iterator(); iter.hasNext();) {
    String str = (String)iter.next();
    System.out.println(str);
    }
    //迭代器用于while循环
    Iterator iter = l.iterator();
    while(iter.hasNext()){
    String str = (String) iter.next();
    System.out.println(str);
    }
    View Code

    总结

    关于集合中List、Map、Set这三个的总结如下:


    List

    ArrayList:非线程安全,适合随机查找和遍历,不适合插入和删除。

    LinkedList : 非线程安全,适合插入和删除,不适合查找。

    Vector : 线程安全。不过不推荐。


    Map

    HashMap:非线程安全,键和值都允许有null值存在。

    TreeMap:非线程安全,按自然顺序或自定义顺序遍历键(key)。

    LinkedHashMap:非线程安全,维护一个双链表,可以将里面的数据按写入的顺序读出。写入比HashMap强,新增和删除比HashMap差。

    ConcurrentHashMap:线程安全,Hashtable的升级版。推荐多线程使用。

    Hashtable:线程安全,键和值不允许有null值存在。不推荐使用。


    Set

    不允许重复的数据 。检索效率低下,删除和插入效率高。

    HashSet: 非线程安全、无序、数据可为空。

    TreeSet: 非线程安全、有序、数据不可为空。

    LinkedHashSet:非线程安全、无序、数据可为空。写入比HashSet强,新增和删除比HashSet差。

    虽然集合号称存储的是 Java 对象,但实际上并不会真正将Java对象放入Set集合中,而只是在 Set 集合中保留这些对象的引用而已。

    也就是说,Java 集合实际上是多个引用变量所组成的集合,这些引用变量指向实际的Java对象。

    就像引用类型的数组一样,当把 Java 对象放入数组之时,并不是真正把 Java 对象放入数组中,而只是把对象的引用放入数组中,每个数组元素都是一个引用变量。

    对于每个Java集合来说,其实它只是多个引用变量的集合。通俗讲Java集合保存的只是变量的地址而已。

    资料

    ArrayList,LinkedList,Vector集合的认识

    http://www.cnblogs.com/Java3y/p/8782788.html

    https://www.cnblogs.com/Java3y/p/8808818.html

  • 相关阅读:
    css小随笔
    正则表达式的疑问
    笔记本各型号CPU性能比较
    调整Ajax的ValidatorCalloutExtender绑定后的提示字体
    GridView显示空表头
    VB.net检测输入内容
    asp.net中System.DateTime.Now.ToString()的一些用法
    收藏的手机论坛
    常用或者将要用的技巧或代码(网摘)
    使用Ajax的MaskedEditExtender来限制输入内容
  • 原文地址:https://www.cnblogs.com/cnki/p/6727183.html
Copyright © 2011-2022 走看看