当前位置: 首页 > 图灵资讯 > 技术篇> 集合详解之 Collection(附面试题)

集合详解之 Collection(附面试题)

来源:图灵教育
时间:2023-06-06 09:28:58

先来看看集合的继承关系图,如下图所示:

集合详解之 Collection(附面试题)_List

其中:

  • 外框为虚线表示接口,边框为实线表示类;
  • 箭头为虚线的表示实现了界面,箭头为实线的表示继承了类别。

为了便于理解,我隐藏了一些与本文内容无关的信息,这些隐藏的内容将在下一章中详细介绍。

从图中可以看出,集合的根节点是 Collection,而 Collection 下面还提供了两个常见的集合,分别是:

  • List:使用最多的有序集合,提供方便的新增、修改、删除操作;
  • Set:不允许在许多需要保证元素唯一性的场景中使用重复元素。

下面我们详细介绍一下集合类。

集合使用1)Vector

Vector 是 Java 如果不需要线程安全,则不建议使用早期提供的线程安全有序集合。毕竟,同步是有线程费用的。

使用示例代码:

Vector vector = new Vector();vector.add("dog");vector.add("cat");vector.remove("cat");System.out.println(vector);

程序执行结果:[dog]

2)ArrayList

ArrayList 它是最常见的非线程安全有序集合。由于内部存储在数组中,随机访问效率很高,但非尾部插入和删除性能较低。如果将元素插入中间,则所有元素将向后移动。ArrayList 的使用与 Vector 类似。

3)LinkedList

LinkedList 采用双向链表数据结构,增删效率较高,随机访问效率较差。

LinkedList 除上述两种操作方法外,还增加了几种操作方法,如 offer() 、peek() 等,详情请参考以下代码:

LinkedList linkedList = new LinkedList();// 添加元素linkedlisttt添加元素.offer("bird");linkedList.push("cat");linkedList.push("dog");// System获得第一个元素.out.println(linkedList.peek());// 获取第一个元素,删除此元素System.out.println(linkedList.poll());System.out.println(linkedList);

程序执行结果:

dogdog[cat, bird]

4)HashSet

HashSet 它是一个没有重复元素的集合。虽然是 Set 集合的子类实际上是 HashMap 相关源代码如下:

public HashSet() {    map = new HashMap<>();}

因此 HashSet 无序集合,无法保证元素的顺序。

HashSet 默认容量为 16,每次扩充 0.75 倍,相关源代码如下:

public HashSet(Collection<? extends E> c) {    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));    addAll(c);}

HashSet 的使用与 Vector 类似。

5)TreeSet

TreeSet 集合实现了自动排序,即集合 TreeSet 将您插入数据进行自动排序。

示例代码如下:

TreeSet treeSet = new TreeSet();treeSet.add("dog");treeSet.add("camel");treeSet.add("cat");treeSet.add("ant");System.out.println(treeSet);

程序执行结果:[ant, camel, cat, dog]

可以看出,TreeSet 的使用与 Vector 类似地,只实现了自动排序。

6)LinkedHashSet

LinkedHashSet 是按元素的 hashCode 该值决定了元素的存储位置,但同时使用链表来维护元素的顺序,使其看起来像是按插入顺序保存的。

LinkedHashSet 的使用与 Vector 类似。

集合与数组

集合和数组的转换可以使用 toArray() 和 Arrays.asList() 为实现,请参考以下代码示例:

List<String> list = new ArrayList();list.add("cat");list.add("dog");// String[]集合转数组 arr = list.toArray(new String[list.size()]);// 数组转集List<String> list2 = Arrays.asList(arr);

集合与数组的区别可以参考「应用数组和排序算法 + 面试题」的内容。

集合排序

在 Java 语言排序提供了两种方式:Comparable 和 Comparator,它们的区别也是常见的面试问题之一。让我们彻底了解一下 Comparable 和 Comparator 使用与差异。

1)Comparable

Comparable 位于 java.lang 包下是一个排序接口,也就是说,如果实现了一个类别 Comparable 接口意味着该类具有排序功能。

Comparable 界面只包含一个函数,定义如下:

package java.lang;import java.util.*;public interface Comparable {  public int compareTo(T o);}

Comparable 使用示例,请参考以下代码:

