Java 集合

Java中常用集合:Map 和 Collection  接口相关实现类 

  |- Collection
      |-List
        |-ArrayList
        |-LinkedList
        |-Vector
        |-CopyOnWriteArrayList (JUC包)
      |-Set
        |-HashSet
        |-LinkedHashSet
        |-TreeSet
 |- Map
     |-HashMap
     |-Hashtable
     |-ConcurrentHashMap (JUC包)
     |-TreeMap

一、List

 1、ArrayList

        底层基于数组实现,数据保存在了elementData属性中。 
     *     当创建时不传入大小或者传入的为0, elementData = {}。此时第一次add elementData的大小将扩容至10
     *           之后使用add方法添加时判断是否需要扩容(当前数组大小 < 添加新元素后的大小),扩容规则为  newCapacity = oldCapacity + (oldCapacity >> 1)
     *     扩容的新数组将会重新申请一块连续空间,然后将数据拷贝过去。
     *     常用方法为  add 、 get、 remove
     *     插入删除效率不高。应为是基于数组实现,删除时后面所有的内存块都需要移动。插入时扩容需要重新申请空间然后进行老到新的复制比较耗时
     *     不支持并发

 其他点
     * elementData属性 使用transient修饰符
     *  transient用来表示一个域不是该对象序行化的一部分,
     *  当一个对象被序行化的时候,transient修饰的变量的值是不包括在序行化的表示中的。
     *  为什么要用transient修饰符?
     *   原因在于elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,
     *      那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化
     *      实际存储的那些元素,而不是整个数组,从而节省空间和时间。 
     *      ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream;
     *      反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。

 2、LinkedList

          linkedList底层使用双向链表结构, 插入删除效率高,查询和随机访问的效率不高(按照位置访问时也需要从first几点一个             一个向下找)

 3、 Vector

     数据存储结构和ArrayList类似,对操作方法加锁。

 4、CopyOnWriteArrayList

         JUC包下的线程安全的ArrayList 。写入时复制(CopyOnWrite,简称COW)思想  ,读写分离。读时不加锁,写加锁。并且当写入时会先复制一份原本的数组,不影响在此期间的度操作。当写入完成之后将数组引用指向复制后的新地址。

List小结 :

 1、ArrayList 、Vector、 CopyOnWriteArrayList的区别?
    Vector使用synchronized对get、add等方法进行了同步设置,是线程安全集合。arrayList没有任何同步机制,线程不安全

    CopyOnWriteArrayList是线程安全的并且比Vector效率更高。因为只在写入时加锁。
 2、 ArrayList和LinkedList 的区别
      两者底层实现不同,arrayList使用的是数组实现,linkedlist 使用的是双向链表。
     由于数组是一块连续的固定大小的内存空间,当arrayList插入时如果达到最大容量需要扩容,
     还需要进行老到新的数据全量拷贝,耗时较长。并且在数据中插入除了在尾部插入,在其他数组任何位置插入都需要移动部分数组元素,比较耗时。
     LinkedList不需要有扩容这一操作,双向链表可以是分散的内存空间,对于插入操作也可快速操作。但是如果需要随机访问,
      链表需要从头结点开始直到到达访问节点,所以访问空间复杂度O(n/2)不如数组快捷。

二、Map接口

   1、HashMap

           底层使用数组+链表+红黑树实现。 JDK1.7之前只有数组+链表,1.8进行优化,当链表节点大于8时链表将转化为红黑树形式,红黑树结构下当小于6时退化为链表。两种数据结构中,数组作为哈希桶,链表是为了解决哈希冲突。HashMap初始化的时候,数组的默认大小为16,还会有一个默认的加载因子0.75,然后通过加载因子和数组大小计算出一个阈值,当数组里已经使用的元素大于这个阈值时,会触发扩容resize()方法进行扩容到原来的两倍(数组当前大小进行左移以为操作),然后重新哈希寻找各自的位置。在1.7之前扩容这个地方,使用的是头部插入,当扩容过程中使用get操作的时候容易出现循环链表,在1.8的时候对这个做了优化,使用尾部插入。不会出现循环链表,但是还是线程不安全的。如果需要保证线程安全的需要使用ConcurrentHashMap、Hashtable。推荐使用的是ConcurrentHashMap
        那么问题来了
        为什么选择6和8中间差一个7呢,不直接使用8呢?
        这是因为防止当进行插入和删除频繁操作时,链表和树之间的频繁转换消耗性能。
        
         为什么使用红黑树呢? 
         当链表节点多了以后,遍历链表的效率会变低,影响整体性能。红黑树作为一种平衡二叉树,元素很多的情况下,层级会控制的很好。对于n个元素的链表遍历的时间复杂度为O(n),但是红黑树的遍历复杂度只是O(logN)。对于AVL树来说,红黑树在查询复杂度上和AVL树差别不大,但是对于最求绝对平衡来说,红黑树在插入和删除上效率会更高。
         
         为什么不直接采用红黑树呢,而是 链表+红黑树?
         查询和插入删除的时间平衡,红黑树的查询虽然快,但是在数据小于8的时候,插入的时候依然伴随着左右旋操作,耗时会比链表插入长。
         
         HashMap中如果自定义key元素需要重写hashcode和equels方法?
         hashCode 和equels作为Object方法,但是默认实现hashCode为内存地址。hashCode方法在Hashmap中,相同的对象hash值必须一样,但是我们知道地址不一样对象也可能一样,所以必须重写hashCode方法。在put 和get操作时会用到对比hash值和equels方法,虽然相同的对象hash值一样,但是也会存在不同的对象hash值一样的情况,而Object默认实现equels是通过hashcode比较的,所以要重写equels方法确保对象一致。

 

2、Hashtable

        类似于HashMap 数据结构,对多操作方法加synchronized 关键字进行并发控制。

3、ConcurrentHashMap 

      是在HashMap的基础上实现线程安全而且兼顾了效率。在1.7之前,ConcurrentHashMap使用的分段锁的技术,初始化时会有一个并发数参数,默认是16。会存在两种数组结构。当元素增多时,锁的粒度会越来越大。到1.8时,放弃了分段锁,使用的是CAS+synchronized,当插入头结点的时候使用CAS(比较-交换),当。已经有头结点时,使用synchronized将头结点锁住然后进行添加。这个过程会自旋添加直到成功。比如所第一次进来发现头节点没有值,然后进行下一次循环发现有值之后如果发现正在扩容,该线程会帮助扩容线程进行扩容,如果发现没有扩容,加锁然后把数据加入链表。

     

     

 

 

        

    

 

        

            

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值