• 数学知识巧学JCF(Java Collections framework)


     

     不知你是否还记得高中我们学过的集合,映射,函数,数学确实很牛逼,拿它来研究java集合类,轻而易举的就把知识理解了。本篇文章适合初学java集合类的小白,也适合补充知识漏缺的学习者,同时也是面试者可以参考的一份资料。

    数学知识

    回顾一下之前所学的知识,结合我多年的高中数学教学经验,相信你会对某些知识有一些新的感悟。

    集合:一般地,我们把研究对象统称为元素(element),把一些元素组成的总体叫做集合(set)。

    对于一个给定的集合,其具有的特征:

    确定性:集合中的元素都是确定的。

    互异性:集合中的元素都是不同的。

    无序性:集合中的元素的顺序是无序的。

    映射:一般地,我们有:

    设A,B是两个非空的集合,如果按照某一个确定的对应关系f.是对应集合A中的任意一个元素x,在集合B中都有唯一确定的元素y与之对应,那么就称对应f:A—>B为集合A到集合B的一个映射(mapping)。

    其实简单的来讲,何谓映射,就是函数上将的关系对应,例如:

    函数 f(x)=x^2  那么每一个x都有唯一的y与之对应,这就是映射关系的一个模型。

    而方程 x^2+y^2=1,这个很明显是圆心为(0,0)的半径为1的圆,任取一个x可能会有一个或者两个y与之对应,这就不能称为映射,进而不能称为函数。(1,0)或者(-1,0)这时候的x只有唯一的确定的y和它对应。

    集合类的学习

    集合类产生的原因:在一般的情况下,我们在写程序时并不知道将需要多少个对象,或者是否需要更加复杂的方式存储对象,显然使用具有固定长度的数组已经不能解决这个问题了。所以java 实用类库提供了一套相当完整的容器类来解决这个问题。

    基本概念

    java容器类类库的用途是“保存对象”,可将其划分为两个不同的概念:

    1)collection.独立元素的序列。主要包含List(序列),Set(集合),Queue(队列)

    List:按照插入的顺序保存元素;

    Set:不能有重复的元素;

    Queue:按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同);

    2)Map:一组成对的“键值对”对象,允许我们使用键来查找值。

    针对经常使用的类库,我们只列出List,Set,Map之间的继承关系:

    List

    List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和删除元素。

    继承自List的子类有ArrayList,   LinkedList ,Vector三类。

    list的特征:

    1 有序的Collection
    2 允许重复的元素,允许空的元素。
    3 插入类似的数据:{1,2,4,{5,2},1,3};

    ArrayList(类似于顺序表)

    其主要用于查找,对于删除和插入,耗时巨大。ArrayList是以数组实现的列表,不支持同步。

    优点:利用索引位置可以快速的定位访问

       适合变动不大,主要用于查询的数据

             和java的数组相比较,其容量是可以动态调整的。

         缺点:不适合指定位置的插入,删除操作。

    --ArrayList在元素填满容器是会自动扩充容器大小的50%

    ArrayListTest 代码分析:

    add()方法,添加元素,默认是在后面添加。

    add(index,value),在指定索引处添加元素。会进行元素的移动。源码如下:

    1 public void add(int index, E element) {
    2         rangeCheckForAdd(index);
    3         ensureCapacityInternal(size + 1);  // Increments modCount!!
    4         System.arraycopy(elementData, index, elementData, index + 1,
    5                          size - index);
    6         elementData[index] = element;
    7         size++;
    8  }

    remove(index)删除指定位置上的元素。源码如下:

     1 public E remove(int index) {
     2         rangeCheck(index);
     3         modCount++;
     4         E oldValue = elementData(index);
     5         int numMoved = size - index - 1;
     6         if (numMoved > 0)
     7             System.arraycopy(elementData, index+1, elementData, index,
     8                              numMoved);
     9         elementData[--size] = null; // clear to let GC do its work
    10         return oldValue;
    11     }

      从源码可以分析出,在ArrayList进行插入和删除的时候,会进行类似顺序表的操作,移动元素,空出位置,然后插入元素。删除:依次移动后面的元素覆盖指定位置的元素。这就会大大减慢ArrayList插入和删除的效率。

    举一个应用的例子,更好的理解ArrayList:

     1 public class ArrayListTest {
     2   public static void main(String[] args) {
     3      //泛型的用法,只允许Integer类型的元素插入。
     4     ArrayList<Integer> arrayList =new ArrayList<Integer>();
     5   //增加元素 
     6            arrayList.add(2);
     7     arrayList.add(3);
     8     arrayList.add(4);
     9     arrayList.add(5);
    10     arrayList.add(4);
    11     arrayList.add(null);//ArrayList允许空值插入,
    12     arrayList.add(new Integer(3));
    13     System.out.println(arrayList);//   [2, 3, 4, 5, 4, null, 3]
    14   //查看元素的个数
    15            System.out.println(arrayList.size());// 7
    16     arrayList.remove(0);
    17     System.out.println(arrayList);//  [3, 4, 5, 4, null, 3]
    18     arrayList.add(1, new Integer(9)); 
    19     System.out.println(arrayList);//   [3, 9, 4, 5, 4, null, 3]
    20     System.out.println("-----------遍历方法-------");
    21     ArrayList<Integer> as=new ArrayList<Integer>(100000);
    22     for(int i=0;i<100000;i++){
    23       as.add(i);
    24     }
    25     traverseByIterator(as);
    26     traverseByFor(as);
    27     traverseByForEach(as);
    28   }
    29   public static void traverseByIterator(ArrayList<Integer>al){
    30     System.out.println("---------迭代器遍历-------------");
    31     long startTime=System.nanoTime();//开始时间
    32     Iterator it=al.iterator();
    33     while(it.hasNext()){//
    34       it.next();
    35     }
    36     long endTime=System.nanoTime();//结束时间
    37     System.out.println((endTime-startTime)+"纳秒");
    38   }
    39   public static void traverseByFor(ArrayList<Integer>al){
    40     System.out.println("---------索引遍历-------------");
    41     long startTime=System.nanoTime();//开始时间
    42     for(int i=0;i<al.size();i++) al.get(i);
    43     long endTime=System.nanoTime();//结束时间
    44     System.out.println((endTime-startTime)+"纳秒");
    45   }
    46   public static void traverseByForEach(ArrayList<Integer>al){
    47     System.out.println("---------Foreach遍历-------------");
    48     long startTime=System.nanoTime();//开始时间
    49     for(Integer temp:al);
    50     long endTime=System.nanoTime();//结束时间
    51     System.out.println((endTime-startTime)+"纳秒");
    52   }
    53 }
    54 -----------遍历方法-------
    55 ---------迭代器遍历-------------
    56 10407039纳秒
    57 ---------索引遍历-------------
    58 7094470纳秒
    59 ---------Foreach遍历-------------
    60 9063813纳秒
    61 可以看到利用索引遍历,相对来说是快一些。
    View Code

    迭代器 Iterator

    1 hasNext()  判断是否有下一个元素
    2 next()    获取下一个元素
    3 remove ()  删除某个元素

    LinkedList:(主要用于增加和修改!)

        --以双向链表实现的列表,不支持同步。

        --可以被当做堆栈、队列和双端队列进行操作

        --顺序访问高效,随机访问较差,中间插入和删除高效

        --适合经常变化的数据

        addFirst()在头部添加元素

        add(3,10);将10插入到第四个位置上

        remove(3)删除第四个位置的元素

    代码详解:

     1 public class LinkedListTest {
     2   public static void main(String[] args) {
     3     LinkedList<Integer> linkedList=new LinkedList<Integer>();
     4     linkedList.add(2);
     5     linkedList.add(3);
     6     linkedList.add(9);
     7     linkedList.add(6);
     8     linkedList.add(7);
     9     System.out.println(linkedList);
    10     //linkedList.addFirst(1);
    11     //linkedList.addLast(10);
    12     //System.out.println(linkedList);
    13     linkedList.add(3, 4);
    14     System.out.println(linkedList);
    15     System.out.println(linkedList.get(4));
    16     LinkedList<Integer> as=new LinkedList<Integer>();
    17     for(int i=0;i<100000;i++){
    18       as.add(i);
    19     }
    20     traverseByIterator(as);
    21     traverseByFor(as);
    22     traverseByForEach(as);
    23   }
    24   public static void traverseByIterator(LinkedList<Integer>al){
    25     System.out.println("---------迭代器遍历-------------");
    26     long startTime=System.nanoTime();//开始时间
    27     Iterator it=al.iterator();
    28     while(it.hasNext()){
    29       it.next();
    30     }
    31     long endTime=System.nanoTime();//结束时间
    32     System.out.println((endTime-startTime)+"纳秒");
    33   }
    34   public static void traverseByFor(LinkedList<Integer>al){
    35     System.out.println("---------索引遍历-------------");
    36     long startTime=System.nanoTime();//开始时间
    37     for(int i=0;i<al.size();i++) al.get(i);
    38     long endTime=System.nanoTime();//结束时间
    39     System.out.println((endTime-startTime)+"纳秒");
    40   }
    41   public static void traverseByForEach(LinkedList<Integer>al){
    42     System.out.println("---------Foreach遍历-------------");
    43     long startTime=System.nanoTime();//开始时间
    44     for(Integer temp:al);
    45     long endTime=System.nanoTime();//结束时间
    46     System.out.println((endTime-startTime)+"纳秒");
    47   }
    48 }
    49 ---------迭代器遍历-------------
    50 6562423纳秒
    51 ---------索引遍历-------------
    52 4565606240纳秒
    53 ---------Foreach遍历-------------
    54 4594622纳秒
    55 可以看出使用索引遍历,对于linkedList真的很费时间!

    add(index,value)源码分析:我们可以看到,这就是双引用(双指针)的赋值操作。

     1 void linkBefore(E e, Node<E> succ) {
     2         // assert succ != null;
     3         final Node<E> pred = succ.prev;
     4         final Node<E> newNode = new Node<>(pred, e, succ);
     5         succ.prev = newNode;
     6         if (pred == null)
     7             first = newNode;
     8         else
     9             pred.next = newNode;
    10         size++;
    11         modCount++;
    12     }

    remove(index)源码分析:同样,这也是对引用的更改操作,方面多了!

     1 E unlink(Node<E> x) {
     2         // assert x != null;
     3         final E element = x.item;
     4         final Node<E> next = x.next;
     5         final Node<E> prev = x.prev;
     6 
     7         if (prev == null) {
     8             first = next;
     9         } else {
    10             prev.next = next;
    11             x.prev = null;
    12         }
    13 
    14         if (next == null) {
    15             last = prev;
    16         } else {
    17             next.prev = prev;
    18             x.next = null;
    19         }
    20 
    21         x.item = null;
    22         size--;
    23         modCount++;
    24         return element;
    25     }

    get(index)源码分析:利用指针挨个往后查找,直到找到位置为index的元素。当然了,找的时候也是要注意方法的,比如说利用二分查找。

     1 Node<E> node(int index) {
     2         // assert isElementIndex(index);
     3 
     4         if (index < (size >> 1)) {
     5             Node<E> x = first;
     6             for (int i = 0; i < index; i++)
     7                 x = x.next;
     8             return x;
     9         } else {
    10             Node<E> x = last;
    11             for (int i = size - 1; i > index; i--)
    12                 x = x.prev;
    13             return x;
    14         }
    15     }

    Vector

    -和ArrayList类似,可变数组实现的列表

        -Vector同步,适合在多线程下使用

        -原先不属于JCF框架,属于java最早的数据结构,性能较差

        -从JDK1.2开始,Vector被重写,并纳入JCF中

        -官方文档建议在非同步的情况下,优先采用ArrayList

        其实vector类似于ArrayList,所以在一般情况下,我们能优先使用ArrayList,在同步的情况下,是可以考虑使用Vector

    代码例子:

     1 public class VectorTest {
     2   public static void main(String[] args) {
     3     Vector<Integer> vs=new Vector<Integer>();
     4     vs.add(1);
     5     vs.add(4);
     6     vs.add(3);
     7     vs.add(5);
     8     vs.add(2);
     9     vs.add(6);
    10     vs.add(9);
    11     System.out.println(vs);
    12     System.out.println(vs.get(0));
    13     vs.remove(5);
    14     System.out.println(vs);
    15     /*Integer []a=new Integer[vs.size()];
    16     vs.toArray(a);
    17     for(Integer m:a){
    18       System.out.print(m+" ");
    19     }*/
    20     Vector <Integer> as=new Vector <Integer>(100000);
    21     for(int i=0;i<1000000;i++){
    22       as.add(i);
    23     }
    24     traverseByIterator(as);
    25     traverseByFor(as);
    26     traverseByForEach(as);
    27     traverseEm(as);
    28   }
    29   public static void traverseByIterator(Vector<Integer>al){
    30     System.out.println("---------迭代器遍历-------------");
    31     long startTime=System.nanoTime();//开始时间
    32     Iterator it=al.iterator();
    33     while(it.hasNext()){
    34       it.next();
    35     }
    36     long endTime=System.nanoTime();//结束时间
    37     System.out.println((endTime-startTime)+"纳秒");
    38   }
    39   public static void traverseByFor(Vector<Integer>al){
    40     System.out.println("---------索引遍历-------------");
    41     long startTime=System.nanoTime();//开始时间
    42     for(int i=0;i<al.size();i++) al.get(i);
    43     long endTime=System.nanoTime();//结束时间
    44     System.out.println((endTime-startTime)+"纳秒");
    45   }
    46   public static void traverseByForEach(Vector<Integer>al){
    47     System.out.println("---------Foreach遍历-------------");
    48     long startTime=System.nanoTime();//开始时间
    49     for(Integer temp:al){
    50       temp.intValue();
    51     }
    52     long endTime=System.nanoTime();//结束时间
    53     System.out.println((endTime-startTime)+"纳秒");
    54   }
    55   public static void traverseEm(Vector<Integer>al){
    56     System.out.println("---------Enumeration遍历-------------");
    57     long startTime=System.nanoTime();//开始时间
    58     for(Enumeration <Integer> ei=al.elements();ei.hasMoreElements();){
    59       ei.nextElement();
    60     }
    61     long endTime=System.nanoTime();//结束时间
    62     System.out.println((endTime-startTime)+"纳秒");
    63   }
    64 }
    65 ---------迭代器遍历-------------
    66 28927404纳秒
    67 ---------索引遍历-------------
    68 32122768纳秒
    69 ---------Foreach遍历-------------
    70 25191768纳秒
    71 ---------Enumeration遍历-------------
    72 26901515纳秒
    73 可以看到Foreach遍历要快于其他的遍历方法。

    add(index,value)源码剖析:这个和ArrayList类似,需要进行元素的复制,所以很慢

     1 public synchronized void insertElementAt(E obj, int index) {
     2         modCount++;
     3         if (index > elementCount) {
     4             throw new ArrayIndexOutOfBoundsException(index
     5                                                      + " > " + elementCount);
     6         }
     7         ensureCapacityHelper(elementCount + 1);
     8         System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
     9         elementData[index] = obj;
    10         elementCount++;
    11     }

    get(index)源码剖析:可以看到,直接根据元素的下表返回数组元素。非常快!

    1 public synchronized E get(int index) {
    2         if (index >= elementCount)
    3             throw new ArrayIndexOutOfBoundsException(index);
    4 
    5         return elementData(index);
    6     }
    7 E elementData(int index) {
    8         return (E) elementData[index];
    9     }

    其实List这部分内容用的数学知识不是很多,但是set和Map确实是类似于数学模型的概念。期待后续Set,Map的学习。

    个人微信公众号

     

  • 相关阅读:
    jmeter学习笔记(二)
    jmeter学习笔记(一)
    让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法
    Fiddler抓包
    76. 最小覆盖子串
    Map中getOrDefault()与数值进行比较
    阻塞非阻塞与同步异步的区别
    81. 搜索旋转排序数组 II
    49. 字母异位词分组
    48. 旋转图像
  • 原文地址:https://www.cnblogs.com/zyxsblogs/p/10993847.html
Copyright © 2020-2023  润新知