class ComparableTest {    public static void main(String[] args) {        Dog[] dogs = new Dog[]{                new Dog(“老旺财”, 10),                new Dog(“小旺财”, 3),                new Dog(二旺财”, 5),        };        // Comparable 排序        Arrays.sort(dogs);        for (Dog d : dogs) {            System.out.println(d.getName() + ":" + d.getAge());        }    }}class Dog implements Comparable<Dog> {    private String name;    private int age;    @Override    public int compareTo(Dog o) {        return age - o.age;    }    public Dog(String name, int age) {        this.name = name;        this.age = age;    }    public String getName() {        return name;    }    public int getAge() {        return age;    }}

程序执行结果:

小旺财:32旺财:5老旺财:10

如果 Dog 类未实现 Comparable 执行代码会报告程序异常的信息,错误信息如下:

Exception in thread "main" java.lang.ClassCastException: xxx cannot be cast to java.lang.Comparable

compareTo() 返回值有三种:

  • e1.compareTo(e2) > 0 即 e1 > e2;
  • e1.compareTo(e2) = 0 即 e1 = e2;
  • e1.compareTo(e2) < 0 即 e1 < e2。
2)Comparator

Comparator 位于外部比较器的外部比较器 java.util 包下,之所以说 Comparator 它是一种外部比较器,因为它不需要在比较类中实现 Comparator 接口,但要创建一个新的比较器类来进行比较和排序。

Comparator 接口包含的主要方法是 compare()定义如下:

public interface Comparator<T> {  int compare(T o1, T o2);}

Comparator 使用示例,请参考以下代码:

class ComparatorTest {    public static void main(String[] args) {        Dog[] dogs = new Dog[]{                new Dog(“老旺财”, 10),                new Dog(“小旺财”, 3),                new Dog(二旺财”, 5),        };        // Comparator 排序        Arrays.sort(dogs,new DogComparator());        for (Dog d : dogs) {            System.out.println(d.getName() + ":" + d.getAge());        }    }}class DogComparator implements Comparator<Dog> {    @Override    public int compare(Dog o1, Dog o2) {        return o1.getAge() - o2.getAge();    }}class Dog {    private String name;    private int age;    public Dog(String name, int age) {        this.name = name;        this.age = age;    }    public String getName() {        return name;    }    public int getAge() {        return age;    }}

程序执行结果:

小旺财:32旺财:5老旺财:10

相关面试题1.List 和 Set 有什么区别?

答:区别分为以下几个方面:

