zoukankan      html  css  js  c++  java
  • JAVA集合~

    集合:

    List:

    List集合具有以下特点:

    1. 集合中的元素允许重复
    2. 集合中的元素是有顺序的,各元素插入的顺序就是各元素的顺序
    3. 集合中的元素可以通过索引来访问或者设置

    List接口常用的实现类有:ArrayList、LinkedList、Vector。

    Arraylist(线程不安全):

    ArrayList 实现了 List 接口,继承了 AbstractList 抽象类,底层是基于数组实现的,并且实现了动态扩容。

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        private static final int DEFAULT_CAPACITY = 10;
        transient Object[] elementData;
        private int size;
    }
    

    RandomAccess 接口 : 随机访问不需要遍历,就可以通过下标(索引)直接访问到内存地址。

    Cloneable 接口 : 这表明 ArrayList 是支持拷贝的。ArrayList 内部的确也重写了 Object 类的 clone() 方法

    Serializable 接口 :序列化转二进制,内部提供了两个私有方法 writeObject 和 readObject 来完成序列化和反序列化

    动态扩容: ArrayList ,

    优点: 底层数据结构是数组,查询快,增删慢。
    缺点: 线程不安全,效率高

    遍历ArrayList的元素主要有以下3种方式:

    ArrayList<String> list = new ArrayList<>();
            list.add("我");
            list.add("adwj");
            list.add("我");
    
    1. 迭代器iterator遍历

              Iterator<String> iterator = list.iterator();
              while (iterator.hasNext()){
                  System.out.println(iterator.next());
              }
      
      
    2. for循环

      for(int i=0;i<list.size();i++){
         System.out.println(list.get(i));
      }
             
      
    3. foreach循环

       for (String s : list) {
          System.out.println(s);
       }
      

    LinkedList :

    LinkedList 是一个继承自 AbstractSequentialList 的双向链表,因此它也可以被当作堆栈、队列或双端队列进行操作

    public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    {
        transient int size = 0;
        transient Node<E> first;
        transient Node<E> last;
    }
    

    优点: 底层数据结构是链表,查询慢,增删快。
    缺点: 线程不安全,效率高

    vector(线程安全):

    public class Vector<E>
        extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
    }
    

    常问:

    相同点

    ArrayList、LinkedList、Vector都实现了List接口,所以使用方式很类似

    不同点

    但是ArrayList、LinkedList、Vector的内部实现方式不同,也就导致了它们之间是有区别的。

    存储结构

    ArrayList和Vector是基于数组实现的,LinkedList是基于双向链表实现的。

    这也就导致ArrayList适合随机查找和遍历,而LinkedList适合动态插入和删除元素。

    线程安全性

    ArrayList和LinkedList是线程不安全的,Vector是线程安全的。

    Vector可以看做是ArrayList在多线程环境下的另一种实现方式,这也导致了Vector的效率没有ArraykList和LinkedList高。

    如果要在并发环境下使用ArrayList或者LinkedList,可以调用Collections类的synchronizedList()方法(分情况)

    如果读大于写 那么可以使用CopyOnWriteArrayList<>() 不过会在写操作复制一个list进行写操作 会占用大量内存

    CopyOnWriteArrayList 中, 定义了一个可重入锁:

    final transient ReentrantLock lock = new ReentrantLock();
    

    该锁用于对所有修改集合的方法 (add, remove 等) 进行同步, 在进行实际修改操作时, 会先复制原来的数组, 再进行修改, 最后替换原来的

    但也会因此引入 "弱一致性" 问题; 所谓 "弱一致性" 是指当一个线程正在读取数据时, 若此时有另一个线程同时在修改该区域的数据, 读取的线程将无法读取最新的数据, 即该读取线程只能读取到它读取时刻以前的最新数据;

    ArrayList初始容量是10,添加第11个元素会扩容到16,之后扩容因子是1.5+1

    Vector初始容量是10,添加到第11个元素扩容为20,21个元素扩容为40,接着80.扩容因子2

    Set:

    Set集合包括Set接口以及Set接口的所有实现类。Set集合具有以下特点:

    1. 集合中不包含重复元素(你可以重复添加,但只会保留第1个)
    2. 集合中的元素不一定保证有序

    Set接口常用的实现类有:HashSet、LinkedHashSet、TreeSet。

    Hashset:

    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    

    底层数据结构是哈希表。(无序,唯一)如何来保证元素唯一性?
    1.依赖两个方法:hashCode()和equals()

    hashCode()判断哈希值是否相同,相同后调用equals() 判断是否是一个元素

    遍历元素用迭代器或者foreach循环

    TreeSet:

    底层数据结构是红黑树。(唯一,有序)

    1. 如何保证元素排序的呢?
      自然排序 实现接口Comparable,重写该接口的方法compareTo()
      比较器排序
    2. 如何保证元素唯一性的呢?
      根据比较的返回值是否是0来决定 :

    LinkedHashSet:

    底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
    1.由链表保证元素有序
    2.由哈希表保证元素唯一

    加载因子为0.75 , 即当 元素个数 超过 容量长度的0.75倍 时,进行扩容

    扩容增量:原容量的 1 倍

    HashSet的容量为16,一次扩容后是容量为32

    Map:

    HashMap(线程不安全)

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable {
    	......
    }
    
    

    HashMap的父类是AbstractMap ,继承了Map<K,V>Cloneable, Serializable

    使用entrySet()获得一个有Key+Value的Set集合 可以使用迭代器iterator()或者foreach()循环遍历

    使用value()获得一个Value的值,可以使用迭代器iterator()或者foreach()循环遍历

    JDK8中hashmap底层是通过数组+链表+红黑树来实现的

    JDK8中的因为使用了红黑树保证了插入和查询了效率,所以实际上JDK8中的Hash算法实现的复杂度降低了

    JDK8中数组扩容的条件也发了变化,只会判断是否当前元素个数是否超过了阈值,而不再判断当前put进来的元素对应的数组下标位置是否有值。

    JDK7中是先扩容再添加新元素,JDK8中是先添加新元素然后再扩容

    HashMap中PUT方法的流程?
    1. 通过key计算出个hashcode (key%table.length,提高效率key&table.length-1,前提数组长度是2的幂)
    2. 通过hashcode与数组表长与操作计算出一个数组下标
    3. 在把put进来的key,value封装为一个node对象
    4. 判断数组下标对应的位置,是不是空,如果是空则把node直接存在该数组位置
    5. 如果该下标对应的位置不为空,则需要把node插入到链表中
    6. 并且还需要判断该链表中是否存在相同的key,如果存在,则更新value
    7. 如果是JDK7,则使用头插法
    8. 如果是JDK8,则会遍历链表,并且在遍历链表的过程中,统计当前链表的元素个数,如果超过8个,则先把链表转变为红黑树,并且把元素插入到红黑树中
    JDK8中链表转变为红黑树的条件?
    1. 链表中的元素的个数为8个或超过8个
    2. 同时,还要满足当前数组的长度大于或等于64才会把链表转变为红黑树。为什么?因为链表转变为红黑树的目的是为了解决链表过长,导致查询和插入效率慢的问题,而如果要解决这个问题,也可以通过数组扩容,把链表缩短也可以解决这个问题。所以在数组长度还不太长的情况,可以先通过数组扩容来解决链表过长的问题。
    HashMap扩容流程是怎样的?
    1. HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间,所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新数组上来,这样才是数组的扩容
    2. 在HashMap中也是一样,先新建一个2被数组大小的数组
    3. 然后遍历老数组上的没一个位置,如果这个位置上是一个链表,就把这个链表上的元素转移到新数组上去
    4. 在这个过程中就需要遍历链表,当然jdk7,和jdk8在这个实
    5. 现时是有不一样的,jdk7就是简单的遍历链表上的没一个元素,然后按每个元素的hashcode结合新数组的长度重新计算得出一个下标,而重新得到的这个数组下标很可能和之前的数组下标是不一样的,这样子就达到了一种效果,就是扩容之后,某个链表会变短,这也就达到了扩容的目的,缩短链表长度,提高了查询效率
    6. 而在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过八,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置。
    7. 元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组会被回收到。
    为什么HashMap的数组的大小是2的幂次方数?

    JDK7的HashMap是数组+链表实现的

    JDK8的HashMap是数组+链表+红黑树实现的

    当某个key-value对需要存储到数组中时,需要先生成一个数组下标index,并且这个index不能越界。

    在HashMap中,先得到key的hashcode,hashcode是一个数字,然后通过 hashcode & (table.length - 1) 运算得到一个数组下标index,是通过与运算计算出来一个数组下标的,而不是通过取余,与运算相比于取余运算速度更快,就是数组的长度得是一个2的幂次方数。2的幂次方数二进制值都是1,10,100,1000.......2的幂次方数-1的二进制值都是11,111,1111......&运算计算出来的值都会小于表长。不然会增加哈希碰撞概率,浪费内存

    Hashtable:

    public class Hashtable<K,V>
        extends Dictionary<K,V>
        implements Map<K,V>, Cloneable, java.io.Serializable {
    {
    	......
    }
    

    HashMap的父类是 Dictionary,继承了Map<K,V>Cloneable, Serializable

    HashTable类的使用方法和HashMap基本一样

    LinkedHashMap:

    public class LinkedHashMap<K,V>
        extends HashMap<K,V>
        implements Map<K,V>
    {
    	......
    }
    
    

    inkedHashMap类继承了HashMap类。

    LinkedHashMap类的使用方法和HashMap基本一样

    TreeMap:

    public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable
    {
    	......
    }
    
    

    TreeMap中的元素是有序的,默认的排序规则是按照key的字典顺序升序排序

    TreeMap类的使用方法和HashMap基本一样

    4类区别:

    相同点

    1)HashMap、Hashtable、LinkedHashMap、TreeMap都实现了Map接口

    2)四者都保证了Key的唯一性,即不允许Key重复

    排序

    HashMap不保证元素的顺序

    Hashtable不保证元素的顺序

    LinkHashMap保证FIFO即按插入顺序排序(链表)

    TreeMap保证元素的顺序,支持自定义排序规则(树)

  • 相关阅读:
    js全局变量
    $.getJSON异步请求和同步请求
    让js中的函数只有一次有效调用
    两个div并排显示,当浏览器界面缩小时会出现换行
    jquery获取窗口和文档的高度和宽度
    后台传带引号(")的数据需要注意
    C# dynamic
    (转)数据库函数解析JSON字符串
    Unicode和UTF-8
    用户通过浏览器修改表单隐藏域
  • 原文地址:https://www.cnblogs.com/RUAYO/p/14088884.html
Copyright © 2011-2022 走看看