集合与泛型

集合

List

ArrayList

ArrayList 是基于动态数组(也叫做可变数组)实现的。其底层实现可以总结如下:

  1. 数组存储ArrayList 底层使用一个数组来存储元素。初始时,数组有一个默认容量,通常是10。
  2. 动态扩容:当添加新元素导致数组容量不足时,ArrayList 会自动扩容。扩容的过程是创建一个新的数组,其容量通常是旧数组容量的1.5倍或2倍,然后将旧数组的元素复制到新数组中。
  3. 快速访问:由于底层是数组,ArrayList 支持通过索引快速访问元素,时间复杂度为O(1)。
  4. 插入和删除:在中间插入或删除元素需要移动后续元素,时间复杂度为O(n),其中n是数组的大小。

LinkedList

LinkedList 是基于双向链表实现的。其底层实现可以总结如下:

  1. 双向链表结构LinkedList 使用节点(Node)来存储元素,每个节点包含一个数据域(data)和两个指针(前驱指针prev和后继指针next),分别指向前一个节点和后一个节点。
  2. 动态存储:与数组不同,链表不需要预先分配固定大小的内存。每添加一个元素,就创建一个新的节点并链接到链表中。
  3. 插入和删除:在链表的任意位置插入或删除元素的时间复杂度为O(1),因为只需要调整相邻节点的指针。然而,定位到某个位置的时间复杂度为O(n),因为需要从头开始遍历链表。
  4. 访问元素:访问链表中的元素通常需要从头或尾开始遍历,时间复杂度为O(n)。

对比

  1. 访问速度ArrayList 由于使用数组存储,可以通过索引快速访问元素,而 LinkedList 需要遍历链表,访问速度较慢。
  2. 插入和删除速度LinkedList 在插入和删除操作方面更高效,尤其是在中间位置插入和删除,而 ArrayList 则需要移动大量元素。
  3. 适用场景ArrayList 适用于频繁访问元素的场景,而 LinkedList 适用于频繁插入和删除元素的场景。

Set

List接口表示一个有序的集合,允许包含重复的元素。Set接口表示一个无序不包含重复元素的集合。

HashSet

HashSet 是基于哈希表(HashMap)实现的集合类,它不允许存储重复元素,并且没有特定的顺序。

底层实现
  1. 哈希表(HashMap)HashSet 底层使用 HashMap 来存储元素。每个元素作为 HashMap 的键,HashMap 的值是一个固定的常量对象。
  2. 哈希函数HashSet 使用元素的 hashCode() 方法来计算元素的哈希值,并通过哈希值决定元素在表中的位置。
  3. 处理哈希冲突HashMap 采用链地址法(链表或红黑树)来处理哈希冲突。当哈希冲突较少时,冲突的元素存储在链表中;当冲突较多时,链表转换为红黑树以提高查询效率。
  4. 加载因子和扩容:当哈希表的元素数量超过阈值(容量 * 加载因子)时,哈希表会扩容并重新哈希已有的元素,新的容量通常是旧容量的两倍。

LinkedHashSet

LinkedHashSetHashSet 的子类,除了哈希表,还维护了一个双向链表以保证元素的插入顺序。

底层实现
  1. 哈希表(HashMap):与 HashSet 类似,LinkedHashSet 也使用 HashMap 来存储元素。
  2. 双向链表LinkedHashSet 通过在 HashMap 的基础上,维护一个双向链表来记录元素的插入顺序。每个节点不仅包含哈希表中的键值对,还包含指向前一个和后一个节点的指针。
  3. 保持插入顺序:通过双向链表,LinkedHashSet 可以保证遍历时按照元素的插入顺序进行。
HashSet和LinkedHashSet的工作原理

使用HashSet、LinkedHashSet存储自定义类的实例时,自定义类需要重写equals()和hashCode()方法。重写equals()确保Set集合的无序性,重写hashCode()用于生成对象的哈希码,哈希码确定对象在哈希表中的位置。如果两个对象通过equals()比较相等,则它们的hashCode也必须相等。

  • 哈希表HashSetLinkedHashSet 使用哈希表来存储元素。每个元素通过 hashCode 方法生成一个哈希码,根据哈希码决定元素的存储位置。
  • 哈希冲突:如果两个对象的哈希码相同,则会发生哈希冲突。此时,HashSetLinkedHashSet 会进一步使用 equals 方法来比较对象,判断是否真的是相同对象(即使哈希码相同,不一定是相同对象)。
  • 唯一性:通过 hashCodeequals 方法,HashSetLinkedHashSet 能够保证集合中不包含重复的元素。

TreeSet

TreeSet 是基于红黑树(Red-Black Tree)实现的集合类,元素是有序的,默认按自然顺序排序,也可以使用指定的比较器(Comparator)进行排序。

底层实现
  1. 红黑树(TreeMap)TreeSet 底层使用 TreeMap 来存储元素。每个元素作为 TreeMap 的键,TreeMap 的值是一个固定的常量对象。
  2. 排序:红黑树是一种自平衡的二叉搜索树,通过在插入和删除时进行旋转和重新着色来保持树的平衡,从而保证了 TreeSet 中元素的有序性。
  3. 搜索、插入、删除:在红黑树中,这些操作的时间复杂度均为 O(log n)。
注意

使用TreeSet存储自定义类的实例时,需要自定义类需要实现Comparable接口的compareTo方法,或者在定义TreeSet时,给TreeSet构造器传递一个实现了Comparator接口的实例。这样才能确保 TreeSet 能够正确地对元素进行排序。

