您的位置:首页 > 移动开发 > Android开发

为你的Android程序选择合适的数据容器

2017-08-18 15:20 267 查看

转载请注明出处:http://blog.csdn.net/hjf_huangjinfu/article/details/77334432

        程序需要数据、而数据需要容器。为数据选择一个合适的容器,可以提高程序的质量。平台和框架已经为我们准备了大量的数据容器,本文初步预览一下各容器的特性和使用场景。一些特殊业务相关的数据容器,需要开发者自行实现,或者组合现有容器,或者重新编写。

选择容器类别

1、List

        使用场景:需要存放一组元素,随后可以以任意顺序读取数据,并且可以允许同一个容器中有相同的元素。

2、Set

        使用场景:需要存放一组元素,随后可以以任意顺序读取数据,但是不允许同一个容器中有相同的元素时。

3、Map

       使用场景:需要存放一组键值对。

4、Queue

       使用场景:需要存放一组数据,但是只能以某种约定的顺序读取数据、或者一个支持生产者-消费者模型的容器。

5、Stack、Deque

       使用场景:需要存放一组数据,随后以后进先出(LIFO)的顺序读取数据。

选择一个List

1、ArrayList

       使用场景:

               不需要支持多线程,按索引读取、更新的操作比较多,而插入和删除的操作比较少。

       实现原理:

              数据存储:数组,删除数据需要移动数组中的其他数据。

2、LinkedList

       使用场景:

               不需要支持多线程,但是经常删除和插入数据,很少进行读取操作。

       实现原理:

              数据存储:双向链表。

3、Vector

      使用场景:

               希望支持多线程。

      实现原理:

            数据存储:数组。

            线程安全:采用Java监视器模式,每个public方法都使用Vector自身的锁来保护。

4、CopyOnWriteArrayList

      使用场景:

               希望支持多线程,而且比 Vector 有更好的可伸缩性,尤其是读取线程数量远大于写入线程数量。

      实现原理:

            数据存储:数组。

            并发方案:写操作的方法都使用CopyOnWriteArrayList自身的锁进行保护,然后在原有数据副本(volatile修饰)上面进行修改,修改完成后用该副本替换原有数据。读操作的方法都是非同步方法。写时加锁复制,读时不加锁。

选择一个Set

1、HashSet

      使用场景:

               不需要支持多线程,最普通的Set,没有其他额外需求。

      实现原理:

               数据存储:内部委托给HashMap,忽略Map的值。

2、ArraySet

      使用场景:

                不需要支持多线程,对数据顺序没有需求,但是希望跟HashSet相比,能够牺牲一些时间来换取更高的内存使用率(毕竟是移动平台)。

      实现原理:

                数据存储:双数组(hash数组+数据数组),hash数组用来有序存储数据的hash,使用二叉查找来定位,对象数组存储数据,和hash在同一个索引。

      备注:

                Android平台提供,不属于JDK。

3、TreeSet

      使用场景:

               不需要支持多线程,但是希望Set中的数据能够以某种约定的顺序排列,以便后面在遍历的时候使用,而且希望能按规则生成一些有序的子Set。

      实现原理:

               数据存储:内部委托给TreeMap,忽略Map的值。

4、LinkedHashSet

      使用场景:

              不需要支持多线程,但是希望能保存数据插入时的顺序,以便后面在遍历的时候使用。

      实现原理:

             数据存储:内部委托给LinkedHashMap,忽略Map的值。

5、EnumSet

      使用场景:

              不需要支持多线程,但是希望只存储一个enum类型的所有值,而且集合构建后不会改变,并且希望在时间和空间上都比较高效。

      实现原理:

             数据存储:基于数组,内部数据构造由JVM提供。

6、CopyOnWriteArraySet

      使用场景:

              需要支持多线程,而且对数据排序没有任何要求,尤其是读取线程数量远大于写入线程数量。

      实现原理:

            数据存储:内部委托给CopyOnWriteArrayList。

            并发方案:内部委托给CopyOnWriteArrayList。

7、ConcurrentSkipListSet

      使用场景:

              需要支持多线程,但是希望Set中的数据能够以某种约定的顺序排列,以便后面在遍历的时候使用,而且希望能按规则生成一些有序的子Set。

      实现原理:

            数据存储:内部委托给ConcurrentSkipListMap。

            并发方案:内部委托给ConcurrentSkipListMap。

选择一个Map

