• 集合


    集合(Collection)

     

    Collection和Collections有什么区别

      Collection是一个集合接口。提供了对集合对象进行基本操作的通用接口方法,实现接口的类主要是List和Set。

      Collections是针对集合类的一个包装类,提供一系列静态方法以实现对各种集合的搜索、排序、线程安全化等操作,大多用来处理线性表。

      集合中线程安全的类有:vector、stack、hashtable、enumeration。除此之外,均是非线程安全的类和接口。

      collection、set、List都是接口,HashSet继承AbstractSet,实现了Set接口

     

    ArrayListLinkedListVector

      List接口有三个实现类,分别是ArrayList、LinkedList和Vector

     

    ArrayList

    Vector

    LinkedList

    内存结构

    数组

    数组

    双向链表

    数组内存扩展

    原数组*1.5+1

    有利于节约内存空间

    原数组*2

     

    插入删除

    效率低

    效率低

    效率高

    随机访问

    效率高

    效率高

    效率低

    线程安全?

    不安全,不同步

    安全,同步

    不安全,不同步

    空间浪费

    List列表的结尾预留一定的空间

     

    每一个元素都需要消耗相当的空间

    备注

     

    提供indexOf(obj, start)接口

    提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    ArrayList本质是顺序存储的线性表,插入和删除操作会引发后续元素移动,效率低,随机访问效率高。ArrayList删除元素后,剩余元素会依次向前移动,因此下标一直在变,size()也会减小。remove(int index)删除索引处元素,remove(Object o)删除元素。

    Vector和ArrayList一样,也是通过数组实现,不同的是它支持线程的同步,但实现同步需要很高的花费,因此访问它比访问ArrayList慢。

    LinkedList的内存是双向链表储存,链式存储结构插入和删除效率高,不需要移动。但是随机访问的效率低,需要从头开始向后依次访问。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

     

    手动实现ArrayList

    public class MyArrayList {
        //定义存放数据的数组
        private Object[] elementData;
        private int size;
        
        //获得集合长度的方法
        public int size(){
            return elementData.length;
        }
        
        //默认初始数组长度为10
        public MyArrayList() {
            this(10);
        }
        
        public MyArrayList(int initialCapacity) {
            if (initialCapacity < 0) {
                try {
                    throw new Exception();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            elementData = new Object[initialCapacity];
        }
        
        //add方法
        public void add (Object o){
            //数组扩容和数据拷贝
            if (size == elementData.length ) {
                Object[] newArray =  new Object[size*2+1];
                System.arraycopy(elementData, 0, newArray, 0, elementData.length);
                elementData = newArray;
            }
            elementData[size++] = o;
        }
        
        //是否为空
        public boolean isEmpty(){
            return size == 0;
        }
        
        //get方法
        public Object get(int index){
            if (index < 0 || index >=size) {
                try {
                    throw new Exception();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return elementData[index];
        }
        
        
        
        public static void main(String[] args) {
            MyArrayList m2 = new MyArrayList(3);
            m2.add("111");
            m2.add("222");
            m2.add("333");
            m2.add("444");
            m2.add("555");
            System.out.println(m2.get(2));
        }
    }

     

    HashMapHashtable的区别

    (1)Hashtable的方法是线程同步的,HashMap未经同步

    (2)Hashtable不允许null值(key和value都不可以),HashMap允许null(keyvalue都可以)。HashMap插入的时候,检查是否已经存在相同的key,如果不存在,则直接插入,如果存在,则用新的value替换旧的value。

    (3)Hashtable使用Enumeration,HashMap使用Iterator

    (4)Hashtable有一个contains(Object value),功能和containsValue(Objectvalue)功能一样;

    (5)哈希值的使用不同,Hashtable直接使用对象的hashCode,而HashMap重新计算hash值。

    (6)Hashtable中hash数组默认大小是11,增加的方式是old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数

    (7)HashMap底层是由数组加链表实现的,对于每一个key值,都需要计算哈希值,然后通过哈希值来确定顺序,并不是按照加入顺序来存储,是无序的

     

    Set中存放元素根据什么来判断相同

      HashSet中的add()方法,底层是靠HashMap来实现。HashMap中存入元素的时候,首先比较hashCode值,若不相等,则存入,若相同再比用equals()比较。所以HashSet是避免重复是根据hashCode和equals保证的。

     

    Set不能有重复元素,且是无序的,要有空值也只能有一个。

    List可以有重复元素,是有序的,空值也可以是多个。

     

     遍历集合(Iterator)

    通用遍历方式

    所有集合都实现Iterator接口,用来遍历集合中的元素。

    接口中的方法如下:

      boolean  hasNext()  如果仍有元素可以迭代,则返回 true

      Object    next()    返回迭代的下一个元素。

      void     remove()  从迭代器指向的 collection 中移除迭代器返回的最后一个元素。

    用法

    public static void main(String[] args) {
            List<Object> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
            
            Iterator<Object> it = list.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
        }

    另一种写法

    for (Iterator it = list.iterator(); it.hasNext();) {
        System.out.println(it.next());            
    }

     

    Map遍历方式

    1、通过获取所有的key,按照来遍历

    Set<String> set = map.keySet(); //得到所有key的集合
    for (String str : map.keySet()) {
        Object obj = map.get(str);//得到每个key多对用value的值
    }

    2、通过Map.entrySet使用iterator遍历key和value

    Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
    while (it.hasNext()) {
         Map.Entry<Integer, String> entry = it.next();
         System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }

    3、通过Map.entrySet遍历key和value,推荐,尤其是容量大时

    //Map.entry<Integer,String> 映射项(键-值对)  有几个方法:用上面的名字entry
    //entry.getKey() ;entry.getValue(); entry.setValue();
    //map.entrySet()  返回此映射中包含的映射关系的 Set视图。
    for (Map.Entry<Integer, String> entry : map.entrySet()) {
        System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }

     

    List遍历方式

    1、使用Iterator

    2、foreach循环

    for (Object object : list) { 
        System.out.println(object); 
    }

    3、for循环

    for(int i = 0 ;i<list.size();i++) {  
        int j= (Integer) list.get(i);
        System.out.println(j);  
    }

     

    每个遍历方法的实现原理是什么?

    1、传统的for循环遍历,基于计数器的:
      遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。
    2、迭代器遍历,Iterator:
      每一个具体实现的数据集合,一般都需要提供相应的Iterator。相比于传统for循环,Iterator取缔了显式的遍历计数器。所以基于顺序存储集合的Iterator可以直接按位置访问数据。而基于链式存储集合的Iterator,正常的实现,都是需要保存当前遍历的位置。然后根据当前位置来向前或者向后移动指针。
    3、foreach循环遍历:
      根据反编译的字节码可以发现,foreach内部也是采用了Iterator的方式实现,只不过Java编译器帮我们生成了这些代码。

     

    各遍历方式对于不同的存储方式,性能如何?

    1、传统的for循环遍历
      因为是基于元素的位置,按位置读取。所以我们可以知道,对于顺序存储,因为读取特定位置元素的平均时间复杂度是O(1),所以遍历整个集合的平均时间复杂度为O(n)。而对于链式存储,因为读取特定位置元素的平均时间复杂度是O(n),所以遍历整个集合的平均时间复杂度为O(n2)。
    2、迭代器遍历,Iterator:
      那么对于RandomAccess类型的集合来说,没有太多意义,反而因为一些额外的操作,还会增加额外的运行时间。但是对于Sequential Access(顺序存取)的集合来说,就有很重大的意义了,因为Iterator内部维护了当前遍历的位置,所以每次遍历,读取下一个位置并不需要从集合的第一个元素开始查找,只要把指针向后移一位就行了,这样一来,遍历整个集合的时间复杂度就降低为O(n);
    3、foreach循环遍历:
      分析Java字节码可知,foreach内部实现原理,也是通过Iterator实现的,只不过这个Iterator是Java编译器帮我们生成的,所以我们不需要再手动去编写。但是因为每次都要做类型转换检查,所以花费的时间比Iterator略长。时间复杂度和Iterator一样。
     

    各遍历方式的适用于什么场合?

    1、传统的for循环:
      顺序存储:读取性能比较高。适用于遍历顺序存储集合。
      链式存储:时间复杂度太大,不适用于遍历链式存储的集合。
    2、迭代器遍历,Iterator:
      顺序存储:如果不是太在意时间,推荐选择此方式,毕竟代码更加简洁,也防止了Off-By-One(大小差一。。就是指某个变量的最大值和最小值可能会和正常值差1,或者循环多执行一次/少执行一次。一般在临界情况时发生)的问题。
      链式存储:意义就重大了,平均时间复杂度降为O(n),还是挺诱人的,所以推荐此种遍历方式。
    3、foreach循环遍历:
      foreach只是让代码更加简洁了,但是他有一些缺点,就是遍历过程中不能操作数据集合(删除等),所以有些场合不使用。而且它本身就是基于Iterator实现的,但是由于类型转换的问题,所以会比直接使用Iterator慢一点,但是还好,时间复杂度都是一样的。所以怎么选择,参考上面两种方式,做一个折中的选择。
     

    Java的最佳实践是什么?

      Java数据集合框架中,提供了一个RandomAccess接口,该接口没有方法,只是一个标记。通常被List接口的实现使用,用来标记该List的实现是否支持Random Access。
    一个数据集合实现了该接口,就意味着它支持Random Access,按位置读取元素的平均时间复杂度为O(1)。比如ArrayList。而没有实现该接口的,就表示不支持Random Access。比如LinkedList。
      所以看来JDK开发者也是注意到这个问题的,那么推荐的做法就是,如果想要遍历一个List,那么先判断是否支持Random Access,也就是 list instanceof RandomAccess。
    ArrayList部分源码
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable

    LinkList部分源码

    public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    if (list instanceof RandomAccess) {
        //使用传统的for循环遍历。
    } else {
        //使用Iterator或者foreach。
    }

     

    参考博客

    [1]java集合遍历的几种方式总结及比较

    http://www.cnblogs.com/leskang/p/6031282.html

     

  • 相关阅读:
    elk使用docker安装
    fastdfs使用docker安装
    jenkins构建执行shell脚本提示permission-denied
    mongodb数据库安装及管理工具mongo-express安装(docker方式)
    docker中文乱码问题解决
    TIDB3.0下线tikv节点
    冲刺第六天
    冲刺第五天
    评价用过的浏览器
    描述用户场景
  • 原文地址:https://www.cnblogs.com/ghq120/p/8335152.html
Copyright © 2020-2023  润新知