  • List 允许有多个 null 值,Set 只允许一个 null 值;
  • List 允许重复元素,Set 重复元素不允许;
  • List 能保证各元素的存储顺序,Set 元素的存储顺序无法保证。
2.哪种集合可以实现自动排序?

答:TreeSet 集合实现元素的自动排序,即元素的自动排序功能无需任何操作即可实现。

3.Vector 和 ArrayList 初始化大小和容量扩展有什么区别?

答:Vector 和 ArrayList 默认容量为 10、源代码如下。

Vector 默认容量源代码:

public Vector() {    this(10);}

ArrayList 默认容量源代码:

private static final int DEFAULT_CAPACITY = 10;

Vector 默认增加容量扩张 1 倍,源代码如下:

private void grow(int minCapacity) {    // overflow-conscious code    int oldCapacity = elementData.length;    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?                                     capacityIncrement : oldCapacity);    if (newCapacity - minCapacity < 0)        newCapacity = minCapacity;    if (newCapacity - MAX_ARRAY_SIZE > 0)        newCapacity = hugeCapacity(minCapacity);    elementData = Arrays.copyOf(elementData, newCapacity);}

其中 capacityIncrement 为初始化 Vector 默认情况为指定情况 0。

ArrayList 默认容量扩大的增加可能会增加 0.5 倍(oldCapacity + (oldCapacity >> 1),源代码如下(JDK 8):

private void grow(int minCapacity) {    // overflow-conscious code    int oldCapacity = elementData.length;    int newCapacity = oldCapacity + (oldCapacity >> 1);    if (newCapacity - minCapacity < 0)        newCapacity = minCapacity;    if (newCapacity - MAX_ARRAY_SIZE > 0)        newCapacity = hugeCapacity(minCapacity);    // minCapacity is usually close to size, so this is a win:    elementData = Arrays.copyOf(elementData, newCapacity);}

4.Vector、ArrayList、LinkedList 有什么区别?

答:这三者都是 List 因此,子类的功能相似,如添加和删除操作、搜索元素等,但在性能、线程安全等方面表现不同,差异如下:

  • Vector 是 Java 它使用早期提供的动态数组 synchronized 为了保证线程安全,如果不推荐使用非线程安全,毕竟线程同步是有性能费用的;
  • ArrayList 它是最常用的动态数组,线程本身并不安全,所以性能要好得多 Vector 同样,它也是动态调整容量的,只是 Vector 扩容会增加 1 倍,而 ArrayList 会增加 50%;
  • LinkedList 它是一个双向链表集合,所以它不需要像上面两个那样调整容量,它也是一个非线程安全集合。
5.Vector、ArrayList、LinkedList 使用场景有什么区别?

答:Vector 和 ArrayList 内部结构以数组形式存储,非常适合随机访问,但非尾部删除或新性能较差。例如,如果我们在中间插入一个元素,我们需要移动后续的所有元素。

LinkedList 插入和删除元素更有效,但随机访问性能比上述两个动态数组慢。

6.Collection 和 Collections 有什么区别?

答:Collection 和 Collections 区别如下:

  • Collection 它是一个集合的上级接口,主要继承它 List 和 Set;
  • Collections 它提供了一些静态方法来实现集合类的帮助类,例如 Collections.sort() 排序、Collections.reverse() 逆序等。
7.以下选项未继承 Collection 接口的是?

A:ListB:SetC:MapD:HashSet

答:C

8.LinkedHashSet 如何保证有序性和独特性?

答:LinkedHashSet 底层数据结构由哈希表和链表组成,链表保证了元素的有序存储和取出,哈希表保证了元素的独特性。

9.HashSet 如何确保数据不可重复?

答:HashSet 其实底层就是 HashMap,只不过 HashSet 实现了 Set 并将数据作为接口 K 值,而 V 值总是用相同的虚拟值来保存,我们可以看到源代码:

public boolean add(E e) {    return map.put(e, PRESENT)==null;// 调用 HashMap 的 put 方法,PRESENT 虚值}自始至终都是一样的

由于 HashMap 的 K 值本身不允许重复,而且在 HashMap 中如果 K/V 同时,会使用新的 V 覆盖掉旧的 V,然后返回旧的 V,那么在 HashSet 执行这句话总会一个一个回来的 false,导致插入失败,保证了数据的不可重复性。

10.执行以下程序会产生什么结果?为什么?

Integer num = 10;Integer num2 = 5;System.out.println(num.compareTo(num2);

答:程序输出的结果是 1,因为 Integer 默认实现了 compareTo 定义自然排序规则的方法,所以当 num 比 num2 大时会返回 1,Integer 相关源代码如下:

public int compareTo(Integer anotherInteger) {    return compare(this.value, anotherInteger.value);}public static int compare(int x, int y) {    return (x < y) ? -1 : ((x == y) ? -1 : ((x == y) ? 0 : 1);}

11.如何用程序实现先进先出的栈结构?

答:集合中可以使用 Stack 实现,Stack 采用标准后进先出的栈结构 Stack 中的 pop() 方法返回栈顶元素并删除该元素,示例代码如下。

Stack stack = new Stack();stack.push("a");stack.push("b");stack.push("c");for (int i = 0; i < 3; i++) {    // 移除并返回栈顶元素    System.out.print(stack.pop() + " ");}

程序执行结果:c b a

12.LinkedList 中的 peek() 和 poll() 有什么区别?

答:peek() 方法返回第一个元素,但不删除当前元素,当元素不存在时返回 null;poll() 方法返回第一个元素并删除该元素,当元素不存在时返回 null。

13.Comparable 和 Comparator 有什么区别?

答:Comparable 和 Comparator 主要区别如下:

  • Comparable 位于 java.lang 包下,而 Comparator 位于 java.util 包下;
  • Comparable 在排序类内部实现, Comparator 外部实现排序类;
  • Comparable 需要重写 CompareTo() 方法,而 Comparator 需要重写 Compare() 方法;
  • Comparator 在类的外部实现,更加灵活方便。
总结

本文介绍的所有集合都是自我实现的 Collection,所以它们都有相同的操作方法,比如 add()、addAll()、remove() 等,Collection 接口方法列表如下图所示:

集合详解之 Collection(附面试题)_数组_02

当然,一些集合也扩展了自己独特的方法,比如原来的方法 LinkedList 的 offer()、push() 等方法。本文还提供了数组和集合互转的方法,List.toArray() 将集合转换为数组,Arrays.asList(array) 将数组转换为集合。最后介绍了 Comparable 和 Comparator 使用和差异,Comparable 和 Comparator 是 Java 语言排序提供了两种排序方法,Comparable 位于 java.lang 包下,如果实现了一个类别 Comparable 接口,这意味着这类有排序功能;而且 Comparator 位于 java.util 它是一种外部比较器,不需要在比较类中实现 Comparator 接口,但要创建一个新的比较器类来进行比较和排序。