1、HashMap

      使用场景:

              不需要支持多线程,最普通的Map,没有其他额外需求。

      实现原理:

              数据存储:基于散列表(Hash表),使用拉链法作为hash冲突解决方案。

2、ArrayMap

      使用场景:

              不需要支持多线程,对数据顺序没有需求,但是希望跟HashMap相比,能够牺牲一些时间来换取更高的内存使用率(毕竟是移动平台)。

      实现原理:

             数据存储:双数组(hash数组 + Key-Value数组)K-V数组长度是hash数组长度的2倍,hash数组用来有序存储数据的hash,使用二叉查找来定位,如果Key的hash所在的hash数组的索引为i,那么Key和Value在K-V数组的索引分别为 i*2,i*2+1,使用开放地址法作为hash冲突解决方案。

      备注:

                Android平台提供,不属于JDK。

3、TreeMap

      使用场景:

              不需要支持多线程,但是希望数据能按照给定的Key的顺序进行排列,并希望后面可以按规则生成一些Key有序的子Map。

      实现原理:

              数据存储:基于红黑树。

4、LinkedHashMap

      使用场景:

              不需要支持多线程,但是希望能保存数据插入时的顺序,以便后面在遍历的时候使用。

      实现原理:

              数据存储:基于散列表(继承自HashMap),使用一个额外的双链表来存储数据插入的顺序。

5、Hashtable

      使用场景:

               希望支持多线程,对并发性能要求不高。

      实现原理:

             数据存储:基于散列表,使用拉链法作为hash冲突解决方案。

             线程安全:采用Java监视器模式,每个public方法都使用Hashtable自身的锁来保护。

6、WeakHashMap

      使用场景:

              不需要支持多线程,但是希望GC可以回收掉一些外部不再使用的键值对(除了该Map,没有地方持有对Key的引用)。

      实现原理:

             数据存储:基于散列表,使用拉链法作为hash冲突解决方案,使用WeakReference来持有对Key的引用。

7、EnumMap

      使用场景:

              不需要支持多线程,但是Key全是某一个enum类型的值,并且希望在时间和空间上都比较高效。

      实现原理:

             数据存储:基于双数组(enum数组+数据数组),内部数据构造由JVM提供。

8、IdentityHashMap

      使用场景:

              不需要支持多线程,但是对Key的识别更为严格,只有引用相同(a == b = true)的Key才被认为是同一个Key。

      实现原理:

             数据存储:基于数组,因为不使用数据的hash,所以不存在hash冲突。

9、ConcurrentHashMap

      使用场景:

              需要支持多线程,而且对并发性能要求比较高,但是对数据顺序没有要求。

      实现原理:

            数据存储:基于散列表(Hash表),使用拉链法作为hash冲突解决方案。

            并发方案:主要基于锁分段技术,每一个在散列桶首部的数据作为整个散列桶的锁,写时加锁,读时不加锁。

10、ConcurrentSkipListMap

      使用场景:

              需要支持多线程,对并发性能要求比较高,并且希望数据能按照给定的Key的顺序进行排列,并希望后面可以按规则生成一些Key有序的子Map。

      实现原理:

             数据存储:基于跳跃表(SkipList)。

            并发方案:基于CAS技术。

11、SparseArray、SparseBooleanArray、SparseIntArray、SparseLongArray

      使用场景:

              不需要支持多线程,但是Key为int类型,并且希望能够牺牲一些时间来换取更高的内存使用率(毕竟是移动平台)。

      实现原理:

            数据存储:基于双数组(int类型的key数组 + 数组数组),key在数组中有序排列,使用二叉查找进行定位。

            备注:SparseArray、SparseBooleanArray、SparseIntArray、SparseLongArray 的Value类型分别为 Object、boolean、int、long。

      备注:

                Android平台提供,不属于JDK。

选择一个Queue

1、LinkedList、ArrayDeque

      使用场景:

              不需要支持多线程的,先进先出,简单的队列。

      使用比较:

              LinkedList支持null数据,而ArrayDeque不支持null数据。

      实现原理:

              LinkedList:基于双向链表。

              ArrayDeque:基于循环数组。

