Java集合

  • 前言
  • 基本概念
  • 列表List
  • 链表LinkedList
  • 映射Map
  • Map性能探讨
  • 集合Set
  • Set存储顺序
  • 队列Queue
  • 优先级队列
  • 双端队列
  • 迭代器Iterators
  • 集合功能
  • 最后总结
  • 最后的最后


前言

如果一个程序只包含固定数量的对象且对象的生命周期都是已知的,那么这是一个非常简单的程序。

程序总是根据运行时才知道的某些条件去创建新的对象。在此之前,无法知道所需对象的数量甚至确切类型。为了解决这个普遍的编程问题,需要在任意时刻和任意位置创建任意数量的对象。在实现方法时,选择不同的数据结构会导致其实现风格以及性能存在着很大差异。java.util 库提供了一套相当完整的集合类来解决这个问题,其中基本的类型有 List 、 Set 、 Queue 和 Map。这些类型也被称作容器类,下面讲详细介绍如何使用标准库中的集合类。

基本概念

Java集合类库采用“持有对象”的思想,并将其分为两个不同的概念,表示为类库的基本接口:

  1. 集合(Collection):一个独立元素的序列,这些元素都服从一条或多条规则。List 必须以插入的顺序保存元素, Set 不能包含重复元素, Queue 按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
  2. 映射(Map) : 一组成对的“键值对”对象,允许使用键来查找值。 ArrayList 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 map 允许我们使用一个对象来查找另一个对象,它也被称作关联数组(associative array),因为它将对象和其它对象关联在一起;或者称作字典(dictionary),因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。 Map 是强大的编程工具。

Java集合类库将接口(interfaces)与实现(implementations)分离。使用接口的目的是,如果想要改变具体实现,只需在创建时修改它就行了。因此,可以像下面这样创建一个 List :

List<User> apples = new ArrayList<>();

这种方式并非总是有效的,因为某些具体类有额外的功能。例如, LinkedList 具有 List 接口中未包含的额外方法,而 TreeMap 也具有在 Map 接口中未包含的方法。如果需要使用这些方法,就不能将它们向上转型为更通用的接口。

列表List

List承诺将元素保存在特定的序列中。 List 接口在 Collection 的基础上添加了许多方法,基本列表操作包括:

  • add() 用于插入元素
  • get() 用于随机访问元素
  • iterator() 获取序列上的一个 Iterator
  • stream() 生成元素的一个 Stream
package com.collection;

import java.util.ArrayList;
import java.util.List;

public class ListCase {

    public static void main(String[] args) {
        
        List<String> arrList = new ArrayList<>(15);
        
        arrList.add("红色");
        System.out.println("0: " +   arrList.get(0));

        arrList.add("黄色");
        System.out.println("1: " + arrList);

        arrList.remove(0);
        arrList.remove("黄色");
        System.out.println("2: " + arrList);
    }

}

输出:

0: 红色
1: [红色, 黄色]
2: []

有两种类型的 List :

  • ArrayList ,擅长随机访问元素,但在 List 中间插入和删除元素时速度较慢。
  • LinkedList ,它通过代价较低的在 List 中间进行的插入和删除操作,提供了优化的顺序访问。 LinkedList 对于随机访问来说相对较慢,但它具有比 ArrayList 更大的特征集。

链表LinkedList

LinkedList 也像 ArrayList 一样实现了基本的 List 接口,但它在 List 中间执行插入和删除操作时比 ArrayList 更高效。然而,它在随机访问操作效率方面却要逊色一些。

映射Map

将对象映射到其他对象的能力是解决编程问题的有效方法。映射表(map)数据结构就是为此设计的。Map表用来存放键/值对。如果提供了键,就能够查找到值。

package com.collection;

import java.util.HashMap;
import java.util.Map;

public class MapCase {

    public static void main(String[] args) {

        Map<Integer, String> map = new HashMap<>();
        map.put(0, "红色");
        map.put(1, "黄色");
        System.out.println("map: " +   map);
    }

}

输出:

map: {0=红色, 1=黄色}

Map性能探讨

性能是 Map 的基本问题,在 get() 中使用线性方法搜索一个键时会非常慢。这就是 HashMap 要加速的地方。它使用一个称为 哈希码 的特殊值来替代慢速搜索一个键。哈希码是一种从相关对象中获取一些信息并将其转换为该对象的“相对唯一” int 的方法。 hashCode() 是根类 Object 中的一个方法,因此所有 Java 对象都可以生成哈希码。 HashMap 获取对象的 hashCode() 并使用它来快速搜索键。这就使得性能有了显著的提升。

以下是基本的 Map 实现。 HashMap上的星号表示,在没有其他约束的情况下,这应该是你的默认选择,因为它针对速度进行了优化。其他实现强调其他特性,因此不如 HashMap 快。

java 集合 比较器 降序 java集合对比_List

散列是在 Map 中存储元素的最常用方法。

Map 中使用的键的要求与 Set 中的元素的要求相同。任何键必须具有 equals() 方法。如果键用于散列映射,则它还必须具有正确的 hashCode() 方法。如果键在 TreeMap 中使用,则必须实现 Comparable 接口。

集合Set

Set 不保存重复的元素。 Set 最常见的用途是测试归属性,可以很轻松地询问某个对象是否在一个 Set 中。因此,查找通常是 Set 最重要的操作,如果不关心元素顺序或并发性,通常会选择 HashSet 实现,该实现针对快速查找进行了优化。Set 就是一个 Collection ,只是行为不同。(这是继承和多态思想的典型应用:表现不同的行为。)

package com.collection;

import java.util.*;

public class SetCase {

    public static void main(String[] args) {
       
        Set<Integer> intSet = new HashSet<>();
        intSet.add(1);
        intSet.add(1);
        intSet.add(2);
        System.out.println("set: " +   intSet);
    }

}

