• 2、容器初探


    简单容器的分类:

    1. 简单容器的分类

    在“图1”中总结出了常用容器的简单关系。可以看到,只有4类容器:ListSetQueueMap。上图中虚线框表示一个接口,实线框表示一个具体的实现类,虚线箭头线表示一种“实现”关系,实线箭头线表示一种“继承”关系。红线箭头不表示实现与继承关系。

    为了理清楚“图1”中简单容器的关系,首先从迭代器(Iterator)的作用开始讲解。

    一、迭代器

    迭代器是一个对象,它的工作是遍历并选择容器中的对象,而程序员不必要知道或关心容器的底层实现(无论ListQueueSet都采用同样的方式来对待)。迭代器是一个轻量级的对象,创建它的代价很小,同样功能也简单(比如:只能单向移动)。只具备如下的功能:

    ①、使用方法iterator()要求容器返回一个IteratorIterator将准备好返回容器的第一个元素。

    ②、使用next()获得容器的下一个元素。

    ③、使用hasNext()检查容器中是否还有元素。

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

    迭代器真正的强大在于将遍历容器的操作与容器底层结构相分离。

    例如:打印包含有Person对象的容器可以写下面一个方法,只需要向打印方法中传入一个迭代器即可,至于这个容器到底是List还是Set根本不需要关心。

    1 void printPerson(Iterator<Person> itr){
    2     if(itr == null)
    3         return;
    4     while(itr.hasNext()){
    5         Person p = itr.next();
    6         System.out.println(p.getName() + " " + p.getAge());
    7     }
    8 }

    Collection接口中就定义有iterator()方法,所以如果容器是ListSetQueue则只需要直接调用对象的iterator()方法便可以得到一个迭代器对象。

    但是,Map容器并没有实现Collection接口,那么一个Map类型的容器该如何获得一个迭代器对象呢?Map定义了三个Collection视角的方法:

    ①、map.entrySet(); 返回一个Set<Entry<T, E>>对象,SetSet<Entry<T, E>>对象中有iterator()方法。

    ②、map.keySet();  返回一个Set<T>对象,Set<T>对象中有iterator()方法。

    ③、map.values(); 返回一个Collection<T>对象,Collection<T>对象中有iterator()方法。

    下面是一个迭代器用于Map的例子:

     1 import java.util.*;
     2 import java.util.Map.Entry;
     3 public class Main{
     4     public static void main(String args[]){
     5         
     6         Map<Integer, String> map = new HashMap<>();
     7         map.put(1, "aaa");
     8         map.put(2, "bbb");
     9         map.put(3, "ccc");
    10         map.put(4, "ddd");
    11         
    12         Set<Entry<Integer, String>> setEntry = map.entrySet();
    13         Iterator<Entry<Integer, String>> itrEntry = setEntry.iterator();
    14         System.out.println("--------map.entrySet()--------");
    15         while(itrEntry.hasNext()){
    16             Entry<Integer, String> entry = itrEntry.next();
    17             Integer key = entry.getKey();
    18             String val = entry.getValue();
    19             System.out.print(key + ":" + val + " ");
    20         }
    21         
    22         Set<Integer> setKey = map.keySet();
    23         Iterator<Integer> itrKey = setKey.iterator();
    24         System.out.println("
    
    --------map.keySet()--------");
    25         while(itrKey.hasNext()){
    26             Integer key = itrKey.next();
    27             System.out.print(key + " ");
    28         }
    29         
    30         Collection<String> values = map.values();
    31         Iterator<String> itrValues = values.iterator();
    32         System.out.println("
    
    --------map.values()--------");
    33         while(itrValues.hasNext()){
    34             String val = itrValues.next();
    35             System.out.print(val + " ");
    36         }
    37     }
    Map中使用迭代器

    运行结果:

    除此之外,ListIterator是一个更强大的Iterator的子类型,它只能够用于各种List类的访问。尽管Iterator只能够单向移动,但是ListIterator却能够双向移动。

    二、List

    public interface List<E> extends Collection<E> 可以看出,List接口直接继承Collection接口。

    下面主要讲List接口的两个具体实现类ArrayListLinkedList

    ArrayList底层的具体实现是一个Object[]对象。

    在其调用add方法的时候会先检查底层Object[]对象是否还有足够的空间,如果空间足够则将元素添加到末尾;如果没有足够的空间则创建一个更大的Object[]数组,并将旧数组中的数据复制到新数组中间去。

    扩容过程调用elementData = Arrays.copyOf(elementData, newCapacity) 来完成,elementData 就是ArrayList底层Object[]对象。copyOf方法最终会调用下面的方法来实现:

    1  public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    2    @SuppressWarnings("unchecked")
    3    T[] copy = ((Object)newType == (Object)Object[].class)
    4             ? (T[]) new Object[newLength]
    5             : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    6    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    7    return copy;
    8  }

    可以看出,扩容原理就是新创建一个相同类型的新数组,同时将老数组中的数据复制到新数组中间去。

    其实,继续追踪System.arraycopy的具体实现,可以引出另外一个问题:

    public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 

    啊哈,JDK中根本看不见System.arraycopy的源代码。却发现了native这个关键字。

    tips:

    之前没去深入了解过底层的东西,在CSDN中找到一篇简要介绍native关键字的博客。http://blog.csdn.net/youjianbo_han_87/article/details/2586375 

    下面是博客的原文:

    native关键字用法

    native是与C++联合开发的时候用的!java自己开发不用的!

    使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。 这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。

    1. native 是用做java 和其他语言(如c++)进行协作时用的也就是native 后的函数的实现不是用java写的
    2. 既然都不是java,那就别管它的源代码了,呵呵

    native的意思就是通知操作系统, 这个函数你必须给我实现,因为我要使用。 所以native关键字的函数都是操作系统实现的, java只能调用。

    java是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了

    LinkedList底层的具体实现是一个链表结构。

    直接看其add方法的源码:

     public boolean add(E e) {
            linkLast(e);
            return true;
      }

    继续查看linkLast的源码:

    void linkLast(E e) {
      final Node<E> l = last;
      final Node<E> newNode = new Node<>(l, e, null);
      last = newNode;
      if (l == null)
        first = newNode;
      else
        l.next = newNode;
      size++;
      modCount++;
    }

    恩,没错。就是在链表末尾添加一个新的节点。

    对比ArrayListLinkedList的区别:

    ①、底层实现不同,ArrayList底层是数组,LinkedList底层是一个单向链表。

    ②、从底层实现分析特性:ArrayList擅长于随机访问,但是对中间位置的插入和删除操作较慢;LinkedList则删除与插入和删除操作,随机访问速度较慢。

    ③、两个list都是通过位置索引编号来查询元素的。

    三、Set

    Set集合最大的特性是:集合中的元素不能重复。

    Set最常被使用的一个功能就是测试归属性,可以很容易的查询某个元素是否在某个Set中。所以,查找就成了Set集合中最重要的操作。通常使用HashSet的实现,它对快速查找进行了优化。

    HashSet使用了散列,元素的迭代输出没有任何规律性。

    TreeSet 将元素存储在“红-黑树”数据结构中,元素的迭代输出按照递增的方式进行。

    LinkedHashSet 使用了散列,看起来它使用了链表来维护元素的插入顺序。

    其底层实现后面再专门研究。

    四、Queue

    队列是一种典型的先进先出FIFO容器。其在并发编程中特别重要。

    可以看到Queue有很多的具体实现类。但是,现在只关注其中的一个实现类:LinkedList。没错,LinkedList实现了Queue接口,那么我们可以将一个LinkedList向上转型为一个Queue,从而实现基本的FIFO操作。

    下面介绍几个和Queue相关的方法:

    ①、boolean offer(E e)方法在允许的情况下将一个元素插入到队尾,或者返回false

    ②、peek()element()方法将在不移除的情况下返回队头。队列为空,peek()返回nullelement()抛出异常。

    ③、poll()remove()方法将移除并返回队头。队列为空,poll()返回nullremove()抛出异常。

    五、Map

    Map具有将对象映射到对象的能力。

    Map可以和其它的Collection一样,很容器将其扩展到多维。只需要将其值设置为Map(扩展Map的值可以是其它容器,甚至是其它Map)。所以,Map的组合可以快速的生成强大的数据结构,比如:随机数分布统计应用等。

    HashMap 使用散列,查找速度快。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能。

    TreeMap  基于红黑树的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparable或Comparator决定)。TreeMap的特点在于,所得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

    LinkedHashMap  类似于HashMap,但是迭代器遍历它时,取得“键值对”的顺序是其插入时的次序,或者是最近最少使用LRU的次序。只比HashMap慢一点;而在迭代访问时反而更快,因为它使用链表维护内部次序。

    后面深入研究Map的具体实现。如果没有特别的限制,应该默认使用HashMap,因为它对速度进行了优化。

    Map使用“键”的要求:

    ①、任何“键”都必须具有一个equals()方法(这也解释了为什么容器中不能放置原始数据类型,而只能放包装数据类型)。

    ②、如果“键”被用于散列Map,那么它还必须具有恰当的hashCode()方法

    ③、如果“键”被用于TreeMap,它必须实现Comparable接口

    六、Foreach与迭代器

    看一个foreach很简单的例子:

    void foreachTest(){
        int[] arr = {1,2,3,4,5,6,7,8}; 
        for(int i : arr){
            System.out.print(i + " ");
        }
    }

    很简洁的就输出了数组中的所有元素,根本不需要关心数组中元素的个数。

    foreach能够用于数组,那么,能够用于上面介绍的容器吗?如果我们自己定义一个奇葩容器,它能用foreach输出吗?

    原理:foreach之所以能够工作,是因为java SE5引入了新的被称为Iterable的接口,该接口包含了一个能够产生Iterator对象的iterator()方法,并且Iterable接口被foreach用来在序列中移动。所以,不难理解,如果你创建了任何实现Iterable接口的类都能够将它用于foreach语句中。

    为了印证上面对于foreach的原理,下面定义一个类,它实现了Iterable接口,那么这个类的对象就可以应用于foreach语句中。

     1 import java.util.*;
     2 public class IterableTest implements Iterable<String>{
     3     private String[] str = {"aaa","bbb","ccc","ddd","eee"};
     4     
     5     //重写iterator()方法,使得其支持foreach
     6     public Iterator<String> iterator() {
     7         return new Iterator<String>(){//匿名内部类
     8             private int index = 0;
     9             public boolean hasNext() {
    10                 if(index < str.length){
    11                     return true;
    12                 }
    13                 return false;
    14             }
    15             public String next() {
    16                 return str[index++];
    17             }
    18         };
    19     }
    20 
    21     //测试看IterableTest对象是否能够运用于foreach中
    22     public static void main(String[] args){
    23         IterableTest itrTst = new IterableTest();
    24         for(String str : itrTst){//IterableTest 对象用于foreach语句中
    25             System.out.print(str + " ");
    26         }
    27     }
    28 }

    现在提出一个新的问题:对于IterableTest 类,我们希望在默认前向迭代器的基础上,添加产生反向迭代器的能力。因此,我们不能够使用覆盖,而是添加一个能够产生Iterable对象的方法,该方法可以用于foreach语句。

    改造代码如下:

     1 import java.util.*;
     2 public class IterableTest implements Iterable<String>{
     3     
     4     private String[] str = {"aaa","bbb","ccc","ddd","eee"};
     5     
     6     //重写iterator()方法,使得其支持foreach
     7     public Iterator<String> iterator() {
     8         return new Iterator<String>(){//匿名内部类
     9             private int index = 0;
    10             
    11             public boolean hasNext() {
    12                 if(index < str.length){
    13                     return true;
    14                 }
    15                 return false;
    16             }
    17             public String next() {
    18                 return str[index++];
    19             }
    20         };
    21     }
    22     /*
    23      * 通过reversed()方法得到一个Iterable对象,该对象重写了
    24      * iterator()方法实现了反向迭代器的功能
    25      */
    26     public Iterable<String> reversed(){
    27         return new Iterable<String>(){
    28             public Iterator<String> iterator() {
    29                 return new Iterator<String>(){
    30                     private int current = str.length - 1;
    31                     
    32                     public boolean hasNext() {
    33                         if(current >= 0){
    34                             return true;
    35                         }
    36                         return false;
    37                     }
    38 
    39                     public String next() {
    40                         return str[current--];
    41                     }
    42                 };
    43             }
    44         };
    45     }
    46     
    47     //测试看IterableTest对象是否能够运用于foreach中
    48     public static void main(String[] args){
    49         IterableTest itrTst = new IterableTest();
    50         for(String str : itrTst){//正向迭代器
    51             System.out.print(str + " ");
    52         }
    53         
    54         System.out.println();
    55         for(String str : itrTst.reversed()){//反向迭代器
    56             System.out.print(str + " ");
    57         }
    58     }
    59 }
  • 相关阅读:
    好友消息和群消息区别
    好友消息和群消息区别
    完整的微信登陆 接收消息流程
    完整的微信登陆 接收消息流程
    perl 对象 通过bless实现
    perl 对象 通过bless实现
    00019_数组的定义
    关于tinymce粘贴图片自动上传
    关于kindeditor粘贴图片自动上传
    关于ckeditor粘贴图片自动上传
  • 原文地址:https://www.cnblogs.com/lj95801/p/5361265.html
Copyright © 2020-2023  润新知