总结

  • HashSet
    • 基于哈希表实现。
    • 快速查找、插入和删除(平均时间复杂度 O(1))。
    • 元素无序。
  • LinkedHashSet
    • 基于哈希表和双向链表实现。
    • 保证插入顺序。
    • 快速查找、插入和删除(平均时间复杂度 O(1)),但有额外的内存开销。
  • TreeSet
    • 基于红黑树实现。
    • 元素有序。
    • 查找、插入和删除的时间复杂度为 O(log n)。

Map

HashMap

HashMap 是基于哈希表(hash table)实现的,它提供了高效的查找、插入和删除操作。

底层实现
  1. 哈希表HashMap 使用一个数组(桶)来存储键值对,每个桶对应一个链表或红黑树(链地址法处理哈希冲突)。

  2. 哈希函数:键的 hashCode 方法用于计算哈希值,决定元素在哈希表中的位置。

  3. 哈希冲突处理

    :当两个键的哈希值相同(哈希冲突)时,元素存储在同一个桶的链表或红黑树中。

    • 链表:当冲突较少时,使用链表存储冲突的元素。
    • 红黑树:当冲突较多时,链表转换为红黑树,以提高查询效率。
特性
  • 时间复杂度:查找、插入和删除操作的平均时间复杂度为 O(1)。
  • 无序存储HashMap 中的元素没有特定的顺序。
  • 允许 null:可以存储一个 null 键和多个 null 值。
  • 非线程安全:多个线程同时访问和修改 HashMap 可能导致数据不一致。

LinkedHashMap

LinkedHashMapHashMap 的子类,具有 HashMap 的所有特性,并且维护了插入顺序或访问顺序。

底层实现
  1. 哈希表:与 HashMap 相同,使用数组和链表或红黑树处理哈希冲突。
  2. 双向链表:维护元素的插入顺序或访问顺序。每个元素还包含前后指针,形成一个双向链表。
特性
  • 有序存储:按照插入顺序或访问顺序存储元素。
  • 时间复杂度:与 HashMap 相同,查找、插入和删除操作的平均时间复杂度为 O(1)。
  • 允许 null:可以存储一个 null 键和多个 null 值。
  • 非线程安全:多个线程同时访问和修改 LinkedHashMap 可能导致数据不一致。

TreeMap

TreeMap 是基于红黑树(Red-Black Tree)实现的,键值对按照键的自然顺序或指定的比较器顺序进行排序。

底层实现
  1. 红黑树:一种自平衡二叉搜索树,通过节点的旋转和重新着色来保持平衡。
  2. 排序:默认情况下,键按其自然顺序排序。如果提供了 Comparator,则按 Comparator 的顺序排序。
特性
  • 有序存储:键值对按照键的顺序存储和迭代。
  • 时间复杂度:查找、插入和删除操作的时间复杂度为 O(log n)。
  • 不允许 nullTreeMap 不允许 null 键,但允许 null 值。
  • 非线程安全:多个线程同时访问和修改 TreeMap 可能导致数据不一致。

Hashtable

Hashtable 是基于哈希表实现的,与 HashMap 类似,但它是线程安全的,适用于多线程环境。

底层实现
  1. 哈希表:使用数组和链表处理哈希冲突。
  2. 同步:所有方法都使用 synchronized 关键字进行同步,确保线程安全。
特性
  • 线程安全:所有方法都是同步的,适用于多线程环境。
  • 不允许 null:不允许 null 键和 null 值。
  • 时间复杂度:查找、插入和删除操作的平均时间复杂度为 O(1)。
  • 无序存储:元素没有特定的顺序。

Properties

PropertiesHashtable 的子类,专门用于处理属性文件(键和值都是字符串)。

底层实现
  1. 哈希表:与 Hashtable 相同,使用数组和链表处理哈希冲突。
  2. 字符串键值对:键和值都必须是字符串(String)。
特性
  • 专用于属性文件:键和值都必须是字符串,常用于读取和保存配置文件。
  • 线程安全:继承了 Hashtable 的同步特性,适用于多线程环境。
  • 不允许 null:不允许 null 键和 null 值。
  • 无序存储:元素没有特定的顺序。
  • 额外功能:提供 loadstore 方法,用于从输入流读取属性文件或将属性保存到输出流。

比较与选择

  • HashMap:适用于对顺序不敏感且需要高效查找和更新操作的场景。
  • LinkedHashMap:适用于需要维护插入顺序或访问顺序的场景。
  • TreeMap:适用于需要有序存储和快速范围查询的场景。
  • Hashtable:适用于需要线程安全的多线程环境,但不允许 null 键和值。
  • Properties:适用于读取和保存配置文件,键和值都必须是字符串。

泛型

泛型也称参数类型,起到标识和约束作用。通过泛型处理不同类型对象的类、接口和方法时,无需显式地进行类型转换。

泛型类

public class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

使用泛型类

Box<String> stringBox = new Box<>();
stringBox.setValue("Hello");
String value = stringBox.getValue();

泛型方法

public <E> E method(E e) {
        return e;
}

public <E> void voidMethod(E e) {

}    

需要注意的是:

创建自定义泛型类的对象,没有指明泛型参数类型时,按照Object处理,但不等价于Object

泛型不能是基本类型,只能是引用类型

泛型不能用于异常

泛型可以有多个,逗号隔开。如Map<K, V>

泛型通配符

<?> 被称为无界通配符,表示任意类型。它常用于方法参数中,表示该方法可以接受任何类型的参数,而无需关心具体的类型是什么。

当你只需要读取数据,而不需要修改数据时,可以使用无界通配符。例如,打印集合中的所有元素。

通配符描述
?表示任何类型
? extends T表示类型是T或T的子类型
? super T表示类型是T或T的超类型
  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_陈你好

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值