输出:

set: [1, 2]

Set存储顺序

Set 需要一种维护存储顺序的方法,该顺序因 Set 的不同实现而异。因此,不同的 Set 实现不仅具有不同的行为,而且它们对可以放入特定 Set 中的对象类型也有不同的要求:

java 集合 比较器 降序 java集合对比_java 集合 比较器 降序_02

HashSet 上的星号表示,在没有其他约束的情况下,这应该是你的默认选择,因为它针对速度进行了优化。

队列Queue

队列是一个典型的“先进先出”(FIFO)集合。 即从集合的一端放入事物,再从另一端去获取它们,事物放入集合的顺序和被取出的顺序是相同的。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中尤为重要,因为它们可以安全地将对象从一个任务传输到另一个任务。

package com.collection;

import java.util.*;

public class QueueCase {

    public static void main(String[] args) {

        Queue<Integer> queue = new LinkedList<>();
        queue.offer(1);
        queue.offer(2);
        System.out.println("queue: " +   queue);
        
    }

}

输出:

queue: [1, 2]

peek() 和 element() 都返回队头元素而不删除它,但是如果队列为空,则 element() 抛出 NoSuchElementException ,而 peek() 返回 null 。 poll() 和 remove()* 都删除并返回队头元素,但如果队列为空,poll() 返回 null ,而 remove() 抛出 NoSuchElementException 。

优先级队列

先进先出(FIFO)描述了最典型的队列规则(queuing discipline)。队列规则是指在给定队列中的一组元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个弹出的元素应该是等待时间最长的元素。

优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。例如,在机场,当飞机临近起飞时,这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息传递系统,某些消息比其他消息更重要,应该尽快处理,而不管它们何时到达。在Java 5 中添加了 PriorityQueue ,以便自动实现这种行为。

当在 PriorityQueue 上调用 offer() 方法来插入一个对象时,该对象会在队列中被排序。 (5)默认的排序使用队列中对象的自然顺序(natural order),但是可以通过提供自己的 Comparator 来修改这个顺序。 PriorityQueue 确保在调用 peek() , poll() 或 remove() 方法时,获得的元素将是队列中优先级最高的元素。PriorityQueue 是允许重复的,最小的值具有最高的优先级(如果是 String ,空格也可以算作值,并且比字母的优先级高)。

双端队列

Deque (双端队列)就像一个队列,但是可以从任一端添加和删除元素。 Java 6为 Deque 添加了一个显式接口。

迭代器Iterators

迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为轻量级对象(lightweight object):创建它的代价小。因此,经常可以看到一些对迭代器有些奇怪的约束。例如,Java 的 Iterator 只能单向移动。这个 Iterator 只能用来:

  1. 使用 iterator() 方法要求集合返回一个 Iterator。 Iterator 将准备好返回序列中的第一个元素。
  2. 使用 next() 方法获得序列中的下一个元素。
  3. 使用 hasNext() 方法检查序列中是否还有元素。
  4. 使用 remove() 方法将迭代器最近返回的那个元素删除。
1. package com.collection;
import java.util.*;
public class IteratorCase {public static void main(String[] args) {
     
     List<String> arrList = new ArrayList<>(15);
     arrList.add("红色");
     arrList.add("黄色");
     Iterator<String> it = arrList.iterator();
     while(it.hasNext()) {
         String s = it.next();
         System.out.println(s);
     }

 }}

输出:

红色
黄色

集合功能

下面这个表格展示了可以对 Collection 执行的所有操作(不包括自动继承自 Object 的方法),因此,可以用 List , Set , Queue 或 Deque 执行这里的所有操作(这些接口可能也提供了一些其他的功能)。Map 不是从 Collection 继承的,所以要单独处理它。

java 集合 比较器 降序 java集合对比_java_03

这里没有提供用于随机访问元素的 get() 方法,因为 Collection 还包含 Set ,它维护自己的内部排序,所以随机访问查找就没有意义了。因此,要查找 Collection 中的元素必须使用迭代器。

最后总结

可以使用集合执行许多非常有用的操作,而不需要太多努力。但是,在某些时候,为了正确地使用它们而不得不更多地了解集合,特别是,必须充分了解散列操作以编写自己的 hashCode() 方法(并且必须知道何时需要),并且你必须充分了解各种集合实现,以根据你的需求选择合适的集合。

  1. Collection 保存单一的元素,而 Map 包含相关联的键值对。使用 Java 泛型,可以指定集合中保存的对象的类型,因此不能将错误类型的对象放入集合中,并且在从集合中获取元素时,不必进行类型转换。各种 Collection 和各种 Map 都可以在你向其中添加更多的元素时,自动调整其尺寸大小。集合不能保存基本类型,但自动装箱机制会负责执行基本类型和集合中保存的包装类型之间的双向转换。
  2. 像数组一样, List 也将数字索引与对象相关联,因此,数组和 List 都是有序集合。
  3. 如果要执行大量的随机访问,则使用 ArrayList ,如果要经常从表中间插入或删除元素,则应该使用 LinkedList 。
  4. 队列和堆栈的行为是通过 LinkedList 提供的。
  5. Map 是一种将对象(而非数字)与对象相关联的设计。 HashMap 专为快速访问而设计,而 TreeMap 保持键始终处于排序状态,所以没有 HashMap 快。 LinkedHashMap 按插入顺序保存其元素,但使用散列提供快速访问的能力。
  6. Set 不接受重复元素。 HashSet 提供最快的查询速度,而 TreeSet 保持元素处于排序状态。 LinkedHashSet 按插入顺序保存其元素,但使用散列提供快速访问的能力。
  7. 不要在新代码中使用遗留类 Vector ,Hashtable 和 Stack 。