典型问答

Q:对比 Vector、ArrayList、LinkedList 有何区别?
A:三者都实现集合框架中的List,也就是有序集合(也称之为序列),因此,具体功能比较相似,比如都提供按照位置进行定位、添加或者删除的操作,都提供迭代器以遍历其内容。但因设计上的区别,在行为、性能、线程安全等方面又有很大不同。
Vector线程安全的动态数组,如果不需要线程安全,建议时用ArrayList,毕竟同步是有额外开销的。Vector内部是使用对象数组来保存数据,可以根据需要自动增加容量,当数据满时,会创建新的数组,并拷贝原有数组数据。
ArrayList动态数组,非线程安全,所以性能要好很多。与Vector近似,ArrayList也可以根据需要调整容量,不过两者的调整逻辑有所区别,Vector扩容时会提高一倍,而ArrayList则是增加50%。
LinkedList双向链表,不需要调整容量,也不是线程安全的。

考点分析

  • 不同类型容器适合的场景
    • Vector和ArrayList作为动态数组,其内部元素以数组形式顺序存储,适合随机访问场合。除了从尾部插入和删除元素外,性能会相对较差,比如在中间位置插入一个元素,需要移动后续所有元素。(结构修改是添加或删除一个或多个元素的任何操作,或显式调整后备数组的大小;仅设置元素的值不是结构修改)
    • LinkedList进行节点插入、删除高效的多,但是随机访问性能则比动态数组慢。
  • 考察Java集合框架
    • Java集合框架的设计结构(至少有一个整体印象)
    • Java提供的主要容器(集合和Map)类型,了解或掌握对应的数据结构、算法,思考具体技术选择。
    • 集合框架的演进与发展
  • 典型排序算法
    • 内部排序,至少掌握基础算法如归并排交换排序(冒泡、快排)、选择排序、插入排序等。
    • 外部排序,掌握利用内存或外部存储处理超大数据集,至少理解过程和思路。

知识拓展

  • Java 集合框架(狭义的集合框架,不包含Map和concurrent包下线程安全的容器)

2019-05-02_112025.png

  • List:有序集合
  • Set:不允许重复元素,是与List最明显的区别,也就是不存在两个对象equals返回true。经常用在保证元素唯一性的场合。Set的内部实现其实是基于Map实现的,只是Map类的马甲(TreeSet 代码里实际默认是利用 TreeMap 实现的,Java 类库创建了一个 Dummy 对象“PRESENT”作为 value,然后所有插入的元素其实是以键的形式放入了 TreeMap 里面;同理,HashSet 其实也是以 HashMap 为基础实现的)
    • TreeSet,支持自然顺序访问,但是增、删、包含等操作相对比较低效
    • HashSet则是利用哈希算法,理想情况下,如果哈希列正常,可以提供常数时间的增、删、包含等操作,但不保证有序
    • LinkedHashSet,内部构建了一个记录插入顺序的双向链表,因此提供了按照插入顺序遍历的能力,与此同时,也保证了常数时间的添加、删除、包含等操作,这些操作性能略低于 HashSet,因为需要维护链表的开销。
  • Queue/Deque:Java提供的标准队列结构的实现,除了集合的基本功能,还支持类似先入先出(FIFO,first-in-first-out)或后入现出(LIFO,last-in-first-out)等特定行为。
  • 每种集合的通用逻辑,都被抽象到相应的抽象类中,比如AbstractList集中了各种List操作的通用部分。
  • 这些集合不是完全孤立的,比如LinkedList本身,既是List也是Deque。
  • 上述集合都不是线程安全的,但并不代表这些集合完全不能支持并发编程的场景,在Collections工具类中,提供了一些列synchronized方法
    ```java static List synchronizedList(List list) List list = Collections.synchronizedList(new ArrayList());//基本实现是将每个基本方法都通过synchronized添加基本的同步支持,简单时用
  1. - **Java默认的排序算法**
  2. - 对于原始数据类型,目前使用的是所谓双轴快速排序(Dual-Pivot QuickSort),是一种改进的快速排序算法,早期版本是相对传统的快速排序,你可以阅读[源码](http://hg.openjdk.java.net/jdk/jdk/file/26ac622a4cab/src/java.base/share/classes/java/util/DualPivotQuicksort.java)。
  3. - 而对于对象数据类型,目前则是使用[TimSort](http://hg.openjdk.java.net/jdk/jdk/file/26ac622a4cab/src/java.base/share/classes/java/util/TimSort.java),思想上也是一种归并和二分插入排序(binarySort)结合的优化排序算法。TimSort 并不是 Java 的独创,简单说它的思路是查找数据集中已经排好序的分区(这里叫 run),然后合并这些分区来达到排序的目的。
  4. - 另外,Java 8 引入了并行排序算法(直接使用 parallelSort 方法),这是为了充分利用现代多核处理器的计算能力,底层实现基于 fork-join 框架(专栏后面会对 fork-join 进行相对详细的介绍),当处理的数据集比较小的时候,差距不明显,甚至还表现差一点;但是,当数据集增长到数万或百万以上时,提高就非常大了,具体还是取决于处理器和系统环境。
  5. - **集合框架的演进与发展**
  6. - Java8中,Java平台支持了LambadaStream,相应的Java集合框架也进行了大范围的增强,以支持类似为集合创建相应stream或者parallelStream的方法实现,可以非常方便的实现函数式代码。这些实现不是在抽象类里,而是以**默认方法**的形式实现在Collection的接口里(Java8在语言层面的新特性,允许接口实现默认方法)。
  7. - Java9中,Java标准类库提供了一系列**of**静态工厂方法,比如List.of()、Set.of(),简化了构建小的容器实例的代码量。(实践中,相当一部分集合实例都是容量相当有限,而且在生命周期中并不会进行修改)在原有的Java类库中,不得不写成:<br />
  8. ```java
  9. ArrayList<String> list = new ArrayList<>();
  10. list.add("Hello");
  11. list.add("World");

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

  1. List<String> simpleList = List.of("Hello","world");

