Java集合实战

ArrayList

排序

Collections.sort()默认将元素升序排序,前提是集合的元素支持自热顺序,即实现了 Comparable 接口的,如 String, Integer。Collections.sort()也可以传一个 Comparable 参数实现自定义排序。

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(20);
    list.add(10);
    list.add(16);
    list.add(14);
    Collections.sort(list);
    System.out.println("ASC: " + list); // ASC: [10, 14, 16, 20]
    list.sort(Comparator.comparing(Integer::intValue).reversed());
    System.out.println("DESC: " + list); // DESC: [20, 16, 14, 10]
}

如果集合元素是其他对象,就用对象的属性做排序依据。那么 sort 方法就要传排序参数了。

public static void main(String[] args) {
    List<Person> list = new ArrayList<>();
    list.add(new Person("张三", 20));
    list.add(new Person("李四", 14));
    list.add(new Person("王五", 16));
    list.add(new Person("赵六", 10));

    list.sort(Comparator.comparing(Person::getAge));
    System.out.println("ASC: " + list);
    list.sort(Comparator.comparing(Person::getAge).reversed());
    System.out.println("DESC: " + list);
}

找集合中的最大最小值

public static void main(String[] args) {
    List<Integer> l = new ArrayList<>();
    l.add(1);
    l.add(2);
    l.add(3);
    Integer min = l.stream().min(Comparator.comparingInt(Integer::intValue)).get();
    Integer max = l.stream().max(Comparator.comparingInt(Integer::intValue)).get();
    System.out.println(min + ", " + max);
    
    l.sort(Comparator.comparingInt(Integer::intValue));
    System.out.println(l.get(0) + ", " + l.get(l.size()-1));
}

TreeSet

TreeSet 是支持排序的 Set。它实际就是 TreeMap。如果 TreeSet 的元素支持自热排序,它默认排序是自然排序,也就是升序;否则就必须给 TreeSet 的构造器传一个 Comparator 参数指定排序依据,否则添加元素会报错。

private static void order() {
    Set<Integer> treeSet = new TreeSet<>(Comparator.comparing(Integer::intValue).reversed());
    treeSet.add(20);
    treeSet.add(10);
    treeSet.add(16);
    treeSet.add(14);
    System.out.println("DESC: " + treeSet); // DSC: [20, 16, 14, 10]
}

同样地,TreeMap 是一个支持排序的 Map。
注意,TreeSet、TreeMap 的 key 不能为 null,因为 key 要用来排序。

LinkedHashSet

LinkedHashSet 是元素保持插入顺序的、不重复的集合,LinkedHashMap 是元素保持插入顺序的、不重复的键值对集合。

Queue

队列(Queue)是一种经常使用的集合。Queue 实际上是实现了一个先进先出(FIFO:First In First Out)的有序表。它和 List 的区别在于,List 可以在任意位置添加和删除元素,而 Queue 只有两个操作:

  1. 把元素添加到队列末尾;
  2. 从队列头部取出元素。

在Java的标准库中,队列接口 Queue 定义了以下几个方法:

  • int size():获取队列长度;
  • boolean add(E)/boolean offer(E):添加元素到队尾;
  • E remove()/E poll():获取队首元素并从队列中删除;
  • E element()/E peek():获取队首元素但并不从队列中删除。

对于具体的实现类,有的 Queue 有最大队列长度限制,有的 Queue 没有。注意到添加、删除和获取队列元素总是有两个方法,这是因为在添加或获取元素失败时,这两个方法的行为是不同的。我们用一个表格总结如下:

throw Exception 返回 false 或 null
添加元素到队尾 add(E e) boolean offer(E e)
取队首元素并删除 E remove() E poll()
取队首元素但不删除 E element() E peek()

Deque

Deque 是双端队列(Double Ended Queue),读作 deck,它允许两头都进,两头都出

The name deque is short for "double ended queue" and is usually pronounced "deck".

由于 Deque 继承了 Queue 接口,因此它继承了 Queue 接口的所有方法。

