对比Vector、ArrayList、LinkedList有何区别?

文章目录

  • 对比Vector、ArrayList、LinkedList有何区别?
  • 典型回答
  • 考点分析&知识拓展
    • 补充说明不同容器适用的场景
    • Java集合框架,需要掌握的几个点
    • 集合框架的整体设计
    • 几个Set的应用场景
    • 如何保证线程安全
    • 理解Java默认排序算法,具体是什么排序方式以及设计思路
    • 集合框架的演进与发展
    • 思考题

对比Vector、ArrayList、LinkedList有何区别?

典型回答

三者都是实现集合框架中的List,也就是所谓的有序集合,因此具体功能比较相似,比如都提供按位置进行定位、添加或者删除的操作,都提供迭代器以遍历其内容等。但因为具体的设计区别,在行为、性能、线程安全等方面,表现又有很大不同。

Vector是Java早起提供的线程安全的动态数组。如果不需要线程安全,不建议使用,因为同步是有额外开销的。Vector内部是使用对象数组来保存数据的,可以根据需要自动的增加容量,当数组满时,会创建新的数组,并拷贝原有数组数据。

ArrayList是应用更加广泛的动态数组实现。它本身不是线程安全的,所以性能要好很多。与Vector近似,ArrayList也是可以根据需要调整容量,不过两者的调整策略有所区别,Vector在扩容时会提高1倍,而ArrayList则是增加50%。

LinkedList顾名思义是Java提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的。

考点分析&知识拓展

补充说明不同容器适用的场景

Vector 和 ArrayList 作为动态数组,其内部元素以数组形式顺序存储的,所以非常适合随机访问的场合。除了尾部插入和删除元素,往往性能会相对较差,比如我们在中间位置插入一个元素,需要移动后续所有元素。

而 LinkedList 进行节点插入、删除却要高效得多,但是随机访问性能则要比动态数组慢。

所以,在应用开发中,如果事先可以估计到,应用操作是偏向于插入、删除,还是随机访问较多,就可以针对性的进行选择。

Java集合框架,需要掌握的几个点

  • Java集合框架的设计结构,至少要有一个整体印象。
  • Java提供的主要容器(集合和Map)类型,了解或掌握对应的数据结构、算法,思考具体技术选择。
  • 将问题扩展到性能、并发等领域。
  • 集合框架的演进与发展。

集合框架的整体设计

为了避免混淆,这里没有将java.util.concurrent下面的线程安全容器加入讲解。也没有列出Map容器,虽然通常概念上我们也会把Map作为集合框架的一部分,但是它本身不是真正的集合(Collection)。仅针对狭义的集合框架进行讲解。

Java的集合框架,Collection接口是所有集合的根,然后扩展提供了三大类集合,分别是:

  • List,也就是前面介绍的有序集合,它提供了方便的访问、插入、删除等操作。
  • Set,不运行重复元素,这是和List最明显的区别,也就是不能存在两个对象的equles返回true。我们在日常开发中有很多需要保证元素唯一性的场景。
  • Queue/Deque,则是Java提供的标准队列结构的实现,除了集合的基本功能,它还支持l类似先进先出(FIFO)或后入先出(LIFO)等特定行为。这里不包括BlockingQueue,因为通常是用于并发编程场合,所以被放置在并发包中。

每种集合的通用逻辑,都被抽象到相应的抽象类之中,比如AbstractList就集中了各种List操作的通用部分。这些集合不是完全孤立的,如,LinkedList本身,即使List,也是Deque。

几个Set的应用场景

HashSet是利用哈希算法,理想情况下,如果哈希散列正常,可以提供常数时间的添加、删除、包含等操作,但是它不保证有序。

TreeSet 支持自然顺序访问,但是添加、删除、包含等操作要相对低效(log(n) 时间)。

LinkedHashSet,内部构建了一个记录插入顺序的双向链表,因此提供了按照插入顺序遍历的能力,与此同时,也保证了常数时间的添加、删除、包含等操作,这些操作性能略低于HashSet,因为需要维护链表的开销。