另外,Java已经支持可变参数(varargs),但是官方类库还提供了一系列特定参数长度的方法(看起来似乎非常不优雅),其实是为了最有性能,JVM在处理变长参数时会有明显的额外开销。
image.png

一课一练

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

  1. 优先级队列,还需考虑VIP再分级,即同等级VIP的平权问题,所以除了考虑直接和VIP等级相关的优先级队列优先级规则问题,还得考虑同等级多个客户不被单一客户大量任务阻塞的问题。

精选留言

Vector、ArrayList、LinkedList均为线型的数据结构,但是从实现方式与应用场景中又存在差别。 1 底层实现方式 ArrayList内部用数组来实现;LinkedList内部采用双向链表实现;Vector内部用数组实现。 2 读写机制 ArrayList在执行插入元素是超过当前数组预定义的最大值时,数组需要扩容,扩容过程需要调用底层System.arraycopy()方法进行大量的数组复制操作;在删除元素时并不会减少数组的容量(如果需要缩小数组容量,可以调用trimToSize()方法);在查找元素时要遍历数组,对于非null的元素采取equals的方式寻找。 LinkedList在插入元素时,须创建一个新的Entry对象,并更新相应元素的前后元素的引用;在查找元素时,需遍历链表;在删除元素时,要遍历链表,找到要删除的元素,然后从链表上将此元素删除即可。 Vector与ArrayList仅在插入元素时容量扩充机制不一致。对于Vector,默认创建一个大小为10的Object数组,并将capacityIncrement设置为0;当插入元素数组大小不够时,如果capacityIncrement大于0,则将Object数组的大小扩大为现有size+capacityIncrement;如果capacityIncrement<=0,则将Object数组的大小扩大为现有大小的2倍。 3 读写效率 ArrayList对元素的增加和删除都会引起数组的内存分配空间动态发生变化。因此,对其进行插入和删除速度较慢,但检索速度很快。 LinkedList由于基于链表方式存放数据,增加和删除元素的速度较快,但是检索速度较慢。 4 线程安全性 ArrayList、LinkedList为非线程安全;Vector是基于synchronized实现的线程安全的ArrayList。 需要注意的是:单线程应尽量使用ArrayList,Vector因为同步会有性能损耗;即使在多线程环境下,我们可以利用Collections这个类中为我们提供的synchronizedList(List list)方法返回一个线程安全的同步列表对象。

问题回答 利用PriorityBlockingQueue或Disruptor可实现基于任务优先级为调度策略的执行调度系统。

集合:就像是一种容器。用于存储、获取、操作对象的容器。

  1. 数组的弊端 ①数组的长度不可变 ②数组没有提供可以查看有效元素个数的方法
  2. 集合的特点 ①集合的长度是可变的 ②集合可以存储任意类型的对象 ③集合只能存储对象
  3. 集合框架 java.util.Collection : 集合层次的根接口 |—- java.util.List: 有序的,可以重复的。

    1. |--- ArrayList: 采用数组结构存储元素。 查询操作多时选择
    2. |--- LinkedList: 采用链表结构存储元素。 增删操作多时选择
    3. |--- Vector:

    |—- java.util.Set: 无序的,不允许重复。

    1. |--- HashSet : Set 接口的典型实现类。
    2. 判断元素是否存在的依据是:先比较 hashCode 值,若 hashCode 存在,再通过 equals() 比较内容
    3. hashCode 值不存在,则直接存储
    4. 注意:重写 hashCode equals 二者需要保持一致!
    5. |--- LinkedHashSet: 相较于 HashSet 多了链表维护元素的顺序。遍历效率高于 HashSet 增删效率低于 HashSet
    6. |--- TreeSet : 拥有自己排序方式
    7. |-- 自然排序(Comparable):
    8. ①需要添加 TreeSet 集合中对象的类实现 Comparable 接口
    9. ②实现 compareTo(Object o) 方法
    10. |-- 定制排序(Comparator
    11. ①创建一个类实现 Comparator 接口
    12. ②实现 compare(Object o1, Object o2) 方法
    13. ③将该实现类的实例作为参数传递给 TreeSet 的构造器