除了 Queue 接口中可用的方法之外,Deque 接口还包括以下方法:

  • addFirst()/addLast() - 在双端队列的开头/末尾添加指定的元素。如果双端队列已满,则引发异常。

  • offerFirst()/offerLast() - 在双端队列的开头/末尾添加指定的元素。如果双端队列已满,则返回 false。

  • getFirst()/getLast() - 返回双端队列的第一个/最后一个元素。如果双端队列为空,则引发异常。

  • peekFirst()/peekLast() - 返回双端队列的第一个/最后一个元素。如果双端队列为空,则返回 null。

  • removeFirst()/removeLast() - 返回并删除双端队列的第一个/最后一个元素。如果双端队列为空,则引发异常。

  • pollFirst()/pollLast() - 返回并删除双端队列的第一个/最后一个元素。如果双端队列为空,则返回 null。

使用 Deque 时,最好使用 Deque 自己提供的方法,因为语义清晰。如果直接写deque.offer(),我们就需要思考,offer()实际上是offerLast(),我们明确地写上offerLast(),不需要思考就能一眼看出这是添加到队尾。

因此,使用 Deque,推荐总是明确调用offerLast()/offerFirst()或者pollFirst()/pollLast()方法。

ArrayDeque 是 Deque 的经典实现。

public static void main(String[] args) {
    // 使用ArrayDeque类创建Deque
    Deque<Integer> numbers = new ArrayDeque<>();

    //添加元素到Deque
    numbers.offer(1);
    numbers.offerLast(2);
    numbers.offerFirst(3);
    System.out.println("Deque: " + numbers); // Deque: [3, 1, 2]

    //访问Deque的元素
    int firstElement = numbers.peekFirst();
    System.out.println("第一个元素: " + firstElement); // 3

    int lastElement = numbers.peekLast();
    System.out.println("最后一个元素: " + lastElement); // 2

    //从Deque 移除元素
    int removedNumber1 = numbers.pollFirst();
    System.out.println("移除第一个元素: " + removedNumber1); // 3

    int removedNumber2 = numbers.pollLast();
    System.out.println("移除最后一个元素: " + removedNumber2); // 2

    System.out.println("更新后的Deque: " + numbers); // 1
}

ArrayDeque 和 LinkedList

ArrayDeque 和 LinkedList 都实现了 Deque 接口。但是,它们之间存在一些差异。

  • LinkedList 支持空元素,而 ArrayDeque 不支持
  • 链表中的每个节点都包含到其他节点的链接。这就是 LinkedList 比 ArrayDeque 需要更多存储空间的原因。
  • 如果要实现队列或双端队列数据结构,则 ArrayDeque 可能比 LinkedList 快。

双端队列作为堆栈数据结构

Java Collections 框架的 Stack 类提供了堆栈的实现。

但是,建议 Deque 用作堆栈而不是 Stack 类。Stack 继承 Vector 集合,Stack 的方法都是引用 Vector 的方法, 而 Vector 的方法都加了同步锁,所以效率低。

Deque 接口提供的用于实现堆栈的方法:

  • push() - 在双端队列的开头添加元素
  • pop() - 从双端队列的开头删除元素
  • peek() - 从双端队列的开头返回一个元素
public static void main(String[] args) {
    ArrayDeque<String> stack = new ArrayDeque<>();

    //将元素添加到stack
    stack.push("Dog");
    stack.push("Cat");
    stack.push("Horse");
    System.out.println("Stack: " + stack);

    //从堆栈顶部访问元素
    String element = stack.peek();
    System.out.println("访问元素: " + element);

    //从堆栈顶部删除元素
    String remElement = stack.pop();
    System.out.println("删除element: " + remElement);
}

PriorityQueue

PriorityQueue 是优先级队列,它默认根据自然顺序排序,也可以通过 Comparator 给每个元素定义“优先级”,跟 TreeSet 有相似之处。

public static void main(String[] args) {
    PriorityQueue<Integer> q = new PriorityQueue<>(Comparator.comparing(Integer::intValue));
    q.add(2);
    q.add(12);
    q.add(14);
    q.add(10);
    while(true){
        Integer e = q.poll();
        if (e == null) {
            break;
        }
        System.out.print(e + " "); // 2 10 12 14
    }
} 

PriorityQueue 的iterator()不保证以任何特定顺序遍历队列元素。如果要遍历可以转成数组再排序遍历。
示例中的 while 循环不是遍历,是根据优先级不断出队列。

EnumSet

EnumSet、EnumMap 的 key 要求为 Enum 类型,在操作枚举类的时候可以考虑是否能用得上这种集合,目前我还没在业务中使用过。

参考资料

posted @ 2024-05-20 07:56  富大龙  阅读(22)  评论(0编辑  收藏  举报