如何保证线程安全

本篇介绍的集合类都不是线程安全的。线程安全容器在java.util.concurrent中。

但并不代表这些集合b不能支持并发编程的场景,使用Collections工具类,提供了一系列synchronized方法。

它的实现,基本就是将每个基本方法,比如 get、set、add 之类,都通过synchronizd添加基本的同步支持,非常简单粗暴,但也非常实用。注意这些方法创建的线程安全集合,都符合迭代时fail-fast行为,即当发生意外的并发修改时,尽早抛出ConcurrentModificationException异常,以避免不可预计的行为。

理解Java默认排序算法,具体是什么排序方式以及设计思路

这个问题本身有点陷阱的意味,因为需要区分:

  • 是Array.sort()还是Collections.sort()(底层是调用Array.sort())。
  • 什么数据类型。
  • 多大的数据集(太小的数据集,复杂排序是没必要的,Java会直接进行二分插入排序)。

对于原始数据类型,目前使用的是所谓双轴快速排序(Dual-Pivot QuickSort),是一种改进的快速排序算法,早期版本是相对传统的快速排序。

而对于对象数据类型,目前则是使用TimSort,思想上也是一种归并和二分插入排序(binarySort)结合的优化排序算法。TimSort 并不是 Java的独创,简单说它的思路是查找数据集中已经排好序的分区(这里叫run),然后合并这些分区来达到排序的目的。

Java 8 引入了并行排序算法(直接使用 parallelSort 方法),这是为了充分利用现代多核处理器的计算能力,底层实现基于fork-join框架,当处理的数据集比较小的时候,差距不明显,甚至还表现差一点;但是,当数据集增长到数万或百万以上时,提高就非常大了,具体还是取决于处理器和系统环境。

集合框架的演进与发展

在 Java 8 之中,Java 平台支持了 Lambda 和 Stream,相应的 Java 集合框架也进行了大范围的增强,以支持类似为集合创建相应stream或者parallelStream的方法实现,我们可以非常方便的实现函数式代码。

阅读Java源代码,你会发现,这些API的设计和实现比较独特,它们并不是实现在抽象类里面,而是以默认方法的形式实现在Collection这样的接口里!这是Java8在语言层面的新特性,允许接口实现默认方法,理论上来说,我们原来实现在类似Collections这种工具类中的方法,大多可以转换到相应的接口上。

在 Java 9 中,Java标准类库提供了一系列的静态工厂方法,比如,List.of()、Set.of(),大大简化了构建小的容器实例的代码量。根据业界实践经验,我们发现相当一部分集合实例都是容量非常有限的,而且在生命周期中并不会进行修改。但是,在原有的 Java 类库中,我们可能不得不写成:

ArrayList list = new ArrayList<>();
list.add("Hello");
list.add("World");

而利用新的容器静态工厂方法,一句代码就够了,并且保证了不可变性。

List simpleList = List.of("Hello","world");

更进一步,通过各种of静态工厂方法创建的实例,还应用了一些我们所谓的最佳实践,比如,它是不可变的,符合我们对线程安全的需求;它因为不需要考虑扩容,所以空间上更加紧凑等。

思考题

题: 思考一个应用场景,比如你需要实现一个云计算任务调度系统,希望可以保证VIP客户的任务被优先处理,你可以利用哪些数据结构或者标准的集合类型呢?更进一步讲,类似场景大多是基于什么数据结构呢?

答: 使用优先级队列(PriorityQueue),可设置优先级,将VIP优先级设置为最高。借鉴操作系统中的调度算法,对于其他用户,我们还可以设计各种公平的优先级选择算法(基于排队先后顺序,基于调度任务所需的时间长短(操作系统中的短作业优先算法)排序、高响应比((所用时间+等待时间)/等待时间)优先进行排序),与 PriorityQueue 结合使用。

你可能感兴趣的:(Java,基础,Java,基础,集合框架)