2、LinkedBlockingQueue、ArrayBlockingQueue

      使用场景:

              需要支持多线程,先进先出,并且可阻塞的生产者-消费者队列。

      使用比较:

              LinkedBlockingQueue支持无界队列和有界队列两种模式,ArrayBlockingQueue只有有界模式,其实如果把界限设置为Integer.MAX_VALUE,在有界无界这一方面就没区别,但是性能上面就差很多了,ArrayBlockingQueue需要一次性分配很多内存,而LinkedBlockingQueue是逐渐分配内存,但是在时间性能上面,ArrayBlockingQueue要比LinkedBlockingQueue好。对于被阻塞的线程来说ArrayBlockingQueue支持公平模式,而LinkedBlockingQueue只支持非公平模式。

     实现原理:

            LinkedBlockingQueue:数据存储基于单向链表,并发方案:基于"读"、"写"两个锁和"非空"、"非满"两个条件队列。

            ArrayBlockingQueue:数据存储:基于循环数组,并发方案:基于ReentrantLock和"非空"、"非满"两个条件队列。

3、ConcurrentLinkedDeque、ConcurrentLinkedQueue

     使用场景:

              需要支持多线程、先进先出,非阻塞的,无界的生产者-消费者队列,并且对高并发的性能有要求。

     使用比较:

              ConcurrentLinkedDeque支持双向队列,而ConcurrentLinkedQueue只支持单向队列。

     实现原理:

            数据存储:ConcurrentLinkedDeque基于双向链表,ConcurrentLinkedQueue基于单向链表。

            并发方案:基于CAS技术。

4、PriorityQueue、PriorityBlockingQueue

     使用场景:

              需要一个无界的,以自定义的顺序来读取数据,而不是先进先出。

     使用比较:

              PriorityQueue不支持多线程,也不支持阻塞,而PriorityBlockingQueue既支持多线程,也支持阻塞。

     实现原理:

            数据存储:基于二叉堆。

            并发方案:PriorityBlockingQueue的并发方案使用ReentrantLock和"非空"条件队列。

5、SynchronousQueue、LinkedTransferQueue

     使用场景:

              需要一个先进先出的,有强转移控制权的生产者-消费者队列,一个生产者线程向队列添加数据,必须有一个消费者线程显式的取走该数据,生产者线程才能返回。

     使用比较:

              SynchronousQueue只支持强移交控制权模式,而LinkedTransferQueue支持强控制权和普通两种模式。

     实现原理:

            SynchronousQueue:基于双队列(生产者线程队列 + 消费者线程队列),没有数据存储空间,线程的调度使用LockSupport,配合CAS来操作链表。

            LinkedTransferQueue:数据存储基于单向链表,并发阻塞控制方案:LockSupport + CAS。

6、LinkedBlockingDeque

     使用场景:

              需要支持多线程,可阻塞的,双向队列。

     实现原理:

             数据存储:双向链表。

             并发阻塞控制方案:基于单个ReentrantLock和"非空"、"非满"两个条件队列。

7、DelayQueue

     使用场景:

              需要一个数据读取顺序依赖于特定延时时间的队列。

     实现原理:

            数据存储:内部委托给PriorityQueue。

            排序规则:自定义排序规则,但是必须依赖于延时时间。

选择一个Stack、Deque

1、ArrayDeque、LinkedList

     使用场景:

              不需要支持多线程,简单的栈。

     实现原理:

            数据存储:基于循环数组。

2、Stack

     使用场景:

              需要支持多线程。

     实现原理:

            数据存储:内部委托给Vector(继承自Vector)。

            线程安全:内部委托给Vector。

3、LinkedBlockingDeque

     使用场景:

              需要支持多线程,可阻塞的,双向队列。

     实现原理:

            数据存储:双向链表。

            并发阻塞控制方案:基于单个ReentrantLock和"非空"、"非满"两个条件队列。

4、ConcurrentLinkedDeque

     使用场景:

              需要支持多线程、先进先出,非阻塞的,无界的生产者-消费者队列,并且对高并发的性能有要求。

     实现原理:

            数据存储:基于双向链表。

            并发方案:基于CAS技术。

附录

1、SkipList,一个以链表形式实现有序顺序表,参考链接:https://en.wikipedia.org/wiki/Skip_list

2、红黑树,一个自平衡二叉树,参考链接:https://en.wikipedia.org/wiki/Red%E2%80%93black_tree

3、CAS,一个非阻塞算法,参考链接:https://en.wikipedia.org/wiki/Compare-and-swap

4、锁分段技术,一个降低锁竞争的方法,参考链接:https://en.wikipedia.org/wiki/Lock_(computer_science)#Granularity
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: